summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2013-02-10 03:28:06 +0100
committerhut <hut@lavabit.com>2013-02-10 03:35:27 +0100
commitd1a1173ddc315f21a3d468f43ac55aa43d31883d (patch)
tree10d728b37294856eb21e6e962089ac38507d868c /ranger
parent184e84284d2f4e48631a4b94b87ba76126470206 (diff)
downloadranger-d1a1173ddc315f21a3d468f43ac55aa43d31883d.tar.gz
replaced tabs with 4 spaces in all python files
PEP 8 (Style Guide for Python Code) suggests the use of 4 spaces:
http://www.python.org/dev/peps/pep-0008/#indentation

If you need to use tools like "git blame", you can use the -w option to
ignore this commit entirely.  Patches will continue to work if you
substitute tabs with 4 spaces everywhere except in the Makefile.
Diffstat (limited to 'ranger')
-rw-r--r--ranger/__init__.py30
-rw-r--r--ranger/api/__init__.py34
-rw-r--r--ranger/api/commands.py636
-rw-r--r--ranger/colorschemes/default.py196
-rw-r--r--ranger/colorschemes/jungle.py16
-rw-r--r--ranger/colorschemes/snow.py48
-rw-r--r--ranger/config/commands.py1620
-rw-r--r--ranger/container/bookmarks.py380
-rw-r--r--ranger/container/history.py252
-rw-r--r--ranger/container/settingobject.py318
-rw-r--r--ranger/container/tags.py200
-rw-r--r--ranger/core/actions.py2254
-rw-r--r--ranger/core/environment.py200
-rw-r--r--ranger/core/fm.py518
-rw-r--r--ranger/core/loader.py654
-rw-r--r--ranger/core/main.py606
-rw-r--r--ranger/core/runner.py386
-rw-r--r--ranger/core/shared.py96
-rw-r--r--ranger/core/tab.py306
-rw-r--r--ranger/ext/accumulator.py180
-rw-r--r--ranger/ext/curses_interrupt_handler.py34
-rw-r--r--ranger/ext/direction.py234
-rw-r--r--ranger/ext/get_executables.py68
-rw-r--r--ranger/ext/human_readable.py78
-rw-r--r--ranger/ext/img_display.py132
-rw-r--r--ranger/ext/iter_tools.py74
-rw-r--r--ranger/ext/keybinding_parser.py406
-rw-r--r--ranger/ext/lazy_property.py52
-rw-r--r--ranger/ext/mount_path.py14
-rw-r--r--ranger/ext/next_available_filename.py20
-rw-r--r--ranger/ext/openstruct.py8
-rw-r--r--ranger/ext/popen_forked.py34
-rw-r--r--ranger/ext/relative_symlink.py32
-rwxr-xr-xranger/ext/rifle.py762
-rw-r--r--ranger/ext/run_forked.py36
-rw-r--r--ranger/ext/shell_escape.py24
-rw-r--r--ranger/ext/signals.py412
-rw-r--r--ranger/ext/spawn.py20
-rw-r--r--ranger/ext/widestring.py250
-rw-r--r--ranger/fsobject/directory.py1032
-rw-r--r--ranger/fsobject/file.py130
-rw-r--r--ranger/fsobject/fsobject.py528
-rw-r--r--ranger/gui/ansi.py282
-rw-r--r--ranger/gui/bar.py242
-rw-r--r--ranger/gui/color.py26
-rw-r--r--ranger/gui/colorscheme.py196
-rw-r--r--ranger/gui/context.py40
-rw-r--r--ranger/gui/curses_shortcuts.py110
-rw-r--r--ranger/gui/displayable.py612
-rw-r--r--ranger/gui/mouse_event.py100
-rw-r--r--ranger/gui/ui.py710
-rw-r--r--ranger/gui/widgets/__init__.py8
-rw-r--r--ranger/gui/widgets/browsercolumn.py758
-rw-r--r--ranger/gui/widgets/browserview.py662
-rw-r--r--ranger/gui/widgets/console.py838
-rw-r--r--ranger/gui/widgets/pager.py398
-rw-r--r--ranger/gui/widgets/statusbar.py546
-rw-r--r--ranger/gui/widgets/taskview.py168
-rw-r--r--ranger/gui/widgets/titlebar.py278
59 files changed, 9642 insertions, 9642 deletions
diff --git a/ranger/__init__.py b/ranger/__init__.py
index 3c608d41..6894258d 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -37,23 +37,23 @@ CONFDIR = '~/.config/ranger'
 # Example usage in the code:
 # import ranger; ranger.log("hello world")
 def log(*objects, **keywords):
-	"""
-	Writes objects to a logfile (for the purpose of debugging only.)
-	Has the same arguments as print() in python3.
-	"""
-	from ranger import arg
-	if LOGFILE is None or not arg.debug or arg.clean: return
-	start = keywords.get('start', 'ranger:')
-	sep   = keywords.get('sep', ' ')
-	end   = keywords.get('end', '\n')
-	_file = keywords['file'] if 'file' in keywords else open(LOGFILE, 'a')
-	_file.write(sep.join(map(str, (start, ) + objects)) + end)
+    """
+    Writes objects to a logfile (for the purpose of debugging only.)
+    Has the same arguments as print() in python3.
+    """
+    from ranger import arg
+    if LOGFILE is None or not arg.debug or arg.clean: return
+    start = keywords.get('start', 'ranger:')
+    sep   = keywords.get('sep', ' ')
+    end   = keywords.get('end', '\n')
+    _file = keywords['file'] if 'file' in keywords else open(LOGFILE, 'a')
+    _file.write(sep.join(map(str, (start, ) + objects)) + end)
 
 
 def log_traceback():
-	from ranger import arg
-	if LOGFILE is None or not arg.debug or arg.clean: return
-	import traceback
-	traceback.print_stack(file=open(LOGFILE, 'a'))
+    from ranger import arg
+    if LOGFILE is None or not arg.debug or arg.clean: return
+    import traceback
+    traceback.print_stack(file=open(LOGFILE, 'a'))
 
 from ranger.core.main import main
diff --git a/ranger/api/__init__.py b/ranger/api/__init__.py
index 1121197a..9fbf2418 100644
--- a/ranger/api/__init__.py
+++ b/ranger/api/__init__.py
@@ -5,24 +5,24 @@ Files in this module contain helper functions used in configuration files.
 # Hooks for use in plugins:
 
 def hook_init(fm):
-	"""
-	Parameters:
-	  fm = the file manager instance
-	Return Value:
-	  ignored
+    """
+    Parameters:
+      fm = the file manager instance
+    Return Value:
+      ignored
 
-	This hook is executed after fm is initialized but before fm.ui is
-	initialized.  You can safely print to stdout and have access to fm to add
-	keybindings and such.
-	"""
+    This hook is executed after fm is initialized but before fm.ui is
+    initialized.  You can safely print to stdout and have access to fm to add
+    keybindings and such.
+    """
 
 def hook_ready(fm):
-	"""
-	Parameters:
-	  fm = the file manager instance
-	Return Value:
-	  ignored
+    """
+    Parameters:
+      fm = the file manager instance
+    Return Value:
+      ignored
 
-	This hook is executed after the user interface is initialized.  You should
-	NOT print anything to stdout anymore from here on.  Use fm.notify instead.
-	"""
+    This hook is executed after the user interface is initialized.  You should
+    NOT print anything to stdout anymore from here on.  Use fm.notify instead.
+    """
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 4a9688a0..ce381e18 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -17,329 +17,329 @@ DELETE_WARNING = 'delete seriously? '
 def alias(*_): pass # COMPAT
 
 class CommandContainer(object):
-	def __init__(self):
-		self.commands = {}
-
-	def __getitem__(self, key):
-		return self.commands[key]
-
-	def alias(self, name, full_command):
-		try:
-			cmd = type(name, (AliasCommand, ), dict())
-			cmd._based_function = name
-			cmd._function_name = name
-			cmd._object_name = name
-			cmd._line = full_command
-			self.commands[name] = cmd
-
-		except:
-			pass
-
-	def load_commands_from_module(self, module):
-		for var in vars(module).values():
-			try:
-				if issubclass(var, Command) and var != Command \
-						and var != FunctionCommand:
-					self.commands[var.get_name()] = var
-			except TypeError:
-				pass
-
-	def load_commands_from_object(self, obj, filtr):
-		for attribute_name in dir(obj):
-			if attribute_name[0] == '_' or attribute_name not in filtr:
-				continue
-			attribute = getattr(obj, attribute_name)
-			if hasattr(attribute, '__call__'):
-				cmd = type(attribute_name, (FunctionCommand, ), dict())
-				cmd._based_function = attribute
-				cmd._function_name = attribute.__name__
-				cmd._object_name = obj.__class__.__name__
-				self.commands[attribute_name] = cmd
-
-	def get_command(self, name, abbrev=True):
-		if abbrev:
-			lst = [cls for cmd, cls in self.commands.items() \
-					if cls.allow_abbrev and cmd.startswith(name) \
-					or cmd == name]
-			if len(lst) == 0:
-				raise KeyError
-			if len(lst) == 1:
-				return lst[0]
-			if self.commands[name] in lst:
-				return self.commands[name]
-			raise ValueError("Ambiguous command")
-		else:
-			try:
-				return self.commands[name]
-			except KeyError:
-				return None
-
-	def command_generator(self, start):
-		return (cmd + ' ' for cmd in self.commands if cmd.startswith(start))
+    def __init__(self):
+        self.commands = {}
+
+    def __getitem__(self, key):
+        return self.commands[key]
+
+    def alias(self, name, full_command):
+        try:
+            cmd = type(name, (AliasCommand, ), dict())
+            cmd._based_function = name
+            cmd._function_name = name
+            cmd._object_name = name
+            cmd._line = full_command
+            self.commands[name] = cmd
+
+        except:
+            pass
+
+    def load_commands_from_module(self, module):
+        for var in vars(module).values():
+            try:
+                if issubclass(var, Command) and var != Command \
+                        and var != FunctionCommand:
+                    self.commands[var.get_name()] = var
+            except TypeError:
+                pass
+
+    def load_commands_from_object(self, obj, filtr):
+        for attribute_name in dir(obj):
+            if attribute_name[0] == '_' or attribute_name not in filtr:
+                continue
+            attribute = getattr(obj, attribute_name)
+            if hasattr(attribute, '__call__'):
+                cmd = type(attribute_name, (FunctionCommand, ), dict())
+                cmd._based_function = attribute
+                cmd._function_name = attribute.__name__
+                cmd._object_name = obj.__class__.__name__
+                self.commands[attribute_name] = cmd
+
+    def get_command(self, name, abbrev=True):
+        if abbrev:
+            lst = [cls for cmd, cls in self.commands.items() \
+                    if cls.allow_abbrev and cmd.startswith(name) \
+                    or cmd == name]
+            if len(lst) == 0:
+                raise KeyError
+            if len(lst) == 1:
+                return lst[0]
+            if self.commands[name] in lst:
+                return self.commands[name]
+            raise ValueError("Ambiguous command")
+        else:
+            try:
+                return self.commands[name]
+            except KeyError:
+                return None
+
+    def command_generator(self, start):
+        return (cmd + ' ' for cmd in self.commands if cmd.startswith(start))
 
 
 class Command(FileManagerAware):
-	"""Abstract command class"""
-	name = None
-	allow_abbrev = True
-	resolve_macros = True
-	escape_macros_for_shell = False
-	quantifier = None
-	_shifted = 0
-	_setting_line = None
-
-	def __init__(self, line, quantifier=None):
-		self.line = line
-		self.args = line.split()
-		self.quantifier = quantifier
-		try:
-			self.firstpart = line[:line.rindex(' ') + 1]
-		except ValueError:
-			self.firstpart = ''
-
-	@classmethod
-	def get_name(self):
-		classdict = self.__mro__[0].__dict__
-		if 'name' in classdict and classdict['name']:
-			return self.name
-		else:
-			return self.__name__
-
-	def execute(self):
-		"""Override this"""
-
-	def tab(self):
-		"""Override this"""
-
-	def quick(self):
-		"""Override this"""
-
-	def cancel(self):
-		"""Override this"""
-
-	# Easy ways to get information
-	def arg(self, n):
-		"""Returns the nth space separated word"""
-		try:
-			return self.args[n]
-		except IndexError:
-			return ""
-
-	def rest(self, n):
-		"""Returns everything from and after arg(n)"""
-		got_space = True
-		word_count = 0
-		for i in range(len(self.line)):
-			if self.line[i] == " ":
-				if not got_space:
-					got_space = True
-					word_count += 1
-			elif got_space:
-				got_space = False
-				if word_count == n + self._shifted:
-					return self.line[i:]
-		return ""
-
-	def start(self, n):
-		"""Returns everything until (inclusively) arg(n)"""
-		return ' '.join(self.args[:n]) + " " # XXX
-
-	def shift(self):
-		del self.args[0]
-		self._setting_line = None
-		self._shifted += 1
-
-	def tabinsert(self, word):
-		return ''.join([self._tabinsert_left, word, self._tabinsert_right])
-
-	def parse_setting_line(self):
-		if self._setting_line is not None:
-			return self._setting_line
-		match = SETTINGS_RE.match(self.rest(1))
-		if match:
-			self.firstpart += match.group(1) + '='
-			result = [match.group(1), match.group(2), True]
-		else:
-			result = [self.arg(1), self.rest(2), ' ' in self.rest(1)]
-		self._setting_line = result
-		return result
-
-	# XXX: Lazy properties? Not so smart? self.line can change after all!
-	@lazy_property
-	def _tabinsert_left(self):
-		try:
-			return self.line[:self.line[0:self.pos].rindex(' ') + 1]
-		except ValueError:
-			return ''
-
-	@lazy_property
-	def _tabinsert_right(self):
-		return self.line[self.pos:]
-
-	# COMPAT: this is still used in old commands.py configs
-	def _tab_only_directories(self):
-		from os.path import dirname, basename, expanduser, join
-
-		cwd = self.fm.thisdir.path
-
-		rel_dest = self.rest(1)
-
-		# expand the tilde into the user directory
-		if rel_dest.startswith('~'):
-			rel_dest = expanduser(rel_dest)
-
-		# define some shortcuts
-		abs_dest = join(cwd, rel_dest)
-		abs_dirname = dirname(abs_dest)
-		rel_basename = basename(rel_dest)
-		rel_dirname = dirname(rel_dest)
-
-		try:
-			# are we at the end of a directory?
-			if rel_dest.endswith('/') or rel_dest == '':
-				_, dirnames, _ = next(os.walk(abs_dest))
-
-			# are we in the middle of the filename?
-			else:
-				_, dirnames, _ = next(os.walk(abs_dirname))
-				dirnames = [dn for dn in dirnames \
-						if dn.startswith(rel_basename)]
-		except (OSError, StopIteration):
-			# os.walk found nothing
-			pass
-		else:
-			dirnames.sort()
-
-			# no results, return None
-			if len(dirnames) == 0:
-				return
-
-			# one result. since it must be a directory, append a slash.
-			if len(dirnames) == 1:
-				return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
-
-			# more than one result. append no slash, so the user can
-			# manually type in the slash to advance into that directory
-			return (self.start(1) + join(rel_dirname, dirname)
-					for dirname in dirnames)
-
-	def _tab_directory_content(self):
-		from os.path import dirname, basename, expanduser, join
-
-		cwd = self.fm.thisdir.path
-
-		rel_dest = self.rest(1)
-
-		# expand the tilde into the user directory
-		if rel_dest.startswith('~'):
-			rel_dest = expanduser(rel_dest)
-
-		# define some shortcuts
-		abs_dest = join(cwd, rel_dest)
-		abs_dirname = dirname(abs_dest)
-		rel_basename = basename(rel_dest)
-		rel_dirname = dirname(rel_dest)
-
-		try:
-			# are we at the end of a directory?
-			if rel_dest.endswith('/') or rel_dest == '':
-				_, dirnames, filenames = next(os.walk(abs_dest))
-				names = dirnames + filenames
-
-			# are we in the middle of the filename?
-			else:
-				_, dirnames, filenames = next(os.walk(abs_dirname))
-				names = [name for name in (dirnames + filenames) \
-						if name.startswith(rel_basename)]
-		except (OSError, StopIteration):
-			# os.walk found nothing
-			pass
-		else:
-			names.sort()
-
-			# no results, return None
-			if len(names) == 0:
-				return
-
-			# one result. append a slash if it's a directory
-			if len(names) == 1:
-				path = join(rel_dirname, names[0])
-				slash = '/' if os.path.isdir(path) else ''
-				return self.start(1) + path + slash
-
-			# more than one result. append no slash, so the user can
-			# manually type in the slash to advance into that directory
-			return (self.start(1) + join(rel_dirname, name) for name in names)
-
-	def _tab_through_executables(self):
-		from ranger.ext.get_executables import get_executables
-		programs = [program for program in get_executables() if \
-				program.startswith(self.rest(1))]
-		if not programs:
-			return
-		if len(programs) == 1:
-			return self.start(1) + programs[0]
-		programs.sort()
-		return (self.start(1) + program for program in programs)
+    """Abstract command class"""
+    name = None
+    allow_abbrev = True
+    resolve_macros = True
+    escape_macros_for_shell = False
+    quantifier = None
+    _shifted = 0
+    _setting_line = None
+
+    def __init__(self, line, quantifier=None):
+        self.line = line
+        self.args = line.split()
+        self.quantifier = quantifier
+        try:
+            self.firstpart = line[:line.rindex(' ') + 1]
+        except ValueError:
+            self.firstpart = ''
+
+    @classmethod
+    def get_name(self):
+        classdict = self.__mro__[0].__dict__
+        if 'name' in classdict and classdict['name']:
+            return self.name
+        else:
+            return self.__name__
+
+    def execute(self):
+        """Override this"""
+
+    def tab(self):
+        """Override this"""
+
+    def quick(self):
+        """Override this"""
+
+    def cancel(self):
+        """Override this"""
+
+    # Easy ways to get information
+    def arg(self, n):
+        """Returns the nth space separated word"""
+        try:
+            return self.args[n]
+        except IndexError:
+            return ""
+
+    def rest(self, n):
+        """Returns everything from and after arg(n)"""
+        got_space = True
+        word_count = 0
+        for i in range(len(self.line)):
+            if self.line[i] == " ":
+                if not got_space:
+                    got_space = True
+                    word_count += 1
+            elif got_space:
+                got_space = False
+                if word_count == n + self._shifted:
+                    return self.line[i:]
+        return ""
+
+    def start(self, n):
+        """Returns everything until (inclusively) arg(n)"""
+        return ' '.join(self.args[:n]) + " " # XXX
+
+    def shift(self):
+        del self.args[0]
+        self._setting_line = None
+        self._shifted += 1
+
+    def tabinsert(self, word):
+        return ''.join([self._tabinsert_left, word, self._tabinsert_right])
+
+    def parse_setting_line(self):
+        if self._setting_line is not None:
+            return self._setting_line
+        match = SETTINGS_RE.match(self.rest(1))
+        if match:
+            self.firstpart += match.group(1) + '='
+            result = [match.group(1), match.group(2), True]
+        else:
+            result = [self.arg(1), self.rest(2), ' ' in self.rest(1)]
+        self._setting_line = result
+        return result
+
+    # XXX: Lazy properties? Not so smart? self.line can change after all!
+    @lazy_property
+    def _tabinsert_left(self):
+        try:
+            return self.line[:self.line[0:self.pos].rindex(' ') + 1]
+        except ValueError:
+            return ''
+
+    @lazy_property
+    def _tabinsert_right(self):
+        return self.line[self.pos:]
+
+    # COMPAT: this is still used in old commands.py configs
+    def _tab_only_directories(self):
+        from os.path import dirname, basename, expanduser, join
+
+        cwd = self.fm.thisdir.path
+
+        rel_dest = self.rest(1)
+
+        # expand the tilde into the user directory
+        if rel_dest.startswith('~'):
+            rel_dest = expanduser(rel_dest)
+
+        # define some shortcuts
+        abs_dest = join(cwd, rel_dest)
+        abs_dirname = dirname(abs_dest)
+        rel_basename = basename(rel_dest)
+        rel_dirname = dirname(rel_dest)
+
+        try:
+            # are we at the end of a directory?
+            if rel_dest.endswith('/') or rel_dest == '':
+                _, dirnames, _ = next(os.walk(abs_dest))
+
+            # are we in the middle of the filename?
+            else:
+                _, dirnames, _ = next(os.walk(abs_dirname))
+                dirnames = [dn for dn in dirnames \
+                        if dn.startswith(rel_basename)]
+        except (OSError, StopIteration):
+            # os.walk found nothing
+            pass
+        else:
+            dirnames.sort()
+
+            # no results, return None
+            if len(dirnames) == 0:
+                return
+
+            # one result. since it must be a directory, append a slash.
+            if len(dirnames) == 1:
+                return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
+
+            # more than one result. append no slash, so the user can
+            # manually type in the slash to advance into that directory
+            return (self.start(1) + join(rel_dirname, dirname)
+                    for dirname in dirnames)
+
+    def _tab_directory_content(self):
+        from os.path import dirname, basename, expanduser, join
+
+        cwd = self.fm.thisdir.path
+
+        rel_dest = self.rest(1)
+
+        # expand the tilde into the user directory
+        if rel_dest.startswith('~'):
+            rel_dest = expanduser(rel_dest)
+
+        # define some shortcuts
+        abs_dest = join(cwd, rel_dest)
+        abs_dirname = dirname(abs_dest)
+        rel_basename = basename(rel_dest)
+        rel_dirname = dirname(rel_dest)
+
+        try:
+            # are we at the end of a directory?
+            if rel_dest.endswith('/') or rel_dest == '':
+                _, dirnames, filenames = next(os.walk(abs_dest))
+                names = dirnames + filenames
+
+            # are we in the middle of the filename?
+            else:
+                _, dirnames, filenames = next(os.walk(abs_dirname))
+                names = [name for name in (dirnames + filenames) \
+                        if name.startswith(rel_basename)]
+        except (OSError, StopIteration):
+            # os.walk found nothing
+            pass
+        else:
+            names.sort()
+
+            # no results, return None
+            if len(names) == 0:
+                return
+
+            # one result. append a slash if it's a directory
+            if len(names) == 1:
+                path = join(rel_dirname, names[0])
+                slash = '/' if os.path.isdir(path) else ''
+                return self.start(1) + path + slash
+
+            # more than one result. append no slash, so the user can
+            # manually type in the slash to advance into that directory
+            return (self.start(1) + join(rel_dirname, name) for name in names)
+
+    def _tab_through_executables(self):
+        from ranger.ext.get_executables import get_executables
+        programs = [program for program in get_executables() if \
+                program.startswith(self.rest(1))]
+        if not programs:
+            return
+        if len(programs) == 1:
+            return self.start(1) + programs[0]
+        programs.sort()
+        return (self.start(1) + program for program in programs)
 
 
 class FunctionCommand(Command):
-	_based_function = None
-	_object_name = ""
-	_function_name = "unknown"
-	def execute(self):
-		if not self._based_function:
-			return
-		if len(self.args) == 1:
-			try:
-				return self._based_function(**{'narg':self.quantifier})
-			except TypeError:
-				return self._based_function()
-
-		args, keywords = list(), dict()
-		for arg in self.args[1:]:
-			equal_sign = arg.find("=")
-			value = arg if (equal_sign is -1) else arg[equal_sign + 1:]
-			try:
-				value = int(value)
-			except:
-				if value in ('True', 'False'):
-					value = (value == 'True')
-				else:
-					try:
-						value = float(value)
-					except:
-						pass
-
-			if equal_sign == -1:
-				args.append(value)
-			else:
-				keywords[arg[:equal_sign]] = value
-
-		if self.quantifier is not None:
-			keywords['narg'] = self.quantifier
-
-		try:
-			if self.quantifier is None:
-				return self._based_function(*args, **keywords)
-			else:
-				try:
-					return self._based_function(*args, **keywords)
-				except TypeError:
-					del keywords['narg']
-					return self._based_function(*args, **keywords)
-		except TypeError:
-			if ranger.arg.debug:
-				raise
-			else:
-				self.fm.notify("Bad arguments for %s.%s: %s, %s" %
-						(self._object_name, self._function_name,
-							repr(args), repr(keywords)), bad=True)
+    _based_function = None
+    _object_name = ""
+    _function_name = "unknown"
+    def execute(self):
+        if not self._based_function:
+            return
+        if len(self.args) == 1:
+            try:
+                return self._based_function(**{'narg':self.quantifier})
+            except TypeError:
+                return self._based_function()
+
+        args, keywords = list(), dict()
+        for arg in self.args[1:]:
+            equal_sign = arg.find("=")
+            value = arg if (equal_sign is -1) else arg[equal_sign + 1:]
+            try:
+                value = int(value)
+            except:
+                if value in ('True', 'False'):
+                    value = (value == 'True')
+                else:
+                    try:
+                        value = float(value)
+                    except:
+                        pass
+
+            if equal_sign == -1:
+                args.append(value)
+            else:
+                keywords[arg[:equal_sign]] = value
+
+        if self.quantifier is not None:
+            keywords['narg'] = self.quantifier
+
+        try:
+            if self.quantifier is None:
+                return self._based_function(*args, **keywords)
+            else:
+                try:
+                    return self._based_function(*args, **keywords)
+                except TypeError:
+                    del keywords['narg']
+                    return self._based_function(*args, **keywords)
+        except TypeError:
+            if ranger.arg.debug:
+                raise
+            else:
+                self.fm.notify("Bad arguments for %s.%s: %s, %s" %
+                        (self._object_name, self._function_name,
+                            repr(args), repr(keywords)), bad=True)
 
 class AliasCommand(Command):
-	_based_function = None
-	_object_name = ""
-	_function_name = "unknown"
-	_line = ""
-	def execute(self):
-		self.fm.execute_console(self._line + ' ' + self.rest(1))
+    _based_function = None
+    _object_name = ""
+    _function_name = "unknown"
+    _line = ""
+    def execute(self):
+        self.fm.execute_console(self._line + ' ' + self.rest(1))
diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py
index 839ec13d..58c79962 100644
--- a/ranger/colorschemes/default.py
+++ b/ranger/colorschemes/default.py
@@ -5,111 +5,111 @@ from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import *
 
 class Default(ColorScheme):
-	progress_bar_color = blue
+    progress_bar_color = blue
 
-	def use(self, context):
-		fg, bg, attr = default_colors
+    def use(self, context):
+        fg, bg, attr = default_colors
 
-		if context.reset:
-			return default_colors
+        if context.reset:
+            return default_colors
 
-		elif context.in_browser:
-			if context.selected:
-				attr = reverse
-			else:
-				attr = normal
-			if context.empty or context.error:
-				bg = red
-			if context.border:
-				fg = default
-			if context.media:
-				if context.image:
-					fg = yellow
-				else:
-					fg = magenta
-			if context.container:
-				fg = red
-			if context.directory:
-				attr |= bold
-				fg = blue
-			elif context.executable and not \
-					any((context.media, context.container,
-						context.fifo, context.socket)):
-				attr |= bold
-				fg = green
-			if context.socket:
-				fg = magenta
-				attr |= bold
-			if context.fifo or context.device:
-				fg = yellow
-				if context.device:
-					attr |= bold
-			if context.link:
-				fg = context.good and cyan or magenta
-			if context.tag_marker and not context.selected:
-				attr |= bold
-				if fg in (red, magenta):
-					fg = white
-				else:
-					fg = red
-			if not context.selected and (context.cut or context.copied):
-				fg = black
-				attr |= bold
-			if context.main_column:
-				if context.selected:
-					attr |= bold
-				if context.marked:
-					attr |= bold
-					fg = yellow
-			if context.badinfo:
-				if attr & reverse:
-					bg = magenta
-				else:
-					fg = magenta
+        elif context.in_browser:
+            if context.selected:
+                attr = reverse
+            else:
+                attr = normal
+            if context.empty or context.error:
+                bg = red
+            if context.border:
+                fg = default
+            if context.media:
+                if context.image:
+                    fg = yellow
+                else:
+                    fg = magenta
+            if context.container:
+                fg = red
+            if context.directory:
+                attr |= bold
+                fg = blue
+            elif context.executable and not \
+                    any((context.media, context.container,
+                        context.fifo, context.socket)):
+                attr |= bold
+                fg = green
+            if context.socket:
+                fg = magenta
+                attr |= bold
+            if context.fifo or context.device:
+                fg = yellow
+                if context.device:
+                    attr |= bold
+            if context.link:
+                fg = context.good and cyan or magenta
+            if context.tag_marker and not context.selected:
+                attr |= bold
+                if fg in (red, magenta):
+                    fg = white
+                else:
+                    fg = red
+            if not context.selected and (context.cut or context.copied):
+                fg = black
+                attr |= bold
+            if context.main_column:
+                if context.selected:
+                    attr |= bold
+                if context.marked:
+                    attr |= bold
+                    fg = yellow
+            if context.badinfo:
+                if attr & reverse:
+                    bg = magenta
+                else:
+                    fg = magenta
 
-		elif context.in_titlebar:
-			attr |= bold
-			if context.hostname:
-				fg = context.bad and red or green
-			elif context.directory:
-				fg = blue
-			elif context.tab:
-				if context.good:
-					bg = green
-			elif context.link:
-				fg = cyan
+        elif context.in_titlebar:
+            attr |= bold
+            if context.hostname:
+                fg = context.bad and red or green
+            elif context.directory:
+                fg = blue
+            elif context.tab:
+                if context.good:
+                    bg = green
+            elif context.link:
+                fg = cyan
 
-		elif context.in_statusbar:
-			if context.permissions:
-				if context.good:
-					fg = cyan
-				elif context.bad:
-					fg = magenta
-			if context.marked:
-				attr |= bold | reverse
-				fg = yellow
-			if context.message:
-				if context.bad:
-					attr |= bold
-					fg = red
-			if context.loaded:
-				bg = self.progress_bar_color
+        elif context.in_statusbar:
+            if context.permissions:
+                if context.good:
+                    fg = cyan
+                elif context.bad:
+                    fg = magenta
+            if context.marked:
+                attr |= bold | reverse
+                fg = yellow
+            if context.message:
+                if context.bad:
+                    attr |= bold
+                    fg = red
+            if context.loaded:
+                bg = self.progress_bar_color
 
-		if context.text:
-			if context.highlight:
-				attr |= reverse
+        if context.text:
+            if context.highlight:
+                attr |= reverse
 
-		if context.in_taskview:
-			if context.title:
-				fg = blue
+        if context.in_taskview:
+            if context.title:
+                fg = blue
 
-			if context.selected:
-				attr |= reverse
+            if context.selected:
+                attr |= reverse
 
-			if context.loaded:
-				if context.selected:
-					fg = self.progress_bar_color
-				else:
-					bg = self.progress_bar_color
+            if context.loaded:
+                if context.selected:
+                    fg = self.progress_bar_color
+                else:
+                    bg = self.progress_bar_color
 
-		return fg, bg, attr
+        return fg, bg, attr
diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py
index 8feeda03..01a5ab1f 100644
--- a/ranger/colorschemes/jungle.py
+++ b/ranger/colorschemes/jungle.py
@@ -5,14 +5,14 @@ from ranger.gui.color import *
 from ranger.colorschemes.default import Default
 
 class Scheme(Default):
-	progress_bar_color = green
-	def use(self, context):
-		fg, bg, attr = Default.use(self, context)
+    progress_bar_color = green
+    def use(self, context):
+        fg, bg, attr = Default.use(self, context)
 
-		if context.directory and not context.marked and not context.link:
-			fg = green
+        if context.directory and not context.marked and not context.link:
+            fg = green
 
-		if context.in_titlebar and context.hostname:
-			fg = red if context.bad else blue
+        if context.in_titlebar and context.hostname:
+            fg = red if context.bad else blue
 
-		return fg, bg, attr
+        return fg, bg, attr
diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py
index 50a41b58..e3b433b6 100644
--- a/ranger/colorschemes/snow.py
+++ b/ranger/colorschemes/snow.py
@@ -5,34 +5,34 @@ from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import *
 
 class Snow(ColorScheme):
-	def use(self, context):
-		fg, bg, attr = default_colors
+    def use(self, context):
+        fg, bg, attr = default_colors
 
-		if context.reset:
-			pass
+        if context.reset:
+            pass
 
-		elif context.in_browser:
-			if context.selected:
-				attr = reverse
-			if context.directory:
-				attr |= bold
+        elif context.in_browser:
+            if context.selected:
+                attr = reverse
+            if context.directory:
+                attr |= bold
 
-		elif context.highlight:
-			attr |= reverse
+        elif context.highlight:
+            attr |= reverse
 
-		elif context.in_titlebar and context.tab and context.good:
-			attr |= reverse
+        elif context.in_titlebar and context.tab and context.good:
+            attr |= reverse
 
-		elif context.in_statusbar:
-			if context.loaded:
-				attr |= reverse
-			if context.marked:
-				attr |= reverse
+        elif context.in_statusbar:
+            if context.loaded:
+                attr |= reverse
+            if context.marked:
+                attr |= reverse
 
-		elif context.in_taskview:
-			if context.selected:
-				attr |= bold
-			if context.loaded:
-				attr |= reverse
+        elif context.in_taskview:
+            if context.selected:
+                attr |= bold
+            if context.loaded:
+                attr |= reverse
 
-		return fg, bg, attr
+        return fg, bg, attr
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 636a949e..14e3f918 100644
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -79,1005 +79,1005 @@
 from ranger.api.commands import *
 
 class alias(Command):
-	"""
-	:alias <newcommand> <oldcommand>
+    """
+    :alias <newcommand> <oldcommand>
 
-	Copies the oldcommand as newcommand.
-	"""
+    Copies the oldcommand as newcommand.
+    """
 
-	context = 'browser'
-	resolve_macros = False
+    context = 'browser'
+    resolve_macros = False
 
-	def execute(self):
-		if not self.arg(1) or not self.arg(2):
-			self.fm.notify('Syntax: alias <newcommand> <oldcommand>', bad=True)
-		else:
-			self.fm.commands.alias(self.arg(1), self.rest(2))
+    def execute(self):
+        if not self.arg(1) or not self.arg(2):
+            self.fm.notify('Syntax: alias <newcommand> <oldcommand>', bad=True)
+        else:
+            self.fm.commands.alias(self.arg(1), self.rest(2))
 
 class cd(Command):
-	"""
-	:cd [-r] <dirname>
-
-	The cd command changes the directory.
-	The command 'cd -' is equivalent to typing ``.
-	Using the option "-r" will get you to the real path.
-	"""
-
-	def execute(self):
-		import os.path
-		if self.arg(1) == '-r':
-			self.shift()
-			destination = os.path.realpath(self.rest(1))
-			if os.path.isfile(destination):
-				destination = os.path.dirname(destination)
-		else:
-			destination = self.rest(1)
-
-		if not destination:
-			destination = '~'
-
-		if destination == '-':
-			self.fm.enter_bookmark('`')
-		else:
-			self.fm.cd(destination)
-
-	def tab(self):
-		import os
-		from os.path import dirname, basename, expanduser, join
-
-		cwd = self.fm.thisdir.path
-		rel_dest = self.rest(1)
-
-		bookmarks = [v.path for v in self.fm.bookmarks.dct.values()
-				if rel_dest in v.path ]
-
-		# expand the tilde into the user directory
-		if rel_dest.startswith('~'):
-			rel_dest = expanduser(rel_dest)
-
-		# define some shortcuts
-		abs_dest = join(cwd, rel_dest)
-		abs_dirname = dirname(abs_dest)
-		rel_basename = basename(rel_dest)
-		rel_dirname = dirname(rel_dest)
-
-		try:
-			# are we at the end of a directory?
-			if rel_dest.endswith('/') or rel_dest == '':
-				_, dirnames, _ = next(os.walk(abs_dest))
-
-			# are we in the middle of the filename?
-			else:
-				_, dirnames, _ = next(os.walk(abs_dirname))
-				dirnames = [dn for dn in dirnames \
-						if dn.startswith(rel_basename)]
-		except (OSError, StopIteration):
-			# os.walk found nothing
-			pass
-		else:
-			dirnames.sort()
-			dirnames = bookmarks + dirnames
-
-			# no results, return None
-			if len(dirnames) == 0:
-				return
-
-			# one result. since it must be a directory, append a slash.
-			if len(dirnames) == 1:
-				return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
-
-			# more than one result. append no slash, so the user can
-			# manually type in the slash to advance into that directory
-			return (self.start(1) + join(rel_dirname, dirname) for dirname in dirnames)
+    """
+    :cd [-r] <dirname>
+
+    The cd command changes the directory.
+    The command 'cd -' is equivalent to typing ``.
+    Using the option "-r" will get you to the real path.
+    """
+
+    def execute(self):
+        import os.path
+        if self.arg(1) == '-r':
+            self.shift()
+            destination = os.path.realpath(self.rest(1))
+            if os.path.isfile(destination):
+                destination = os.path.dirname(destination)
+        else:
+            destination = self.rest(1)
+
+        if not destination:
+            destination = '~'
+
+        if destination == '-':
+            self.fm.enter_bookmark('`')
+        else:
+            self.fm.cd(destination)
+
+    def tab(self):
+        import os
+        from os.path import dirname, basename, expanduser, join
+
+        cwd = self.fm.thisdir.path
+        rel_dest = self.rest(1)
+
+        bookmarks = [v.path for v in self.fm.bookmarks.dct.values()
+                if rel_dest in v.path ]
+
+        # expand the tilde into the user directory
+        if rel_dest.startswith('~'):
+            rel_dest = expanduser(rel_dest)
+
+        # define some shortcuts
+        abs_dest = join(cwd, rel_dest)
+        abs_dirname = dirname(abs_dest)
+        rel_basename = basename(rel_dest)
+        rel_dirname = dirname(rel_dest)
+
+        try:
+            # are we at the end of a directory?
+            if rel_dest.endswith('/') or rel_dest == '':
+                _, dirnames, _ = next(os.walk(abs_dest))
+
+            # are we in the middle of the filename?
+            else:
+                _, dirnames, _ = next(os.walk(abs_dirname))
+                dirnames = [dn for dn in dirnames \
+                        if dn.startswith(rel_basename)]
+        except (OSError, StopIteration):
+            # os.walk found nothing
+            pass
+        else:
+            dirnames.sort()
+            dirnames = bookmarks + dirnames
+
+            # no results, return None
+            if len(dirnames) == 0:
+                return
+
+            # one result. since it must be a directory, append a slash.
+            if len(dirnames) == 1:
+                return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
+
+            # more than one result. append no slash, so the user can
+            # manually type in the slash to advance into that directory
+            return (self.start(1) + join(rel_dirname, dirname) for dirname in dirnames)
 
 
 class chain(Command):
-	"""
-	:chain <command1>; <command2>; ...
-	Calls multiple commands at once, separated by semicolons.
-	"""
-	def execute(self):
-		for command in self.rest(1).split(";"):
-			self.fm.execute_console(command)
+    """
+    :chain <command1>; <command2>; ...
+    Calls multiple commands at once, separated by semicolons.
+    """
+    def execute(self):
+        for command in self.rest(1).split(";"):
+            self.fm.execute_console(command)
 
 
 class search(Command):
-	def execute(self):
-		self.fm.search_file(self.rest(1), regexp=True)
+    def execute(self):
+        self.fm.search_file(self.rest(1), regexp=True)
 
 
 class search_inc(Command):
-	def quick(self):
-		self.fm.search_file(self.rest(1), regexp=True, offset=0)
+    def quick(self):
+        self.fm.search_file(self.rest(1), regexp=True, offset=0)
 
 
 class shell(Command):
-	escape_macros_for_shell = True
-
-	def execute(self):
-		if self.arg(1) and self.arg(1)[0] == '-':
-			flags = self.arg(1)[1:]
-			command = self.rest(2)
-		else:
-			flags = ''
-			command = self.rest(1)
-
-		if not command and 'p' in flags:
-			command = 'cat %f'
-		if command:
-			if '%' in command:
-				command = self.fm.substitute_macros(command)
-			self.fm.execute_command(command, flags=flags)
-
-	def tab(self):
-		from ranger.ext.get_executables import get_executables
-		if self.arg(1) and self.arg(1)[0] == '-':
-			command = self.rest(2)
-		else:
-			command = self.rest(1)
-		start = self.line[0:len(self.line) - len(command)]
-
-		try:
-			position_of_last_space = command.rindex(" ")
-		except ValueError:
-			return (start + program + ' ' for program \
-					in get_executables() if program.startswith(command))
-		if position_of_last_space == len(command) - 1:
-			selection = self.fm.thistab.get_selection()
-			if len(selection) == 1:
-				return self.line + selection[0].shell_escaped_basename + ' '
-			else:
-				return self.line + '%s '
-		else:
-			before_word, start_of_word = self.line.rsplit(' ', 1)
-			return (before_word + ' ' + file.shell_escaped_basename \
-					for file in self.fm.thisdir.files \
-					if file.shell_escaped_basename.startswith(start_of_word))
+    escape_macros_for_shell = True
+
+    def execute(self):
+        if self.arg(1) and self.arg(1)[0] == '-':
+            flags = self.arg(1)[1:]
+            command = self.rest(2)
+        else:
+            flags = ''
+            command = self.rest(1)
+
+        if not command and 'p' in flags:
+            command = 'cat %f'
+        if command:
+            if '%' in command:
+                command = self.fm.substitute_macros(command)
+            self.fm.execute_command(command, flags=flags)
+
+    def tab(self):
+        from ranger.ext.get_executables import get_executables
+        if self.arg(1) and self.arg(1)[0] == '-':
+            command = self.rest(2)
+        else:
+            command = self.rest(1)
+        start = self.line[0:len(self.line) - len(command)]
+
+        try:
+            position_of_last_space = command.rindex(" ")
+        except ValueError:
+            return (start + program + ' ' for program \
+                    in get_executables() if program.startswith(command))
+        if position_of_last_space == len(command) - 1:
+            selection = self.fm.thistab.get_selection()
+            if len(selection) == 1:
+                return self.line + selection[0].shell_escaped_basename + ' '
+            else:
+                return self.line + '%s '
+        else:
+            before_word, start_of_word = self.line.rsplit(' ', 1)
+            return (before_word + ' ' + file.shell_escaped_basename \
+                    for file in self.fm.thisdir.files \
+                    if file.shell_escaped_basename.startswith(start_of_word))
 
 class open_with(Command):
-	def execute(self):
-		app, flags, mode = self._get_app_flags_mode(self.rest(1))
-		self.fm.execute_file(
-				files = [f for f in self.fm.thistab.get_selection()],
-				app = app,
-				flags = flags,
-				mode = mode)
-
-	def tab(self):
-		return self._tab_through_executables()
-
-	def _get_app_flags_mode(self, string):
-		"""
-		Extracts the application, flags and mode from a string.
-
-		examples:
-		"mplayer f 1" => ("mplayer", "f", 1)
-		"aunpack 4" => ("aunpack", "", 4)
-		"p" => ("", "p", 0)
-		"" => None
-		"""
-
-		app = ''
-		flags = ''
-		mode = 0
-		split = string.split()
-
-		if len(split) == 0:
-			pass
-
-		elif len(split) == 1:
-			part = split[0]
-			if self._is_app(part):
-				app = part
-			elif self._is_flags(part):
-				flags = part
-			elif self._is_mode(part):
-				mode = part
-
-		elif len(split) == 2:
-			part0 = split[0]
-			part1 = split[1]
-
-			if self._is_app(part0):
-				app = part0
-				if self._is_flags(part1):
-					flags = part1
-				elif self._is_mode(part1):
-					mode = part1
-			elif self._is_flags(part0):
-				flags = part0
-				if self._is_mode(part1):
-					mode = part1
-			elif self._is_mode(part0):
-				mode = part0
-				if self._is_flags(part1):
-					flags = part1
-
-		elif len(split) >= 3:
-			part0 = split[0]
-			part1 = split[1]
-			part2 = split[2]
-
-			if self._is_app(part0):
-				app = part0
-				if self._is_flags(part1):
-					flags = part1
-					if self._is_mode(part2):
-						mode = part2
-				elif self._is_mode(part1):
-					mode = part1
-					if self._is_flags(part2):
-						flags = part2
-			elif self._is_flags(part0):
-				flags = part0
-				if self._is_mode(part1):
-					mode = part1
-			elif self._is_mode(part0):
-				mode = part0
-				if self._is_flags(part1):
-					flags = part1
-
-		return app, flags, int(mode)
-
-	def _is_app(self, arg):
-		return not self._is_flags(arg) and not arg.isdigit()
-
-	def _is_flags(self, arg):
-		from ranger.core.runner import ALLOWED_FLAGS
-		return all(x in ALLOWED_FLAGS for x in arg)
-
-	def _is_mode(self, arg):
-		return all(x in '0123456789' for x in arg)
+    def execute(self):
+        app, flags, mode = self._get_app_flags_mode(self.rest(1))
+        self.fm.execute_file(
+                files = [f for f in self.fm.thistab.get_selection()],
+                app = app,
+                flags = flags,
+                mode = mode)
+
+    def tab(self):
+        return self._tab_through_executables()
+
+    def _get_app_flags_mode(self, string):
+        """
+        Extracts the application, flags and mode from a string.
+
+        examples:
+        "mplayer f 1" => ("mplayer", "f", 1)
+        "aunpack 4" => ("aunpack", "", 4)
+        "p" => ("", "p", 0)
+        "" => None
+        """
+
+        app = ''
+        flags = ''
+        mode = 0
+        split = string.split()
+
+        if len(split) == 0:
+            pass
+
+        elif len(split) == 1:
+            part = split[0]
+            if self._is_app(part):
+                app = part
+            elif self._is_flags(part):
+                flags = part
+            elif self._is_mode(part):
+                mode = part
+
+        elif len(split) == 2:
+            part0 = split[0]
+            part1 = split[1]
+
+            if self._is_app(part0):
+                app = part0
+                if self._is_flags(part1):
+                    flags = part1
+                elif self._is_mode(part1):
+                    mode = part1
+            elif self._is_flags(part0):
+                flags = part0
+                if self._is_mode(part1):
+                    mode = part1
+            elif self._is_mode(part0):
+                mode = part0
+                if self._is_flags(part1):
+                    flags = part1
+
+        elif len(split) >= 3:
+            part0 = split[0]
+            part1 = split[1]
+            part2 = split[2]
+
+            if self._is_app(part0):
+                app = part0
+                if self._is_flags(part1):
+                    flags = part1
+                    if self._is_mode(part2):
+                        mode = part2
+                elif self._is_mode(part1):
+                    mode = part1
+                    if self._is_flags(part2):
+                        flags = part2
+            elif self._is_flags(part0):
+                flags = part0
+                if self._is_mode(part1):
+                    mode = part1
+            elif self._is_mode(part0):
+                mode = part0
+                if self._is_flags(part1):
+                    flags = part1
+
+        return app, flags, int(mode)
+
+    def _is_app(self, arg):
+        return not self._is_flags(arg) and not arg.isdigit()
+
+    def _is_flags(self, arg):
+        from ranger.core.runner import ALLOWED_FLAGS
+        return all(x in ALLOWED_FLAGS for x in arg)
+
+    def _is_mode(self, arg):
+        return all(x in '0123456789' for x in arg)
 
 
 class find(Command):
-	"""
-	:find <string>
-
-	The find command will attempt to find a partial, case insensitive
-	match in the filenames of the current directory and execute the
-	file automatically.
-	"""
-
-	count = 0
-	tab = Command._tab_directory_content
-
-	def execute(self):
-		if self.quick():
-			self.fm.move(right=1)
-			self.fm.block_input(0.5)
-		else:
-			self.fm.cd(self.rest(1))
-
-	def quick(self):
-		self.count = 0
-		cwd = self.fm.thisdir
-		arg = self.rest(1)
-		if not arg:
-			return False
-
-		if arg == '.':
-			return False
-		if arg == '..':
-			return True
-
-		deq = deque(cwd.files)
-		deq.rotate(-cwd.pointer)
-		i = 0
-		case_insensitive = arg.lower() == arg
-		for fsobj in deq:
-			if case_insensitive:
-				filename = fsobj.basename_lower
-			else:
-				filename = fsobj.basename
-			if arg in filename:
-				self.count += 1
-				if self.count == 1:
-					cwd.move(to=(cwd.pointer + i) % len(cwd.files))
-					self.fm.thisfile = cwd.pointed_obj
-			if self.count > 1:
-				return False
-			i += 1
-
-		return self.count == 1
+    """
+    :find <string>
+
+    The find command will attempt to find a partial, case insensitive
+    match in the filenames of the current directory and execute the
+    file automatically.
+    """
+
+    count = 0
+    tab = Command._tab_directory_content
+
+    def execute(self):
+        if self.quick():
+            self.fm.move(right=1)
+            self.fm.block_input(0.5)
+        else:
+            self.fm.cd(self.rest(1))
+
+    def quick(self):
+        self.count = 0
+        cwd = self.fm.thisdir
+        arg = self.rest(1)
+        if not arg:
+            return False
+
+        if arg == '.':
+            return False
+        if arg == '..':
+            return True
+
+        deq = deque(cwd.files)
+        deq.rotate(-cwd.pointer)
+        i = 0
+        case_insensitive = arg.lower() == arg
+        for fsobj in deq:
+            if case_insensitive:
+                filename = fsobj.basename_lower
+            else:
+                filename = fsobj.basename
+            if arg in filename:
+                self.count += 1
+                if self.count == 1:
+                    cwd.move(to=(cwd.pointer + i) % len(cwd.files))
+                    self.fm.thisfile = cwd.pointed_obj
+            if self.count > 1:
+                return False
+            i += 1
+
+        return self.count == 1
 
 
 class set_(Command):
-	"""
-	:set <option name>=<python expression>
-
-	Gives an option a new value.
-	"""
-	name = 'set'  # don't override the builtin set class
-	def execute(self):
-		name = self.arg(1)
-		name, value, _ = self.parse_setting_line()
-		self.fm.set_option_from_string(name, value)
-
-	def tab(self):
-		name, value, name_done = self.parse_setting_line()
-		settings = self.fm.settings
-		if not name:
-			return sorted(self.firstpart + setting for setting in settings)
-		if not value and not name_done:
-			return (self.firstpart + setting for setting in settings \
-					if setting.startswith(name))
-		if not value:
-			return self.firstpart + str(settings[name])
-		if bool in settings.types_of(name):
-			if 'true'.startswith(value.lower()):
-				return self.firstpart + 'True'
-			if 'false'.startswith(value.lower()):
-				return self.firstpart + 'False'
+    """
+    :set <option name>=<python expression>
+
+    Gives an option a new value.
+    """
+    name = 'set'  # don't override the builtin set class
+    def execute(self):
+        name = self.arg(1)
+        name, value, _ = self.parse_setting_line()
+        self.fm.set_option_from_string(name, value)
+
+    def tab(self):
+        name, value, name_done = self.parse_setting_line()
+        settings = self.fm.settings
+        if not name:
+            return sorted(self.firstpart + setting for setting in settings)
+        if not value and not name_done:
+            return (self.firstpart + setting for setting in settings \
+                    if setting.startswith(name))
+        if not value:
+            return self.firstpart + str(settings[name])
+        if bool in settings.types_of(name):
+            if 'true'.startswith(value.lower()):
+                return self.firstpart + 'True'
+            if 'false'.startswith(value.lower()):
+                return self.firstpart + 'False'
 
 
 class setlocal(set_):
-	"""
-	:setlocal path=<python string> <option name>=<python expression>
-
-	Gives an option a new value.
-	"""
-	PATH_RE = re.compile(r'^\s*path="?(.*?)"?\s*$')
-	def execute(self):
-		import os.path
-		match = self.PATH_RE.match(self.arg(1))
-		if match:
-			path = os.path.normpath(os.path.expanduser(match.group(1)))
-			self.shift()
-		elif self.fm.thisdir:
-			path = self.fm.thisdir.path
-		else:
-			path = None
-
-		if path:
-			name = self.arg(1)
-			name, value, _ = self.parse_setting_line()
-			self.fm.set_option_from_string(name, value, localpath=path)
+    """
+    :setlocal path=<python string> <option name>=<python expression>
+
+    Gives an option a new value.
+    """
+    PATH_RE = re.compile(r'^\s*path="?(.*?)"?\s*$')
+    def execute(self):
+        import os.path
+        match = self.PATH_RE.match(self.arg(1))
+        if match:
+            path = os.path.normpath(os.path.expanduser(match.group(1)))
+            self.shift()
+        elif self.fm.thisdir:
+            path = self.fm.thisdir.path
+        else:
+            path = None
+
+        if path:
+            name = self.arg(1)
+            name, value, _ = self.parse_setting_line()
+            self.fm.set_option_from_string(name, value, localpath=path)
 
 
 class quit(Command):
-	"""
-	:quit
+    """
+    :quit
 
-	Closes the current tab.  If there is only one tab, quit the program.
-	"""
+    Closes the current tab.  If there is only one tab, quit the program.
+    """
 
-	def execute(self):
-		if len(self.fm.tabs) <= 1:
-			self.fm.exit()
-		self.fm.tab_close()
+    def execute(self):
+        if len(self.fm.tabs) <= 1:
+            self.fm.exit()
+        self.fm.tab_close()
 
 
 class quitall(Command):
-	"""
-	:quitall
+    """
+    :quitall
 
-	Quits the program immediately.
-	"""
+    Quits the program immediately.
+    """
 
-	def execute(self):
-		self.fm.exit()
+    def execute(self):
+        self.fm.exit()
 
 
 class quit_bang(quitall):
-	"""
-	:quit!
+    """
+    :quit!
 
-	Quits the program immediately.
-	"""
-	name = 'quit!'
-	allow_abbrev = False
+    Quits the program immediately.
+    """
+    name = 'quit!'
+    allow_abbrev = False
 
 
 class terminal(Command):
-	"""
-	:terminal
-
-	Spawns an "x-terminal-emulator" starting in the current directory.
-	"""
-	def execute(self):
-		import os
-		from ranger.ext.get_executables import get_executables
-		command = os.environ.get('TERMCMD', os.environ.get('TERM'))
-		if command not in get_executables():
-			command = 'x-terminal-emulator'
-		if command not in get_executables():
-			command = 'xterm'
-		self.fm.run(command, flags='f')
+    """
+    :terminal
+
+    Spawns an "x-terminal-emulator" starting in the current directory.
+    """
+    def execute(self):
+        import os
+        from ranger.ext.get_executables import get_executables
+        command = os.environ.get('TERMCMD', os.environ.get('TERM'))
+        if command not in get_executables():
+            command = 'x-terminal-emulator'
+        if command not in get_executables():
+            command = 'xterm'
+        self.fm.run(command, flags='f')
 
 
 class delete(Command):
-	"""
-	:delete
+    """
+    :delete
 
-	Tries to delete the selection.
+    Tries to delete the selection.
 
-	"Selection" is defined as all the "marked files" (by default, you
-	can mark files with space or v). If there are no marked files,
-	use the "current file" (where the cursor is)
+    "Selection" is defined as all the "marked files" (by default, you
+    can mark files with space or v). If there are no marked files,
+    use the "current file" (where the cursor is)
 
-	When attempting to delete non-empty directories or multiple
-	marked files, it will require a confirmation.
-	"""
+    When attempting to delete non-empty directories or multiple
+    marked files, it will require a confirmation.
+    """
 
-	allow_abbrev = False
+    allow_abbrev = False
 
-	def execute(self):
-		import os
-		if self.rest(1):
-			self.fm.notify("Error: delete takes no arguments! It deletes "
-					"the selected file(s).", bad=True)
-			return
+    def execute(self):
+        import os
+        if self.rest(1):
+            self.fm.notify("Error: delete takes no arguments! It deletes "
+                    "the selected file(s).", bad=True)
+            return
 
-		cwd = self.fm.thisdir
-		cf = self.fm.thisfile
-		if not cwd or not cf:
-			self.fm.notify("Error: no file selected for deletion!", bad=True)
-			return
+        cwd = self.fm.thisdir
+        cf = self.fm.thisfile
+        if not cwd or not cf:
+            self.fm.notify("Error: no file selected for deletion!", bad=True)
+            return
 
-		confirm = self.fm.settings.confirm_on_delete
-		many_files = (cwd.marked_items or (cf.is_directory and not cf.is_link \
-				and len(os.listdir(cf.path)) > 0))
+        confirm = self.fm.settings.confirm_on_delete
+        many_files = (cwd.marked_items or (cf.is_directory and not cf.is_link \
+                and len(os.listdir(cf.path)) > 0))
 
-		if confirm != 'never' and (confirm != 'multiple' or many_files):
-			self.fm.ui.console.ask("Confirm deletion of: %s (y/N)" %
-				', '.join(f.basename for f in self.fm.thistab.get_selection()),
-				self._question_callback, ('n', 'N', 'y', 'Y'))
-		else:
-			# no need for a confirmation, just delete
-			self.fm.delete()
+        if confirm != 'never' and (confirm != 'multiple' or many_files):
+            self.fm.ui.console.ask("Confirm deletion of: %s (y/N)" %
+                ', '.join(f.basename for f in self.fm.thistab.get_selection()),
+                self._question_callback, ('n', 'N', 'y', 'Y'))
+        else:
+            # no need for a confirmation, just delete
+            self.fm.delete()
 
-	def _question_callback(self, answer):
-		if answer == 'y' or answer == 'Y':
-			self.fm.delete()
+    def _question_callback(self, answer):
+        if answer == 'y' or answer == 'Y':
+            self.fm.delete()
 
 
 class mark(Command):
-	"""
-	:mark <regexp>
-
-	Mark all files matching a regular expression.
-	"""
-	do_mark = True
-
-	def execute(self):
-		import re
-		cwd = self.fm.thisdir
-		input = self.rest(1)
-		searchflags = re.UNICODE
-		if input.lower() == input: # "smartcase"
-			searchflags |= re.IGNORECASE
-		pattern = re.compile(input, searchflags)
-		for fileobj in cwd.files:
-			if pattern.search(fileobj.basename):
-				cwd.mark_item(fileobj, val=self.do_mark)
-		self.fm.ui.status.need_redraw = True
-		self.fm.ui.need_redraw = True
+    """
+    :mark <regexp>
+
+    Mark all files matching a regular expression.
+    """
+    do_mark = True
+
+    def execute(self):
+        import re
+        cwd = self.fm.thisdir
+        input = self.rest(1)
+        searchflags = re.UNICODE
+        if input.lower() == input: # "smartcase"
+            searchflags |= re.IGNORECASE
+        pattern = re.compile(input, searchflags)
+        for fileobj in cwd.files:
+            if pattern.search(fileobj.basename):
+                cwd.mark_item(fileobj, val=self.do_mark)
+        self.fm.ui.status.need_redraw = True
+        self.fm.ui.need_redraw = True
 
 
 class mark_tag(Command):
-	"""
-	:mark_tag [<tags>]
-
-	Mark all tags that are tagged with either of the given tags.
-	When leaving out the tag argument, all tagged files are marked.
-	"""
-	do_mark = True
-
-	def execute(self):
-		cwd = self.fm.thisdir
-		tags = self.rest(1).replace(" ","")
-		if not self.fm.tags:
-			return
-		for fileobj in cwd.files:
-			try:
-				tag = self.fm.tags.tags[fileobj.realpath]
-			except KeyError:
-				continue
-			if not tags or tag in tags:
-				cwd.mark_item(fileobj, val=self.do_mark)
-		self.fm.ui.status.need_redraw = True
-		self.fm.ui.need_redraw = True
+    """
+    :mark_tag [<tags>]
+
+    Mark all tags that are tagged with either of the given tags.
+    When leaving out the tag argument, all tagged files are marked.
+    """
+    do_mark = True
+
+    def execute(self):
+        cwd = self.fm.thisdir
+        tags = self.rest(1).replace(" ","")
+        if not self.fm.tags:
+            return
+        for fileobj in cwd.files:
+            try:
+                tag = self.fm.tags.tags[fileobj.realpath]
+            except KeyError:
+                continue
+            if not tags or tag in tags:
+                cwd.mark_item(fileobj, val=self.do_mark)
+        self.fm.ui.status.need_redraw = True
+        self.fm.ui.need_redraw = True
 
 
 class console(Command):
-	"""
-	:console <command>
-
-	Open the console with the given command.
-	"""
-	def execute(self):
-		position = None
-		if self.arg(1)[0:2] == '-p':
-			try:
-				position = int(self.arg(1)[2:])
-				self.shift()
-			except:
-				pass
-		self.fm.open_console(self.rest(1), position=position)
+    """
+    :console <command>
+
+    Open the console with the given command.
+    """
+    def execute(self):
+        position = None
+        if self.arg(1)[0:2] == '-p':
+            try:
+                position = int(self.arg(1)[2:])
+                self.shift()
+            except:
+                pass
+        self.fm.open_console(self.rest(1), position=position)
 
 
 class load_copy_buffer(Command):
-	"""
-	:load_copy_buffer
-
-	Load the copy buffer from confdir/copy_buffer
-	"""
-	copy_buffer_filename = 'copy_buffer'
-	def execute(self):
-		from ranger.fsobject import File
-		from os.path import exists
-		try:
-			fname = self.fm.confpath(self.copy_buffer_filename)
-			f = open(fname, 'r')
-		except:
-			return self.fm.notify("Cannot open %s" % \
-					(fname or self.copy_buffer_filename), bad=True)
-		self.fm.copy_buffer = set(File(g) \
-			for g in f.read().split("\n") if exists(g))
-		f.close()
-		self.fm.ui.redraw_main_column()
+    """
+    :load_copy_buffer
+
+    Load the copy buffer from confdir/copy_buffer
+    """
+    copy_buffer_filename = 'copy_buffer'
+    def execute(self):
+        from ranger.fsobject import File
+        from os.path import exists
+        try:
+            fname = self.fm.confpath(self.copy_buffer_filename)
+            f = open(fname, 'r')
+        except:
+            return self.fm.notify("Cannot open %s" % \
+                    (fname or self.copy_buffer_filename), bad=True)
+        self.fm.copy_buffer = set(File(g) \
+            for g in f.read().split("\n") if exists(g))
+        f.close()
+        self.fm.ui.redraw_main_column()
 
 
 class save_copy_buffer(Command):
-	"""
-	:save_copy_buffer
-
-	Save the copy buffer to confdir/copy_buffer
-	"""
-	copy_buffer_filename = 'copy_buffer'
-	def execute(self):
-		fname = None
-		try:
-			fname = self.fm.confpath(self.copy_buffer_filename)
-			f = open(fname, 'w')
-		except:
-			return self.fm.notify("Cannot open %s" % \
-					(fname or self.copy_buffer_filename), bad=True)
-		f.write("\n".join(f.path for f in self.fm.copy_buffer))
-		f.close()
+    """
+    :save_copy_buffer
+
+    Save the copy buffer to confdir/copy_buffer
+    """
+    copy_buffer_filename = 'copy_buffer'
+    def execute(self):
+        fname = None
+        try:
+            fname = self.fm.confpath(self.copy_buffer_filename)
+            f = open(fname, 'w')
+        except:
+            return self.fm.notify("Cannot open %s" % \
+                    (fname or self.copy_buffer_filename), bad=True)
+        f.write("\n".join(f.path for f in self.fm.copy_buffer))
+        f.close()
 
 
 class unmark(mark):
-	"""
-	:unmark <regexp>
+    """
+    :unmark <regexp>
 
-	Unmark all files matching a regular expression.
-	"""
-	do_mark = False
+    Unmark all files matching a regular expression.
+    """
+    do_mark = False
 
 
 class unmark_tag(mark_tag):
-	"""
-	:unmark_tag [<tags>]
+    """
+    :unmark_tag [<tags>]
 
-	Unmark all tags that are tagged with either of the given tags.
-	When leaving out the tag argument, all tagged files are unmarked.
-	"""
-	do_mark = False
+    Unmark all tags that are tagged with either of the given tags.
+    When leaving out the tag argument, all tagged files are unmarked.
+    """
+    do_mark = False
 
 
 class mkdir(Command):
-	"""
-	:mkdir <dirname>
+    """
+    :mkdir <dirname>
 
-	Creates a directory with the name <dirname>.
-	"""
+    Creates a directory with the name <dirname>.
+    """
 
-	def execute(self):
-		from os.path import join, expanduser, lexists
-		from os import mkdir
+    def execute(self):
+        from os.path import join, expanduser, lexists
+        from os import mkdir
 
-		dirname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
-		if not lexists(dirname):
-			mkdir(dirname)
-		else:
-			self.fm.notify("file/directory exists!", bad=True)
+        dirname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
+        if not lexists(dirname):
+            mkdir(dirname)
+        else:
+            self.fm.notify("file/directory exists!", bad=True)
 
-	def tab(self):
-		return self._tab_directory_content()
+    def tab(self):
+        return self._tab_directory_content()
 
 
 class touch(Command):
-	"""
-	:touch <fname>
+    """
+    :touch <fname>
 
-	Creates a file with the name <fname>.
-	"""
+    Creates a file with the name <fname>.
+    """
 
-	def execute(self):
-		from os.path import join, expanduser, lexists
+    def execute(self):
+        from os.path import join, expanduser, lexists
 
-		fname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
-		if not lexists(fname):
-			open(fname, 'a').close()
-		else:
-			self.fm.notify("file/directory exists!", bad=True)
+        fname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
+        if not lexists(fname):
+            open(fname, 'a').close()
+        else:
+            self.fm.notify("file/directory exists!", bad=True)
 
-	def tab(self):
-		return self._tab_directory_content()
+    def tab(self):
+        return self._tab_directory_content()
 
 
 class edit(Command):
-	"""
-	:edit <filename>
+    """
+    :edit <filename>
 
-	Opens the specified file in vim
-	"""
+    Opens the specified file in vim
+    """
 
-	def execute(self):
-		if not self.arg(1):
-			self.fm.edit_file(self.fm.thisfile.path)
-		else:
-			self.fm.edit_file(self.rest(1))
+    def execute(self):
+        if not self.arg(1):
+            self.fm.edit_file(self.fm.thisfile.path)
+        else:
+            self.fm.edit_file(self.rest(1))
 
-	def tab(self):
-		return self._tab_directory_content()
+    def tab(self):
+        return self._tab_directory_content()
 
 
 class eval_(Command):
-	"""
-	:eval [-q] <python code>
-
-	Evaluates the python code.
-	`fm' is a reference to the FM instance.
-	To display text, use the function `p'.
-
-	Examples:
-	:eval fm
-	:eval len(fm.directories)
-	:eval p("Hello World!")
-	"""
-	name = 'eval'
-	resolve_macros = False
-
-	def execute(self):
-		if self.arg(1) == '-q':
-			code = self.rest(2)
-			quiet = True
-		else:
-			code = self.rest(1)
-			quiet = False
-		import ranger
-		global cmd, fm, p, quantifier
-		fm = self.fm
-		cmd = self.fm.execute_console
-		p = fm.notify
-		quantifier = self.quantifier
-		try:
-			try:
-				result = eval(code)
-			except SyntaxError:
-				exec(code)
-			else:
-				if result and not quiet:
-					p(result)
-		except Exception as err:
-			p(err)
+    """
+    :eval [-q] <python code>
+
+    Evaluates the python code.
+    `fm' is a reference to the FM instance.
+    To display text, use the function `p'.
+
+    Examples:
+    :eval fm
+    :eval len(fm.directories)
+    :eval p("Hello World!")
+    """
+    name = 'eval'
+    resolve_macros = False
+
+    def execute(self):
+        if self.arg(1) == '-q':
+            code = self.rest(2)
+            quiet = True
+        else:
+            code = self.rest(1)
+            quiet = False
+        import ranger
+        global cmd, fm, p, quantifier
+        fm = self.fm
+        cmd = self.fm.execute_console
+        p = fm.notify
+        quantifier = self.quantifier
+        try:
+            try:
+                result = eval(code)
+            except SyntaxError:
+                exec(code)
+            else:
+                if result and not quiet:
+                    p(result)
+        except Exception as err:
+            p(err)
 
 
 class rename(Command):
-	"""
-	:rename <newname>
+    """
+    :rename <newname>
 
-	Changes the name of the currently highlighted file to <newname>
-	"""
+    Changes the name of the currently highlighted file to <newname>
+    """
 
-	def execute(self):
-		from ranger.fsobject import File
-		from os import access
+    def execute(self):
+        from ranger.fsobject import File
+        from os import access
 
-		new_name = self.rest(1)
+        new_name = self.rest(1)
 
-		if not new_name:
-			return self.fm.notify('Syntax: rename <newname>', bad=True)
+        if not new_name:
+            return self.fm.notify('Syntax: rename <newname>', bad=True)
 
-		if new_name == self.fm.thisfile.basename:
-			return
+        if new_name == self.fm.thisfile.basename:
+            return
 
-		if access(new_name, os.F_OK):
-			return self.fm.notify("Can't rename: file already exists!", bad=True)
+        if access(new_name, os.F_OK):
+            return self.fm.notify("Can't rename: file already exists!", bad=True)
 
-		self.fm.rename(self.fm.thisfile, new_name)
-		f = File(new_name)
-		self.fm.thisdir.pointed_obj = f
-		self.fm.thisfile = f
+        self.fm.rename(self.fm.thisfile, new_name)
+        f = File(new_name)
+        self.fm.thisdir.pointed_obj = f
+        self.fm.thisfile = f
 
-	def tab(self):
-		return self._tab_directory_content()
+    def tab(self):
+        return self._tab_directory_content()
 
 
 class chmod(Command):
-	"""
-	:chmod <octal number>
+    """
+    :chmod <octal number>
 
-	Sets the permissions of the selection to the octal number.
+    Sets the permissions of the selection to the octal number.
 
-	The octal number is between 0 and 777. The digits specify the
-	permissions for the user, the group and others.
+    The octal number is between 0 and 777. The digits specify the
+    permissions for the user, the group and others.
 
-	A 1 permits execution, a 2 permits writing, a 4 permits reading.
-	Add those numbers to combine them. So a 7 permits everything.
-	"""
+    A 1 permits execution, a 2 permits writing, a 4 permits reading.
+    Add those numbers to combine them. So a 7 permits everything.
+    """
 
-	def execute(self):
-		mode = self.rest(1)
-		if not mode:
-			mode = str(self.quantifier)
+    def execute(self):
+        mode = self.rest(1)
+        if not mode:
+            mode = str(self.quantifier)
 
-		try:
-			mode = int(mode, 8)
-			if mode < 0 or mode > 0o777:
-				raise ValueError
-		except ValueError:
-			self.fm.notify("Need an octal number between 0 and 777!", bad=True)
-			return
+        try:
+            mode = int(mode, 8)
+            if mode < 0 or mode > 0o777:
+                raise ValueError
+        except ValueError:
+            self.fm.notify("Need an octal number between 0 and 777!", bad=True)
+            return
 
-		for file in self.fm.thistab.get_selection():
-			try:
-				os.chmod(file.path, mode)
-			except Exception as ex:
-				self.fm.notify(ex)
+        for file in self.fm.thistab.get_selection():
+            try:
+                os.chmod(file.path, mode)
+            except Exception as ex:
+                self.fm.notify(ex)
 
-		try:
-			# reloading directory.  maybe its better to reload the selected
-			# files only.
-			self.fm.thisdir.load_content()
-		except:
-			pass
+        try:
+            # reloading directory.  maybe its better to reload the selected
+            # files only.
+            self.fm.thisdir.load_content()
+        except:
+            pass
 
 
 class bulkrename(Command):
-	"""
-	:bulkrename
-
-	This command opens a list of selected files in an external editor.
-	After you edit and save the file, it will generate a shell script
-	which does bulk renaming according to the changes you did in the file.
-
-	This shell script is opened in an editor for you to review.
-	After you close it, it will be executed.
-	"""
-	def execute(self):
-		import sys
-		import tempfile
-		from ranger.fsobject.file import File
-		from ranger.ext.shell_escape import shell_escape as esc
-		py3 = sys.version > "3"
-
-		# Create and edit the file list
-		filenames = [f.basename for f in self.fm.thistab.get_selection()]
-		listfile = tempfile.NamedTemporaryFile()
-
-		if py3:
-			listfile.write("\n".join(filenames).encode("utf-8"))
-		else:
-			listfile.write("\n".join(filenames))
-		listfile.flush()
-		self.fm.execute_file([File(listfile.name)], app='editor')
-		listfile.seek(0)
-		if py3:
-			new_filenames = listfile.read().decode("utf-8").split("\n")
-		else:
-			new_filenames = listfile.read().split("\n")
-		listfile.close()
-		if all(a == b for a, b in zip(filenames, new_filenames)):
-			self.fm.notify("No renaming to be done!")
-			return
-
-		# Generate and execute script
-		cmdfile = tempfile.NamedTemporaryFile()
-		cmdfile.write(b"# This file will be executed when you close the editor.\n")
-		cmdfile.write(b"# Please double-check everything, clear the file to abort.\n")
-		if py3:
-			cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
-				for old, new in zip(filenames, new_filenames) \
-				if old != new).encode("utf-8"))
-		else:
-			cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
-				for old, new in zip(filenames, new_filenames) if old != new))
-		cmdfile.flush()
-		self.fm.execute_file([File(cmdfile.name)], app='editor')
-		self.fm.run(['/bin/sh', cmdfile.name], flags='w')
-		cmdfile.close()
+    """
+    :bulkrename
+
+    This command opens a list of selected files in an external editor.
+    After you edit and save the file, it will generate a shell script
+    which does bulk renaming according to the changes you did in the file.
+
+    This shell script is opened in an editor for you to review.
+    After you close it, it will be executed.
+    """
+    def execute(self):
+        import sys
+        import tempfile
+        from ranger.fsobject.file import File
+        from ranger.ext.shell_escape import shell_escape as esc
+        py3 = sys.version > "3"
+
+        # Create and edit the file list
+        filenames = [f.basename for f in self.fm.thistab.get_selection()]
+        listfile = tempfile.NamedTemporaryFile()
+
+        if py3:
+            listfile.write("\n".join(filenames).encode("utf-8"))
+        else:
+            listfile.write("\n".join(filenames))
+        listfile.flush()
+        self.fm.execute_file([File(listfile.name)], app='editor')
+        listfile.seek(0)
+        if py3:
+            new_filenames = listfile.read().decode("utf-8").split("\n")
+        else:
+            new_filenames = listfile.read().split("\n")
+        listfile.close()
+        if all(a == b for a, b in zip(filenames, new_filenames)):
+            self.fm.notify("No renaming to be done!")
+            return
+
+        # Generate and execute script
+        cmdfile = tempfile.NamedTemporaryFile()
+        cmdfile.write(b"# This file will be executed when you close the editor.\n")
+        cmdfile.write(b"# Please double-check everything, clear the file to abort.\n")
+        if py3:
+            cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
+                for old, new in zip(filenames, new_filenames) \
+                if old != new).encode("utf-8"))
+        else:
+            cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
+                for old, new in zip(filenames, new_filenames) if old != new))
+        cmdfile.flush()
+        self.fm.execute_file([File(cmdfile.name)], app='editor')
+        self.fm.run(['/bin/sh', cmdfile.name], flags='w')
+        cmdfile.close()
 
 
 class relink(Command):
-	"""
-	:relink <newpath>
+    """
+    :relink <newpath>
 
-	Changes the linked path of the currently highlighted symlink to <newpath>
-	"""
+    Changes the linked path of the currently highlighted symlink to <newpath>
+    """
 
-	def execute(self):
-		from ranger.fsobject import File
+    def execute(self):
+        from ranger.fsobject import File
 
-		new_path = self.rest(1)
-		cf = self.fm.thisfile
+        new_path = self.rest(1)
+        cf = self.fm.thisfile
 
-		if not new_path:
-			return self.fm.notify('Syntax: relink <newpath>', bad=True)
+        if not new_path:
+            return self.fm.notify('Syntax: relink <newpath>', bad=True)
 
-		if not cf.is_link:
-			return self.fm.notify('%s is not a symlink!' % cf.basename, bad=True)
+        if not cf.is_link:
+            return self.fm.notify('%s is not a symlink!' % cf.basename, bad=True)
 
-		if new_path == os.readlink(cf.path):
-			return
+        if new_path == os.readlink(cf.path):
+            return
 
-		try:
-			os.remove(cf.path)
-			os.symlink(new_path, cf.path)
-		except OSError as err:
-			self.fm.notify(err)
+        try:
+            os.remove(cf.path)
+            os.symlink(new_path, cf.path)
+        except OSError as err:
+            self.fm.notify(err)
 
-		self.fm.reset()
-		self.fm.thisdir.pointed_obj = cf
-		self.fm.thisfile = cf
+        self.fm.reset()
+        self.fm.thisdir.pointed_obj = cf
+        self.fm.thisfile = cf
 
-	def tab(self):
-		if not self.rest(1):
-			return self.line+os.readlink(self.fm.thisfile.path)
-		else:
-			return self._tab_directory_content()
+    def tab(self):
+        if not self.rest(1):
+            return self.line+os.readlink(self.fm.thisfile.path)
+        else:
+            return self._tab_directory_content()
 
 
 class help_(Command):
-	"""
-	:help
-
-	Display ranger's manual page.
-	"""
-	name = 'help'
-	def execute(self):
-		if self.quantifier == 1:
-			self.fm.dump_keybindings()
-		elif self.quantifier == 2:
-			self.fm.dump_commands()
-		elif self.quantifier == 3:
-			self.fm.dump_settings()
-		else:
-			self.fm.display_help()
+    """
+    :help
+
+    Display ranger's manual page.
+    """
+    name = 'help'
+    def execute(self):
+        if self.quantifier == 1:
+            self.fm.dump_keybindings()
+        elif self.quantifier == 2:
+            self.fm.dump_commands()
+        elif self.quantifier == 3:
+            self.fm.dump_settings()
+        else:
+            self.fm.display_help()
 
 
 class copymap(Command):
-	"""
-	:copymap <keys> <newkeys1> [<newkeys2>...]
-	Copies a "browser" keybinding from <keys> to <newkeys>
-	"""
-	context = 'browser'
+    """
+    :copymap <keys> <newkeys1> [<newkeys2>...]
+    Copies a "browser" keybinding from <keys> to <newkeys>
+    """
+    context = 'browser'
 
-	def execute(self):
-		if not self.arg(1) or not self.arg(2):
-			return self.fm.notify("Not enough arguments", bad=True)
+    def execute(self):
+        if not self.arg(1) or not self.arg(2):
+            return self.fm.notify("Not enough arguments", bad=True)
 
-		for arg in self.args[2:]:
-			self.fm.ui.keymaps.copy(self.context, self.arg(1), arg)
+        for arg in self.args[2:]:
+            self.fm.ui.keymaps.copy(self.context, self.arg(1), arg)
 
 
 class copypmap(copymap):
-	"""
-	:copypmap <keys> <newkeys1> [<newkeys2>...]
-	Copies a "pager" keybinding from <keys> to <newkeys>
-	"""
-	context = 'pager'
+    """
+    :copypmap <keys> <newkeys1> [<newkeys2>...]
+    Copies a "pager" keybinding from <keys> to <newkeys>
+    """
+    context = 'pager'
 
 
 class copycmap(copymap):
-	"""
-	:copycmap <keys> <newkeys1> [<newkeys2>...]
-	Copies a "console" keybinding from <keys> to <newkeys>
-	"""
-	context = 'console'
+    """
+    :copycmap <keys> <newkeys1> [<newkeys2>...]
+    Copies a "console" keybinding from <keys> to <newkeys>
+    """
+    context = 'console'
 
 
 class copytmap(copymap):
-	"""
-	:copycmap <keys> <newkeys1> [<newkeys2>...]
-	Copies a "taskview" keybinding from <keys> to <newkeys>
-	"""
-	context = 'taskview'
+    """
+    :copycmap <keys> <newkeys1> [<newkeys2>...]
+    Copies a "taskview" keybinding from <keys> to <newkeys>
+    """
+    context = 'taskview'
 
 
 class unmap(Command):
-	"""
-	:unmap <keys> [<keys2>, ...]
-	Remove the given "browser" mappings
-	"""
-	context = 'browser'
+    """
+    :unmap <keys> [<keys2>, ...]
+    Remove the given "browser" mappings
+    """
+    context = 'browser'
 
-	def execute(self):
-		for arg in self.args[1:]:
-			self.fm.ui.keymaps.unbind(self.context, arg)
+    def execute(self):
+        for arg in self.args[1:]:
+            self.fm.ui.keymaps.unbind(self.context, arg)
 
 
 class cunmap(unmap):
-	"""
-	:cunmap <keys> [<keys2>, ...]
-	Remove the given "console" mappings
-	"""
-	context = 'browser'
+    """
+    :cunmap <keys> [<keys2>, ...]
+    Remove the given "console" mappings
+    """
+    context = 'browser'
 
 
 class punmap(unmap):
-	"""
-	:punmap <keys> [<keys2>, ...]
-	Remove the given "pager" mappings
-	"""
-	context = 'pager'
+    """
+    :punmap <keys> [<keys2>, ...]
+    Remove the given "pager" mappings
+    """
+    context = 'pager'
 
 
 class tunmap(unmap):
-	"""
-	:tunmap <keys> [<keys2>, ...]
-	Remove the given "taskview" mappings
-	"""
-	context = 'taskview'
+    """
+    :tunmap <keys> [<keys2>, ...]
+    Remove the given "taskview" mappings
+    """
+    context = 'taskview'
 
 
 class map_(Command):
-	"""
-	:map <keysequence> <command>
-	Maps a command to a keysequence in the "browser" context.
+    """
+    :map <keysequence> <command>
+    Maps a command to a keysequence in the "browser" context.
 
-	Example:
-	map j move down
-	map J move down 10
-	"""
-	name = 'map'
-	context = 'browser'
-	resolve_macros = False
+    Example:
+    map j move down
+    map J move down 10
+    """
+    name = 'map'
+    context = 'browser'
+    resolve_macros = False
 
-	def execute(self):
-		self.fm.ui.keymaps.bind(self.context, self.arg(1), self.rest(2))
+    def execute(self):
+        self.fm.ui.keymaps.bind(self.context, self.arg(1), self.rest(2))
 
 
 class cmap(map_):
-	""":cmap <keysequence> <command>
-	Maps a command to a keysequence in the "console" context.
+    """:cmap <keysequence> <command>
+    Maps a command to a keysequence in the "console" context.
 
-	Example:
-	cmap <ESC> console_close
-	cmap <C-x> console_type test
-	"""
-	context = 'console'
+    Example:
+    cmap <ESC> console_close
+    cmap <C-x> console_type test
+    """
+    context = 'console'
 
 
 class tmap(map_):
-	""":tmap <keysequence> <command>
-	Maps a command to a keysequence in the "taskview" context.
-	"""
-	context = 'taskview'
+    """:tmap <keysequence> <command>
+    Maps a command to a keysequence in the "taskview" context.
+    """
+    context = 'taskview'
 
 
 class pmap(map_):
-	""":pmap <keysequence> <command>
-	Maps a command to a keysequence in the "pager" context.
-	"""
-	context = 'pager'
+    """:pmap <keysequence> <command>
+    Maps a command to a keysequence in the "pager" context.
+    """
+    context = 'pager'
 
 
 class filter(Command):
-	"""
-	:filter <string>
+    """
+    :filter <string>
 
-	Displays only the files which contain <string> in their basename.
-	"""
+    Displays only the files which contain <string> in their basename.
+    """
 
-	def execute(self):
-		self.fm.set_filter(self.rest(1))
-		self.fm.reload_cwd()
+    def execute(self):
+        self.fm.set_filter(self.rest(1))
+        self.fm.reload_cwd()
 
 
 class grep(Command):
-	"""
-	:grep <string>
-
-	Looks for a string in all marked files or directories
-	"""
-
-	def execute(self):
-		if self.rest(1):
-			action = ['grep', '--color=always', '--line-number']
-			action.extend(['-e', self.rest(1), '-r'])
-			action.extend(f.path for f in self.fm.thistab.get_selection())
-			self.fm.execute_command(action, flags='p')
+    """
+    :grep <string>
+
+    Looks for a string in all marked files or directories
+    """
+
+    def execute(self):
+        if self.rest(1):
+            action = ['grep', '--color=always', '--line-number']
+            action.extend(['-e', self.rest(1), '-r'])
+            action.extend(f.path for f in self.fm.thistab.get_selection())
+            self.fm.execute_command(action, flags='p')
diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py
index 4d692989..bda7907b 100644
--- a/ranger/container/bookmarks.py
+++ b/ranger/container/bookmarks.py
@@ -7,193 +7,193 @@ import os
 ALLOWED_KEYS = string.ascii_letters + string.digits + "`'"
 
 class Bookmarks(object):
-	"""Bookmarks is a container which associates keys with bookmarks.
-
-		A key is a string with: len(key) == 1 and key in ALLOWED_KEYS.
-
-		A bookmark is an object with: bookmark == bookmarktype(str(instance))
-		Which is true for str or FileSystemObject. This condition is required
-		so bookmark-objects can be saved to and loaded from a file.
-
-		Optionally, a bookmark.go() method is used for entering a bookmark.
-		"""
-
-	last_mtime = None
-	autosave = True
-	load_pattern = re.compile(r"^[\d\w']:.")
-
-	def __init__(self, bookmarkfile, bookmarktype=str, autosave=False):
-		"""<bookmarkfile> specifies the path to the file where
-		bookmarks are saved in.
-		"""
-		self.autosave = autosave
-		self.dct = {}
-		self.path = bookmarkfile
-		self.bookmarktype = bookmarktype
-
-	def load(self):
-		"""Load the bookmarks from path/bookmarks"""
-		try:
-			new_dict = self._load_dict()
-		except OSError:
-			return
-
-		self._set_dict(new_dict, original=new_dict)
-
-	def delete(self, key):
-		"""Delete the bookmark with the given key"""
-		if key == '`':
-			key = "'"
-		if key in self.dct:
-			del self.dct[key]
-			if self.autosave: self.save()
-
-	def enter(self, key):
-		"""Enter the bookmark with the given key.
-		Requires the bookmark instance to have a go() method.
-		"""
-
-		try:
-			return self[key].go()
-		except (IndexError, KeyError, AttributeError):
-			return False
-
-	def update_if_outdated(self):
-		if self.last_mtime != self._get_mtime():
-			self.update()
-
-	def remember(self, value):
-		"""Bookmarks <value> to the key '"""
-		self["'"] = value
-		if self.autosave: self.save()
-
-	def __iter__(self):
-		return iter(self.dct.items())
-
-	def __getitem__(self, key):
-		"""Get the bookmark associated with the key"""
-		if key == '`':
-			key = "'"
-		if key in self.dct:
-			return self.dct[key]
-		else:
-			raise KeyError("Nonexistant Bookmark: `%s'!" % key)
-
-	def __setitem__(self, key, value):
-		"""Bookmark <value> to the key <key>.
-		key is expected to be a 1-character string and element of ALLOWED_KEYS.
-		value is expected to be a filesystemobject.
-		"""
-		if key == '`':
-			key = "'"
-		if key in ALLOWED_KEYS:
-			self.dct[key] = value
-			if self.autosave: self.save()
-
-	def __contains__(self, key):
-		"""Test whether a bookmark-key is defined"""
-		return key in self.dct
-
-	def update(self):
-		"""Update the bookmarks from the bookmark file.
-		Useful if two instances are running which define different bookmarks.
-		"""
-
-		try:
-			real_dict = self._load_dict()
-			real_dict_copy = real_dict.copy()
-		except OSError:
-			return
-
-		for key in set(self.dct.keys()) | set(real_dict.keys()):
-			# set some variables
-			if key in self.dct:
-				current = self.dct[key]
-			else:
-				current = None
-
-			if key in self.original_dict:
-				original = self.original_dict[key]
-			else:
-				original = None
-
-			if key in real_dict:
-				real = real_dict[key]
-			else:
-				real = None
-
-			# determine if there have been changes
-			if current == original and current != real:
-				continue   # another ranger instance has changed the bookmark
-
-			if key not in self.dct:
-				del real_dict[key]   # the user has deleted it
-			else:
-				real_dict[key] = current   # the user has changed it
-
-		self._set_dict(real_dict, original=real_dict_copy)
-
-	def save(self):
-		"""Save the bookmarks to the bookmarkfile.
-		This is done automatically after every modification if autosave is True."""
-		self.update()
-		if self.path is None:
-			return
-		if os.access(self.path, os.W_OK):
-			f = open(self.path+".new", 'w')
-			for key, value in self.dct.items():
-				if type(key) == str\
-						and key in ALLOWED_KEYS:
-					try:
-						f.write("{0}:{1}\n".format(str(key), str(value)))
-					except:
-						pass
-
-			f.close()
-			os.rename(self.path+".new", self.path)
-		self._update_mtime()
-
-	def _load_dict(self):
-		dct = {}
-
-		if self.path is None:
-			return dct
-
-		if not os.path.exists(self.path):
-			try:
-				f = open(self.path, 'w')
-			except:
-				raise OSError('Cannot read the given path')
-			f.close()
-
-		if os.access(self.path, os.R_OK):
-			f = open(self.path, 'r')
-			for line in f:
-				if self.load_pattern.match(line):
-					key, value = line[0], line[2:-1]
-					if key in ALLOWED_KEYS: 
-						dct[key] = self.bookmarktype(value)
-			f.close()
-			return dct
-		else:
-			raise OSError('Cannot read the given path')
-
-	def _set_dict(self, dct, original):
-		if original is None:
-			original = {}
-
-		self.dct.clear()
-		self.dct.update(dct)
-		self.original_dict = original
-		self._update_mtime()
-
-	def _get_mtime(self):
-		if self.path is None:
-			return None
-		try:
-			return os.stat(self.path).st_mtime
-		except OSError:
-			return None
-
-	def _update_mtime(self):
-		self.last_mtime = self._get_mtime()
+    """Bookmarks is a container which associates keys with bookmarks.
+
+        A key is a string with: len(key) == 1 and key in ALLOWED_KEYS.
+
+        A bookmark is an object with: bookmark == bookmarktype(str(instance))
+        Which is true for str or FileSystemObject. This condition is required
+        so bookmark-objects can be saved to and loaded from a file.
+
+        Optionally, a bookmark.go() method is used for entering a bookmark.
+        """
+
+    last_mtime = None
+    autosave = True
+    load_pattern = re.compile(r"^[\d\w']:.")
+
+    def __init__(self, bookmarkfile, bookmarktype=str, autosave=False):
+        """<bookmarkfile> specifies the path to the file where
+        bookmarks are saved in.
+        """
+        self.autosave = autosave
+        self.dct = {}
+        self.path = bookmarkfile
+        self.bookmarktype = bookmarktype
+
+    def load(self):
+        """Load the bookmarks from path/bookmarks"""
+        try:
+            new_dict = self._load_dict()
+        except OSError:
+            return
+
+        self._set_dict(new_dict, original=new_dict)
+
+    def delete(self, key):
+        """Delete the bookmark with the given key"""
+        if key == '`':
+            key = "'"
+        if key in self.dct:
+            del self.dct[key]
+            if self.autosave: self.save()
+
+    def enter(self, key):
+        """Enter the bookmark with the given key.
+        Requires the bookmark instance to have a go() method.
+        """
+
+        try:
+            return self[key].go()
+        except (IndexError, KeyError, AttributeError):
+            return False
+
+    def update_if_outdated(self):
+        if self.last_mtime != self._get_mtime():
+            self.update()
+
+    def remember(self, value):
+        """Bookmarks <value> to the key '"""
+        self["'"] = value
+        if self.autosave: self.save()
+
+    def __iter__(self):
+        return iter(self.dct.items())
+
+    def __getitem__(self, key):
+        """Get the bookmark associated with the key"""
+        if key == '`':
+            key = "'"
+        if key in self.dct:
+            return self.dct[key]
+        else:
+            raise KeyError("Nonexistant Bookmark: `%s'!" % key)
+
+    def __setitem__(self, key, value):
+        """Bookmark <value> to the key <key>.
+        key is expected to be a 1-character string and element of ALLOWED_KEYS.
+        value is expected to be a filesystemobject.
+        """
+        if key == '`':
+            key = "'"
+        if key in ALLOWED_KEYS:
+            self.dct[key] = value
+            if self.autosave: self.save()
+
+    def __contains__(self, key):
+        """Test whether a bookmark-key is defined"""
+        return key in self.dct
+
+    def update(self):
+        """Update the bookmarks from the bookmark file.
+        Useful if two instances are running which define different bookmarks.
+        """
+
+        try:
+            real_dict = self._load_dict()
+            real_dict_copy = real_dict.copy()
+        except OSError:
+            return
+
+        for key in set(self.dct.keys()) | set(real_dict.keys()):
+            # set some variables
+            if key in self.dct:
+                current = self.dct[key]
+            else:
+                current = None
+
+            if key in self.original_dict:
+                original = self.original_dict[key]
+            else:
+                original = None
+
+            if key in real_dict:
+                real = real_dict[key]
+            else:
+                real = None
+
+            # determine if there have been changes
+            if current == original and current != real:
+                continue   # another ranger instance has changed the bookmark
+
+            if key not in self.dct:
+                del real_dict[key]   # the user has deleted it
+            else:
+                real_dict[key] = current   # the user has changed it
+
+        self._set_dict(real_dict, original=real_dict_copy)
+
+    def save(self):
+        """Save the bookmarks to the bookmarkfile.
+        This is done automatically after every modification if autosave is True."""
+        self.update()
+        if self.path is None:
+            return
+        if os.access(self.path, os.W_OK):
+            f = open(self.path+".new", 'w')
+            for key, value in self.dct.items():
+                if type(key) == str\
+                        and key in ALLOWED_KEYS:
+                    try:
+                        f.write("{0}:{1}\n".format(str(key), str(value)))
+                    except:
+                        pass
+
+            f.close()
+            os.rename(self.path+".new", self.path)
+        self._update_mtime()
+
+    def _load_dict(self):
+        dct = {}
+
+        if self.path is None:
+            return dct
+
+        if not os.path.exists(self.path):
+            try:
+                f = open(self.path, 'w')
+            except:
+                raise OSError('Cannot read the given path')
+            f.close()
+
+        if os.access(self.path, os.R_OK):
+            f = open(self.path, 'r')
+            for line in f:
+                if self.load_pattern.match(line):
+                    key, value = line[0], line[2:-1]
+                    if key in ALLOWED_KEYS: 
+                        dct[key] = self.bookmarktype(value)
+            f.close()
+            return dct
+        else:
+            raise OSError('Cannot read the given path')
+
+    def _set_dict(self, dct, original):
+        if original is None:
+            original = {}
+
+        self.dct.clear()
+        self.dct.update(dct)
+        self.original_dict = original
+        self._update_mtime()
+
+    def _get_mtime(self):
+        if self.path is None:
+            return None
+        try:
+            return os.stat(self.path).st_mtime
+        except OSError:
+            return None
+
+    def _update_mtime(self):
+        self.last_mtime = self._get_mtime()
diff --git a/ranger/container/history.py b/ranger/container/history.py
index d0e077aa..6939c533 100644
--- a/ranger/container/history.py
+++ b/ranger/container/history.py
@@ -4,131 +4,131 @@
 # TODO: rewrite to use deque instead of list
 
 class HistoryEmptyException(Exception):
-	pass
+    pass
 
 class History(object):
-	def __init__(self, maxlen=None, unique=True):
-		if isinstance(maxlen, History):
-			self._history = list(maxlen._history)
-			self._index = maxlen._index
-			self.maxlen = maxlen.maxlen
-			self.unique = maxlen.unique
-		else:
-			self._history = []
-			self._index = 0
-			self.maxlen = maxlen
-			self.unique = unique
-
-	def add(self, item):
-		# Remove everything after index
-		if self._index < len(self._history) - 2:
-			del self._history[:self._index+1]
-		# Remove Duplicates
-		if self.unique:
-			try:
-				self._history.remove(item)
-			except:
-				pass
-		else:
-			if self._history and self._history[-1] == item:
-				del self._history[-1]
-		# Remove first if list is too long
-		if len(self._history) > max(self.maxlen - 1, 0):
-			del self._history[0]
-		# Append the item and fast forward
-		self._history.append(item)
-		self._index = len(self._history) - 1
-
-	def modify(self, item, unique=False):
-		if self._history and unique:
-			try:
-				self._history.remove(item)
-				self._index -= 1
-			except:
-				pass
-		try:
-			self._history[self._index] = item
-		except IndexError:
-			self.add(item)
-
-	def rebase(self, other_history):
-		assert isinstance(other_history, History)
-		index_offset = len(self._history) - self._index
-		self._history[:self._index] = list(other_history._history)
-		if len(self._history) > self.maxlen:
-			self._history = self._history[-self.maxlen:]
-		self._index = len(self._history) - index_offset
-
-	def __len__(self):
-		return len(self._history)
-
-	def current(self):
-		if self._history:
-			return self._history[self._index]
-		else:
-			raise HistoryEmptyException
-
-	def top(self):
-		try:
-			return self._history[-1]
-		except IndexError:
-			raise HistoryEmptyException()
-
-	def bottom(self):
-		try:
-			return self._history[0]
-		except IndexError:
-			raise HistoryEmptyException()
-
-	def back(self):
-		self._index -= 1
-		if self._index < 0:
-			self._index = 0
-		return self.current()
-
-	def move(self, n):
-		self._index += n
-		if self._index > len(self._history) - 1:
-			self._index = len(self._history) - 1
-		if self._index < 0:
-			self._index = 0
-		return self.current()
-
-	def search(self, string, n):
-		if n != 0 and string:
-			step = n > 0 and 1 or -1
-			i = self._index
-			steps_left = steps_left_at_start = int(abs(n))
-			while steps_left:
-				i += step
-				if i >= len(self._history) or i < 0:
-					break
-				if self._history[i].startswith(string):
-					steps_left -= 1
-			if steps_left != steps_left_at_start:
-				self._index = i
-		return self.current()
-
-	def __iter__(self):
-		return self._history.__iter__()
-
-	def next(self):
-		return self._history.next()
-
-	def forward(self):
-		if self._history:
-			self._index += 1
-			if self._index > len(self._history) - 1:
-				self._index = len(self._history) - 1
-		else:
-			self._index = 0
-		return self.current()
-
-	def fast_forward(self):
-		if self._history:
-			self._index = len(self._history) - 1
-		else:
-			self._index = 0
-
-	def _left(self):  # used for unit test
-		return self._history[0:self._index+1]
+    def __init__(self, maxlen=None, unique=True):
+        if isinstance(maxlen, History):
+            self._history = list(maxlen._history)
+            self._index = maxlen._index
+            self.maxlen = maxlen.maxlen
+            self.unique = maxlen.unique
+        else:
+            self._history = []
+            self._index = 0
+            self.maxlen = maxlen
+            self.unique = unique
+
+    def add(self, item):
+        # Remove everything after index
+        if self._index < len(self._history) - 2:
+            del self._history[:self._index+1]
+        # Remove Duplicates
+        if self.unique:
+            try:
+                self._history.remove(item)
+            except:
+                pass
+        else:
+            if self._history and self._history[-1] == item:
+                del self._history[-1]
+        # Remove first if list is too long
+        if len(self._history) > max(self.maxlen - 1, 0):
+            del self._history[0]
+        # Append the item and fast forward
+        self._history.append(item)
+        self._index = len(self._history) - 1
+
+    def modify(self, item, unique=False):
+        if self._history and unique:
+            try:
+                self._history.remove(item)
+                self._index -= 1
+            except:
+                pass
+        try:
+            self._history[self._index] = item
+        except IndexError:
+            self.add(item)
+
+    def rebase(self, other_history):
+        assert isinstance(other_history, History)
+        index_offset = len(self._history) - self._index
+        self._history[:self._index] = list(other_history._history)
+        if len(self._history) > self.maxlen:
+            self._history = self._history[-self.maxlen:]
+        self._index = len(self._history) - index_offset
+
+    def __len__(self):
+        return len(self._history)
+
+    def current(self):
+        if self._history:
+            return self._history[self._index]
+        else:
+            raise HistoryEmptyException
+
+    def top(self):
+        try:
+            return self._history[-1]
+        except IndexError:
+            raise HistoryEmptyException()
+
+    def bottom(self):
+        try:
+            return self._history[0]
+        except IndexError:
+            raise HistoryEmptyException()
+
+    def back(self):
+        self._index -= 1
+        if self._index < 0:
+            self._index = 0
+        return self.current()
+
+    def move(self, n):
+        self._index += n
+        if self._index > len(self._history) - 1:
+            self._index = len(self._history) - 1
+        if self._index < 0:
+            self._index = 0
+        return self.current()
+
+    def search(self, string, n):
+        if n != 0 and string:
+            step = n > 0 and 1 or -1
+            i = self._index
+            steps_left = steps_left_at_start = int(abs(n))
+            while steps_left:
+                i += step
+                if i >= len(self._history) or i < 0:
+                    break
+                if self._history[i].startswith(string):
+                    steps_left -= 1
+            if steps_left != steps_left_at_start:
+                self._index = i
+        return self.current()
+
+    def __iter__(self):
+        return self._history.__iter__()
+
+    def next(self):
+        return self._history.next()
+
+    def forward(self):
+        if self._history:
+            self._index += 1
+            if self._index > len(self._history) - 1:
+                self._index = len(self._history) - 1
+        else:
+            self._index = 0
+        return self.current()
+
+    def fast_forward(self):
+        if self._history:
+            self._index = len(self._history) - 1
+        else:
+            self._index = 0
+
+    def _left(self):  # used for unit test
+        return self._history[0:self._index+1]
diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py
index 7d715f38..3e5c287f 100644
--- a/ranger/container/settingobject.py
+++ b/ranger/container/settingobject.py
@@ -7,171 +7,171 @@ from ranger.core.shared import FileManagerAware
 import re
 
 ALLOWED_SETTINGS = {
-	'autosave_bookmarks': bool,
-	'autoupdate_cumulative_size': bool,
-	'collapse_preview': bool,
-	'colorscheme': str,
-	'column_ratios': (tuple, list),
-	'confirm_on_delete': str,
-	'dirname_in_tabs': bool,
-	'display_size_in_main_column': bool,
-	'display_size_in_status_bar': bool,
-	'display_tags_in_all_columns': bool,
-	'draw_borders': bool,
-	'draw_progress_bar_in_status_bar': bool,
-	'flushinput': bool,
-	'hidden_filter': (str, type(re.compile(""))), #COMPAT change to str-only
-	'max_console_history_size': (int, type(None)),
-	'max_history_size': (int, type(None)),
-	'mouse_enabled': bool,
-	'padding_right': bool,
-	'preview_directories': bool,
-	'preview_images': bool,
-	'preview_files': bool,
-	'preview_script': (str, type(None)),
-	'save_console_history': bool,
-	'scroll_offset': int,
-	'shorten_title': int,  # XXX Note: False is an instance of int
-	'show_cursor': bool,
-	'show_hidden_bookmarks': bool,
-	'show_hidden': bool,
-	'status_bar_on_top': bool,
-	'sort_case_insensitive': bool,
-	'sort_directories_first': bool,
-	'sort_reverse': bool,
-	'sort': str,
-	'tilde_in_titlebar': bool,
-	'unicode_ellipsis': bool,
-	'update_title': bool,
-	'update_tmux_title': bool,
-	'use_preview_script': bool,
-	'xterm_alt_key': bool,
+    'autosave_bookmarks': bool,
+    'autoupdate_cumulative_size': bool,
+    'collapse_preview': bool,
+    'colorscheme': str,
+    'column_ratios': (tuple, list),
+    'confirm_on_delete': str,
+    'dirname_in_tabs': bool,
+    'display_size_in_main_column': bool,
+    'display_size_in_status_bar': bool,
+    'display_tags_in_all_columns': bool,
+    'draw_borders': bool,
+    'draw_progress_bar_in_status_bar': bool,
+    'flushinput': bool,
+    'hidden_filter': (str, type(re.compile(""))), #COMPAT change to str-only
+    'max_console_history_size': (int, type(None)),
+    'max_history_size': (int, type(None)),
+    'mouse_enabled': bool,
+    'padding_right': bool,
+    'preview_directories': bool,
+    'preview_images': bool,
+    'preview_files': bool,
+    'preview_script': (str, type(None)),
+    'save_console_history': bool,
+    'scroll_offset': int,
+    'shorten_title': int,  # XXX Note: False is an instance of int
+    'show_cursor': bool,
+    'show_hidden_bookmarks': bool,
+    'show_hidden': bool,
+    'status_bar_on_top': bool,
+    'sort_case_insensitive': bool,
+    'sort_directories_first': bool,
+    'sort_reverse': bool,
+    'sort': str,
+    'tilde_in_titlebar': bool,
+    'unicode_ellipsis': bool,
+    'update_title': bool,
+    'update_tmux_title': bool,
+    'use_preview_script': bool,
+    'xterm_alt_key': bool,
 }
 
 DEFAULT_VALUES = {
-	bool: False,
-	type(None): None,
-	str: "",
-	int: 0,
-	list: [],
-	tuple: tuple([]),
+    bool: False,
+    type(None): None,
+    str: "",
+    int: 0,
+    list: [],
+    tuple: tuple([]),
 }
 
 class SettingObject(SignalDispatcher, FileManagerAware):
-	def __init__(self):
-		SignalDispatcher.__init__(self)
-		self.__dict__['_localsettings'] = dict()
-		self.__dict__['_settings'] = dict()
-		for name in ALLOWED_SETTINGS:
-			self.signal_bind('setopt.'+name,
-					self._raw_set_with_signal, priority=0.2)
-
-	def set(self, name, value, path=None):
-   		assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name)
-   		if name not in self._settings:
-   			previous = None
-   		else:
-   			previous=self._settings[name]
-   		assert self._check_type(name, value)
-   		kws = dict(setting=name, value=value, previous=previous, path=path, fm=self.fm)
-   		self.signal_emit('setopt', **kws)
-   		self.signal_emit('setopt.'+name, **kws)
-
-	def get(self, name, path=None):
-		assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name)
-		if path and path in self._localsettings and name in self._localsettings[path]:
-			return self._localsettings[path][name]
-		elif name in self._settings:
-			return self._settings[name]
-		else:
-			type_ = self.types_of(name)[0]
-			value = DEFAULT_VALUES[type_]
-			self._raw_set(name, value, None)
-			self.__setattr__(name, value)
-			return self._settings[name]
-
-	def __setattr__(self, name, value):
-		if name.startswith('_'):
-			self.__dict__[name] = value
-		else:
-			self.set(name, value, None)
-
-	def __getattr__(self, name):
-		if name.startswith('_'):
-			return self.__dict__[name]
-		else:
-			return self.get(name, None)
-
-	def __iter__(self):
-		for x in self._settings:
-			yield x
-
-	def types_of(self, name):
-		try:
-			typ = ALLOWED_SETTINGS[name]
-		except KeyError:
-			return tuple()
-		else:
-			if isinstance(typ, tuple):
-				return typ
-			else:
-				return (typ, )
-
-
-	def _check_type(self, name, value):
-		typ = ALLOWED_SETTINGS[name]
-		if isfunction(typ):
-			assert typ(value), \
-				"Warning: The option `" + name + "' has an incorrect type!"
-		else:
-			assert isinstance(value, typ), \
-				"Warning: The option `" + name + "' has an incorrect type!"\
-				" Got " + str(type(value)) + ", expected " + str(typ) + "!" +\
-				" Please check if your commands.py is up to date." if not \
-				self.fm.ui.is_set_up else ""
-		return True
-
-	__getitem__ = __getattr__
-	__setitem__ = __setattr__
-
-	def _raw_set(self, name, value, path):
-		if path:
-			if not path in self._localsettings:
-				self._localsettings[path] = dict()
-			self._localsettings[path][name] = value
-
-			# make sure name is in _settings, so __iter__ runs through local settigns too.
-			if not name in self._settings:
-				type_ = self.types_of(name)[0]
-				value = DEFAULT_VALUES[type_]
-				self._settings[name] = value
-		else:
-			self._settings[name] = value
-
-	def _raw_set_with_signal(self, signal):
-		self._raw_set(signal.setting, signal.value, signal.path)
+    def __init__(self):
+        SignalDispatcher.__init__(self)
+        self.__dict__['_localsettings'] = dict()
+        self.__dict__['_settings'] = dict()
+        for name in ALLOWED_SETTINGS:
+            self.signal_bind('setopt.'+name,
+                    self._raw_set_with_signal, priority=0.2)
+
+    def set(self, name, value, path=None):
+        assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name)
+        if name not in self._settings:
+            previous = None
+        else:
+            previous=self._settings[name]
+        assert self._check_type(name, value)
+        kws = dict(setting=name, value=value, previous=previous, path=path, fm=self.fm)
+        self.signal_emit('setopt', **kws)
+        self.signal_emit('setopt.'+name, **kws)
+
+    def get(self, name, path=None):
+        assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name)
+        if path and path in self._localsettings and name in self._localsettings[path]:
+            return self._localsettings[path][name]
+        elif name in self._settings:
+            return self._settings[name]
+        else:
+            type_ = self.types_of(name)[0]
+            value = DEFAULT_VALUES[type_]
+            self._raw_set(name, value, None)
+            self.__setattr__(name, value)
+            return self._settings[name]
+
+    def __setattr__(self, name, value):
+        if name.startswith('_'):
+            self.__dict__[name] = value
+        else:
+            self.set(name, value, None)
+
+    def __getattr__(self, name):
+        if name.startswith('_'):
+            return self.__dict__[name]
+        else:
+            return self.get(name, None)
+
+    def __iter__(self):
+        for x in self._settings:
+            yield x
+
+    def types_of(self, name):
+        try:
+            typ = ALLOWED_SETTINGS[name]
+        except KeyError:
+            return tuple()
+        else:
+            if isinstance(typ, tuple):
+                return typ
+            else:
+                return (typ, )
+
+
+    def _check_type(self, name, value):
+        typ = ALLOWED_SETTINGS[name]
+        if isfunction(typ):
+            assert typ(value), \
+                "Warning: The option `" + name + "' has an incorrect type!"
+        else:
+            assert isinstance(value, typ), \
+                "Warning: The option `" + name + "' has an incorrect type!"\
+                " Got " + str(type(value)) + ", expected " + str(typ) + "!" +\
+                " Please check if your commands.py is up to date." if not \
+                self.fm.ui.is_set_up else ""
+        return True
+
+    __getitem__ = __getattr__
+    __setitem__ = __setattr__
+
+    def _raw_set(self, name, value, path):
+        if path:
+            if not path in self._localsettings:
+                self._localsettings[path] = dict()
+            self._localsettings[path][name] = value
+
+            # make sure name is in _settings, so __iter__ runs through local settigns too.
+            if not name in self._settings:
+                type_ = self.types_of(name)[0]
+                value = DEFAULT_VALUES[type_]
+                self._settings[name] = value
+        else:
+            self._settings[name] = value
+
+    def _raw_set_with_signal(self, signal):
+        self._raw_set(signal.setting, signal.value, signal.path)
 
 
 class LocalSettingObject():
-	def __init__(self, path, parent):
-		self.__dict__['_parent'] = parent
-		self.__dict__['_path'] = path
-
-	def __setattr__(self, name, value):
-		if name.startswith('_'):
-			self.__dict__[name] = value
-		else:
-			self._parent.set(name, value, self._path)
-
-	def __getattr__(self, name):
-		if name.startswith('_'):
-			return self.__dict__[name]
-		else:
-			return self._parent.get(name, self._path)
-
-	def __iter__(self):
-		for x in self._parent._settings:
-			yield x
-
-	__getitem__ = __getattr__
-	__setitem__ = __setattr__
+    def __init__(self, path, parent):
+        self.__dict__['_parent'] = parent
+        self.__dict__['_path'] = path
+
+    def __setattr__(self, name, value):
+        if name.startswith('_'):
+            self.__dict__[name] = value
+        else:
+            self._parent.set(name, value, self._path)
+
+    def __getattr__(self, name):
+        if name.startswith('_'):
+            return self.__dict__[name]
+        else:
+            return self._parent.get(name, self._path)
+
+    def __iter__(self):
+        for x in self._parent._settings:
+            yield x
+
+    __getitem__ = __getattr__
+    __setitem__ = __setattr__
diff --git a/ranger/container/tags.py b/ranger/container/tags.py
index 7212345e..b37ac4dc 100644
--- a/ranger/container/tags.py
+++ b/ranger/container/tags.py
@@ -7,103 +7,103 @@ import string
 ALLOWED_KEYS = string.ascii_letters + string.digits + string.punctuation
 
 class Tags(object):
-	default_tag = '*'
-
-	def __init__(self, filename):
-
-		self._filename = realpath(abspath(expanduser(filename)))
-
-		if isdir(dirname(self._filename)) and not exists(self._filename):
-			open(self._filename, 'w')
-
-		self.sync()
-
-	def __contains__(self, item):
-		return item in self.tags
-
-	def add(self, *items, **others):
-		if 'tag' in others:
-			tag = others['tag']
-		else:
-			tag = self.default_tag
-		self.sync()
-		for item in items:
-			self.tags[item] = tag
-		self.dump()
-
-	def remove(self, *items):
-		self.sync()
-		for item in items:
-			try:
-				del(self.tags[item])
-			except KeyError:
-				pass
-		self.dump()
-
-	def toggle(self, *items, **others):
-		if 'tag' in others:
-			tag = others['tag']
-		else:
-			tag = self.default_tag
-		tag = str(tag)
-		if tag not in ALLOWED_KEYS:
-			return
-		self.sync()
-		for item in items:
-			try:
-				if item in self and tag in (self.tags[item], self.default_tag):
-					del(self.tags[item])
-				else:
-					self.tags[item] = tag
-			except KeyError:
-				pass
-		self.dump()
-
-	def marker(self, item):
-		if item in self.tags:
-			return self.tags[item]
-		else:
-			return self.default_tag
-
-	def sync(self):
-		try:
-			f = open(self._filename, 'r')
-		except OSError:
-			pass
-		else:
-			self.tags = self._parse(f)
-			f.close()
-
-	def dump(self):
-		try:
-			f = open(self._filename, 'w')
-		except OSError:
-			pass
-		else:
-			self._compile(f)
-			f.close()
-
-	def _compile(self, f):
-		for path, tag in self.tags.items():
-			if tag == self.default_tag:
-				# COMPAT: keep the old format if the default tag is used
-				f.write(path + '\n')
-			elif tag in ALLOWED_KEYS:
-				f.write('{0}:{1}\n'.format(tag, path))
-
-	def _parse(self, f):
-		result = dict()
-		for line in f:
-			line = line.strip()
-			if len(line) > 2 and line[1] == ':':
-				tag, path = line[0], line[2:]
-				if tag in ALLOWED_KEYS:
-					result[path] = tag
-			else:
-				result[line] = self.default_tag
-
-		return result
-
-	def __nonzero__(self):
-		return True
-	__bool__ = __nonzero__
+    default_tag = '*'
+
+    def __init__(self, filename):
+
+        self._filename = realpath(abspath(expanduser(filename)))
+
+        if isdir(dirname(self._filename)) and not exists(self._filename):
+            open(self._filename, 'w')
+
+        self.sync()
+
+    def __contains__(self, item):
+        return item in self.tags
+
+    def add(self, *items, **others):
+        if 'tag' in others:
+            tag = others['tag']
+        else:
+            tag = self.default_tag
+        self.sync()
+        for item in items:
+            self.tags[item] = tag
+        self.dump()
+
+    def remove(self, *items):
+        self.sync()
+        for item in items:
+            try:
+                del(self.tags[item])
+            except KeyError:
+                pass
+        self.dump()
+
+    def toggle(self, *items, **others):
+        if 'tag' in others:
+            tag = others['tag']
+        else:
+            tag = self.default_tag
+        tag = str(tag)
+        if tag not in ALLOWED_KEYS:
+            return
+        self.sync()
+        for item in items:
+            try:
+                if item in self and tag in (self.tags[item], self.default_tag):
+                    del(self.tags[item])
+                else:
+                    self.tags[item] = tag
+            except KeyError:
+                pass
+        self.dump()
+
+    def marker(self, item):
+        if item in self.tags:
+            return self.tags[item]
+        else:
+            return self.default_tag
+
+    def sync(self):
+        try:
+            f = open(self._filename, 'r')
+        except OSError:
+            pass
+        else:
+            self.tags = self._parse(f)
+            f.close()
+
+    def dump(self):
+        try:
+            f = open(self._filename, 'w')
+        except OSError:
+            pass
+        else:
+            self._compile(f)
+            f.close()
+
+    def _compile(self, f):
+        for path, tag in self.tags.items():
+            if tag == self.default_tag:
+                # COMPAT: keep the old format if the default tag is used
+                f.write(path + '\n')
+            elif tag in ALLOWED_KEYS:
+                f.write('{0}:{1}\n'.format(tag, path))
+
+    def _parse(self, f):
+        result = dict()
+        for line in f:
+            line = line.strip()
+            if len(line) > 2 and line[1] == ':':
+                tag, path = line[0], line[2:]
+                if tag in ALLOWED_KEYS:
+                    result[path] = tag
+            else:
+                result[line] = self.default_tag
+
+        return result
+
+    def __nonzero__(self):
+        return True
+    __bool__ = __nonzero__
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)
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index 61bbb6b2..6ef61941 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -9,103 +9,103 @@ from ranger.core.shared import SettingsAware, FileManagerAware
 
 # COMPAT
 class Environment(SettingsAware, FileManagerAware, SignalDispatcher):
-	def __init__(self, path):
-		SignalDispatcher.__init__(self)
-
-	def _get_copy(self): return self.fm.copy_buffer
-	def _set_copy(self, obj): self.fm.copy_buffer = obj
-	copy = property(_get_copy, _set_copy)
-
-	def _get_cut(self): return self.fm.do_cut
-	def _set_cut(self, obj): self.fm.do_cut = obj
-	cut = property(_get_cut, _set_cut)
-
-	def _get_keymaps(self): return self.fm.ui.keymaps
-	def _set_keymaps(self, obj): self.fm.ui.keymaps = obj
-	keymaps = property(_get_keymaps, _set_keymaps)
-
-	def _get_keybuffer(self): return self.fm.ui.keybuffer
-	def _set_keybuffer(self, obj): self.fm.ui.keybuffer = obj
-	keybuffer = property(_get_keybuffer, _set_keybuffer)
-
-	def _get_username(self): return self.fm.username
-	def _set_username(self, obj): self.fm.username = obj
-	username = property(_get_username, _set_username)
-
-	def _get_hostname(self): return self.fm.hostname
-	def _set_hostname(self, obj): self.fm.hostname = obj
-	hostname = property(_get_hostname, _set_hostname)
-
-	def _get_home_path(self): return self.fm.home_path
-	def _set_home_path(self, obj): self.fm.home_path = obj
-	home_path = property(_get_home_path, _set_home_path)
-
-	def _get_get_directory(self): return self.fm.get_directory
-	def _set_get_directory(self, obj): self.fm.get_directory = obj
-	get_directory = property(_get_get_directory, _set_get_directory)
-
-	def _get_garbage_collect(self): return self.fm.garbage_collect
-	def _set_garbage_collect(self, obj): self.fm.garbage_collect = obj
-	garbage_collect = property(_get_garbage_collect, _set_garbage_collect)
-
-	def _get_cwd(self): return self.fm.thisdir
-	def _set_cwd(self, obj): self.fm.thisdir = obj
-	cwd = property(_get_cwd, _set_cwd)
-
-	def _get_cf(self): return self.fm.thisfile
-	def _set_cf(self, obj): self.fm.thisfile = obj
-	cf = property(_get_cf, _set_cf)
-
-	def _get_history(self): return self.fm.thistab.history
-	def _set_history(self, obj): self.fm.thistab.history = obj
-	history = property(_get_history, _set_history)
-
-	def _get_last_search(self): return self.fm.thistab.last_search
-	def _set_last_search(self, obj): self.fm.thistab.last_search = obj
-	last_search = property(_get_last_search, _set_last_search)
-
-	def _get_path(self): return self.fm.thistab.path
-	def _set_path(self, obj): self.fm.thistab.path = obj
-	path = property(_get_path, _set_path)
-
-	def _get_pathway(self): return self.fm.thistab.pathway
-	def _set_pathway(self, obj): self.fm.thistab.pathway = obj
-	pathway = property(_get_pathway, _set_pathway)
-
-	def _get_enter_dir(self): return self.fm.thistab.enter_dir
-	def _set_enter_dir(self, obj): self.fm.thistab.enter_dir = obj
-	enter_dir = property(_get_enter_dir, _set_enter_dir)
-
-	def _get_at_level(self): return self.fm.thistab.at_level
-	def _set_at_level(self, obj): self.fm.thistab.at_level = obj
-	at_level = property(_get_at_level, _set_at_level)
-
-	def _get_get_selection(self): return self.fm.thistab.get_selection
-	def _set_get_selection(self, obj): self.fm.thistab.get_selection = obj
-	get_selection = property(_get_get_selection, _set_get_selection)
-
-	def _get_assign_cursor_positions_for_subdirs(self):
-		return self.fm.thistab.assign_cursor_positions_for_subdirs
-	def _set_assign_cursor_positions_for_subdirs(self, obj):
-		self.fm.thistab.assign_cursor_positions_for_subdirs = obj
-	assign_cursor_positions_for_subdirs = property(
-			_get_assign_cursor_positions_for_subdirs,
-			_set_assign_cursor_positions_for_subdirs)
-
-	def _get_ensure_correct_pointer(self):
-		return self.fm.thistab.ensure_correct_pointer
-	def _set_ensure_correct_pointer(self, obj):
-		self.fm.thistab.ensure_correct_pointer = obj
-	ensure_correct_pointer = property(_get_ensure_correct_pointer,
-			_set_ensure_correct_pointer)
-
-	def _get_history_go(self): return self.fm.thistab.history_go
-	def _set_history_go(self, obj): self.fm.thistab.history_go = obj
-	history_go = property(_get_history_go, _set_history_go)
-
-	def _set_cf_from_signal(self, signal):
-		self.fm._cf = signal.new
-
-	def get_free_space(self, path):
-		stat = os.statvfs(path)
-		return stat.f_bavail * stat.f_bsize
+    def __init__(self, path):
+        SignalDispatcher.__init__(self)
+
+    def _get_copy(self): return self.fm.copy_buffer
+    def _set_copy(self, obj): self.fm.copy_buffer = obj
+    copy = property(_get_copy, _set_copy)
+
+    def _get_cut(self): return self.fm.do_cut
+    def _set_cut(self, obj): self.fm.do_cut = obj
+    cut = property(_get_cut, _set_cut)
+
+    def _get_keymaps(self): return self.fm.ui.keymaps
+    def _set_keymaps(self, obj): self.fm.ui.keymaps = obj
+    keymaps = property(_get_keymaps, _set_keymaps)
+
+    def _get_keybuffer(self): return self.fm.ui.keybuffer
+    def _set_keybuffer(self, obj): self.fm.ui.keybuffer = obj
+    keybuffer = property(_get_keybuffer, _set_keybuffer)
+
+    def _get_username(self): return self.fm.username
+    def _set_username(self, obj): self.fm.username = obj
+    username = property(_get_username, _set_username)
+
+    def _get_hostname(self): return self.fm.hostname
+    def _set_hostname(self, obj): self.fm.hostname = obj
+    hostname = property(_get_hostname, _set_hostname)
+
+    def _get_home_path(self): return self.fm.home_path
+    def _set_home_path(self, obj): self.fm.home_path = obj
+    home_path = property(_get_home_path, _set_home_path)
+
+    def _get_get_directory(self): return self.fm.get_directory
+    def _set_get_directory(self, obj): self.fm.get_directory = obj
+    get_directory = property(_get_get_directory, _set_get_directory)
+
+    def _get_garbage_collect(self): return self.fm.garbage_collect
+    def _set_garbage_collect(self, obj): self.fm.garbage_collect = obj
+    garbage_collect = property(_get_garbage_collect, _set_garbage_collect)
+
+    def _get_cwd(self): return self.fm.thisdir
+    def _set_cwd(self, obj): self.fm.thisdir = obj
+    cwd = property(_get_cwd, _set_cwd)
+
+    def _get_cf(self): return self.fm.thisfile
+    def _set_cf(self, obj): self.fm.thisfile = obj
+    cf = property(_get_cf, _set_cf)
+
+    def _get_history(self): return self.fm.thistab.history
+    def _set_history(self, obj): self.fm.thistab.history = obj
+    history = property(_get_history, _set_history)
+
+    def _get_last_search(self): return self.fm.thistab.last_search
+    def _set_last_search(self, obj): self.fm.thistab.last_search = obj
+    last_search = property(_get_last_search, _set_last_search)
+
+    def _get_path(self): return self.fm.thistab.path
+    def _set_path(self, obj): self.fm.thistab.path = obj
+    path = property(_get_path, _set_path)
+
+    def _get_pathway(self): return self.fm.thistab.pathway
+    def _set_pathway(self, obj): self.fm.thistab.pathway = obj
+    pathway = property(_get_pathway, _set_pathway)
+
+    def _get_enter_dir(self): return self.fm.thistab.enter_dir
+    def _set_enter_dir(self, obj): self.fm.thistab.enter_dir = obj
+    enter_dir = property(_get_enter_dir, _set_enter_dir)
+
+    def _get_at_level(self): return self.fm.thistab.at_level
+    def _set_at_level(self, obj): self.fm.thistab.at_level = obj
+    at_level = property(_get_at_level, _set_at_level)
+
+    def _get_get_selection(self): return self.fm.thistab.get_selection
+    def _set_get_selection(self, obj): self.fm.thistab.get_selection = obj
+    get_selection = property(_get_get_selection, _set_get_selection)
+
+    def _get_assign_cursor_positions_for_subdirs(self):
+        return self.fm.thistab.assign_cursor_positions_for_subdirs
+    def _set_assign_cursor_positions_for_subdirs(self, obj):
+        self.fm.thistab.assign_cursor_positions_for_subdirs = obj
+    assign_cursor_positions_for_subdirs = property(
+            _get_assign_cursor_positions_for_subdirs,
+            _set_assign_cursor_positions_for_subdirs)
+
+    def _get_ensure_correct_pointer(self):
+        return self.fm.thistab.ensure_correct_pointer
+    def _set_ensure_correct_pointer(self, obj):
+        self.fm.thistab.ensure_correct_pointer = obj
+    ensure_correct_pointer = property(_get_ensure_correct_pointer,
+            _set_ensure_correct_pointer)
+
+    def _get_history_go(self): return self.fm.thistab.history_go
+    def _set_history_go(self, obj): self.fm.thistab.history_go = obj
+    history_go = property(_get_history_go, _set_history_go)
+
+    def _set_cf_from_signal(self, signal):
+        self.fm._cf = signal.new
+
+    def get_free_space(self, path):
+        stat = os.statvfs(path)
+        return stat.f_bavail * stat.f_bsize
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 0cd3cb5c..d29e446b 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -29,262 +29,262 @@ from ranger import __version__
 from ranger.core.loader import Loader
 
 class FM(Actions, SignalDispatcher):
-	input_blocked = False
-	input_blocked_until = 0
-	mode = 'normal'  # either 'normal' or 'visual'.
-	search_method = 'ctime'
-
-	_previous_selection = None
-	_visual_reverse = False
-	_visual_start = None
-	_visual_start_pos = None
-
-	def __init__(self, ui=None, bookmarks=None, tags=None, paths=['.']):
-		"""Initialize FM."""
-		Actions.__init__(self)
-		SignalDispatcher.__init__(self)
-		if ui is None:
-			self.ui = UI()
-		else:
-			self.ui = ui
-		self.start_paths = paths
-		self.directories = dict()
-		self.log = deque(maxlen=20)
-		self.bookmarks = bookmarks
-		self.current_tab = 1
-		self.tabs = {}
-		self.tags = tags
-		self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS)
-		self.py3 = sys.version_info >= (3, )
-		self.previews = {}
-		self.loader = Loader()
-		self.copy_buffer = set()
-		self.do_cut = False
-
-		try:
-			self.username = pwd.getpwuid(os.geteuid()).pw_name
-		except:
-			self.username = 'uid:' + str(os.geteuid())
-		self.hostname = socket.gethostname()
-		self.home_path = os.path.expanduser('~')
-
-		self.log.append('ranger {0} started! Process ID is {1}.' \
-				.format(__version__, os.getpid()))
-		self.log.append('Running on Python ' + sys.version.replace('\n',''))
-
-		mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
-		mimetypes.knownfiles.append(self.relpath('data/mime.types'))
-		self.mimetypes = mimetypes.MimeTypes()
-
-	def initialize(self):
-		"""If ui/bookmarks are None, they will be initialized here."""
-
-		self.tabs = dict((n+1, Tab(path)) for n, path in
-				enumerate(self.start_paths))
-		tab_list = self._get_tab_list()
-		if tab_list:
-			self.current_tab = tab_list[0]
-			self.thistab = self.tabs[self.current_tab]
-		else:
-			self.current_tab = 1
-			self.tabs[self.current_tab] = self.thistab = Tab('.')
-
-		if not ranger.arg.clean and os.path.isfile(self.confpath('rifle.conf')):
-			rifleconf = self.confpath('rifle.conf')
-		else:
-			rifleconf = self.relpath('config/rifle.conf')
-		self.rifle = Rifle(rifleconf)
-		self.rifle.reload_config()
-
-		if self.bookmarks is None:
-			if ranger.arg.clean:
-				bookmarkfile = None
-			else:
-				bookmarkfile = self.confpath('bookmarks')
-			self.bookmarks = Bookmarks(
-					bookmarkfile=bookmarkfile,
-					bookmarktype=Directory,
-					autosave=self.settings.autosave_bookmarks)
-			self.bookmarks.load()
-
-		if not ranger.arg.clean and self.tags is None:
-			self.tags = Tags(self.confpath('tagged'))
-
-		self.ui.setup_curses()
-		self.ui.initialize()
-
-		self.rifle.hook_before_executing = lambda a, b, flags: \
-			self.ui.suspend() if 'f' not in flags else None
-		self.rifle.hook_after_executing = lambda a, b, flags: \
-			self.ui.initialize() if 'f' not in flags else None
-		self.rifle.hook_logger = self.notify
-
-		def mylogfunc(text):
-			self.notify(text, bad=True)
-		self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
-
-	def destroy(self):
-		debug = ranger.arg.debug
-		if self.ui:
-			try:
-				self.ui.destroy()
-			except:
-				if debug:
-					raise
-		if self.loader:
-			try:
-				self.loader.destroy()
-			except:
-				if debug:
-					raise
-
-	def _get_thisfile(self):
-		return self.thistab.thisfile
-
-	def _set_thisfile(self, obj):
-		self.thistab.thisfile = obj
-
-	def _get_thisdir(self):
-		return self.thistab.thisdir
-
-	def _set_thisdir(self, obj):
-		self.thistab.thisdir = obj
-
-	thisfile = property(_get_thisfile, _set_thisfile)
-	thisdir  = property(_get_thisdir,  _set_thisdir)
-
-	def block_input(self, sec=0):
-		self.input_blocked = sec != 0
-		self.input_blocked_until = time() + sec
-
-	def input_is_blocked(self):
-		if self.input_blocked and time() > self.input_blocked_until:
-			self.input_blocked = False
-		return self.input_blocked
-
-	def copy_config_files(self, which):
-		if ranger.arg.clean:
-			sys.stderr.write("refusing to copy config files in clean mode\n")
-			return
-		import shutil
-		def copy(_from, to):
-			if os.path.exists(self.confpath(to)):
-				sys.stderr.write("already exists: %s\n" % self.confpath(to))
-			else:
-				sys.stderr.write("creating: %s\n" % self.confpath(to))
-				try:
-					shutil.copy(self.relpath(_from), self.confpath(to))
-				except Exception as e:
-					sys.stderr.write("  ERROR: %s\n" % str(e))
-		if which == 'rifle' or which == 'all':
-			copy('config/rifle.conf', 'rifle.conf')
-		if which == 'commands' or which == 'all':
-			copy('config/commands.py', 'commands.py')
-		if which == 'rc' or which == 'all':
-			copy('config/rc.conf', 'rc.conf')
-		if which == 'scope' or which == 'all':
-			copy('data/scope.sh', 'scope.sh')
-			os.chmod(self.confpath('scope.sh'),
-				os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR)
-		if which in ('all', 'rifle', 'scope', 'commands', 'rc'):
-			sys.stderr.write("\nPlease note that configuration files may "
-				"change as ranger evolves.\nIt's completely up to you to keep "
-				"them up to date.\n")
-		else:
-			sys.stderr.write("Unknown config file `%s'\n" % which)
-
-	def confpath(self, *paths):
-		"""returns the path relative to rangers configuration directory"""
-		if ranger.arg.clean:
-			assert 0, "Should not access relpath_conf in clean mode!"
-		else:
-			return os.path.join(ranger.arg.confdir, *paths)
-
-	def relpath(self, *paths):
-		"""returns the path relative to rangers library directory"""
-		return os.path.join(ranger.RANGERDIR, *paths)
-
-	def get_directory(self, path):
-		"""Get the directory object at the given path"""
-		path = os.path.abspath(path)
-		try:
-			return self.directories[path]
-		except KeyError:
-			obj = Directory(path)
-			self.directories[path] = obj
-			return obj
-
-	def garbage_collect(self, age, tabs=None):  # tabs=None is for COMPATibility
-		"""Delete unused directory objects"""
-		for key in tuple(self.directories):
-			value = self.directories[key]
-			if age != -1:
-				if not value.is_older_than(age) \
-						or any(value in tab.pathway for tab in self.tabs.values()):
-					continue
-			del self.directories[key]
-			if value.is_directory:
-				value.files = None
-		self.settings.signal_garbage_collect()
-		self.signal_garbage_collect()
-
-	def loop(self):
-		"""
-		The main loop consists of:
-		1. reloading bookmarks if outdated
-		2. letting the loader work
-		3. drawing and finalizing ui
-		4. reading and handling user input
-		5. after X loops: collecting unused directory objects
-		"""
-
-		self.enter_dir(self.thistab.path)
-
-		gc_tick = 0
-
-		# for faster lookup:
-		ui = self.ui
-		throbber = ui.throbber
-		loader = self.loader
-		has_throbber = hasattr(ui, 'throbber')
-		zombies = self.run.zombies
-
-		ranger.api.hook_ready(self)
-
-		try:
-			while True:
-				loader.work()
-				if has_throbber:
-					if loader.has_work():
-						throbber(loader.status)
-					else:
-						throbber(remove=True)
-
-				ui.redraw()
-
-				ui.set_load_mode(not loader.paused and loader.has_work())
-
-				ui.handle_input()
-
-				if zombies:
-					for zombie in tuple(zombies):
-						if zombie.poll() is not None:
-							zombies.remove(zombie)
-
-				gc_tick += 1
-				if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE:
-					gc_tick = 0
-					self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE)
-
-		except KeyboardInterrupt:
-			# this only happens in --debug mode. By default, interrupts
-			# are caught in curses_interrupt_handler
-			raise SystemExit
-
-		finally:
-			if ranger.arg.choosedir and self.thisdir and self.thisdir.path:
-				# XXX: UnicodeEncodeError: 'utf-8' codec can't encode character
-				# '\udcf6' in position 42: surrogates not allowed
-				open(ranger.arg.choosedir, 'w').write(self.thisdir.path)
-			self.bookmarks.remember(self.thisdir)
-			self.bookmarks.save()
+    input_blocked = False
+    input_blocked_until = 0
+    mode = 'normal'  # either 'normal' or 'visual'.
+    search_method = 'ctime'
+
+    _previous_selection = None
+    _visual_reverse = False
+    _visual_start = None
+    _visual_start_pos = None
+
+    def __init__(self, ui=None, bookmarks=None, tags=None, paths=['.']):
+        """Initialize FM."""
+        Actions.__init__(self)
+        SignalDispatcher.__init__(self)
+        if ui is None:
+            self.ui = UI()
+        else:
+            self.ui = ui
+        self.start_paths = paths
+        self.directories = dict()
+        self.log = deque(maxlen=20)
+        self.bookmarks = bookmarks
+        self.current_tab = 1
+        self.tabs = {}
+        self.tags = tags
+        self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS)
+        self.py3 = sys.version_info >= (3, )
+        self.previews = {}
+        self.loader = Loader()
+        self.copy_buffer = set()
+        self.do_cut = False
+
+        try:
+            self.username = pwd.getpwuid(os.geteuid()).pw_name
+        except:
+            self.username = 'uid:' + str(os.geteuid())
+        self.hostname = socket.gethostname()
+        self.home_path = os.path.expanduser('~')
+
+        self.log.append('ranger {0} started! Process ID is {1}.' \
+                .format(__version__, os.getpid()))
+        self.log.append('Running on Python ' + sys.version.replace('\n',''))
+
+        mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
+        mimetypes.knownfiles.append(self.relpath('data/mime.types'))
+        self.mimetypes = mimetypes.MimeTypes()
+
+    def initialize(self):
+        """If ui/bookmarks are None, they will be initialized here."""
+
+        self.tabs = dict((n+1, Tab(path)) for n, path in
+                enumerate(self.start_paths))
+        tab_list = self._get_tab_list()
+        if tab_list:
+            self.current_tab = tab_list[0]
+            self.thistab = self.tabs[self.current_tab]
+        else:
+            self.current_tab = 1
+            self.tabs[self.current_tab] = self.thistab = Tab('.')
+
+        if not ranger.arg.clean and os.path.isfile(self.confpath('rifle.conf')):
+            rifleconf = self.confpath('rifle.conf')
+        else:
+            rifleconf = self.relpath('config/rifle.conf')
+        self.rifle = Rifle(rifleconf)
+        self.rifle.reload_config()
+
+        if self.bookmarks is None:
+            if ranger.arg.clean:
+                bookmarkfile = None
+            else:
+                bookmarkfile = self.confpath('bookmarks')
+            self.bookmarks = Bookmarks(
+                    bookmarkfile=bookmarkfile,
+                    bookmarktype=Directory,
+                    autosave=self.settings.autosave_bookmarks)
+            self.bookmarks.load()
+
+        if not ranger.arg.clean and self.tags is None:
+            self.tags = Tags(self.confpath('tagged'))
+
+        self.ui.setup_curses()
+        self.ui.initialize()
+
+        self.rifle.hook_before_executing = lambda a, b, flags: \
+            self.ui.suspend() if 'f' not in flags else None
+        self.rifle.hook_after_executing = lambda a, b, flags: \
+            self.ui.initialize() if 'f' not in flags else None
+        self.rifle.hook_logger = self.notify
+
+        def mylogfunc(text):
+            self.notify(text, bad=True)
+        self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
+
+    def destroy(self):
+        debug = ranger.arg.debug
+        if self.ui:
+            try:
+                self.ui.destroy()
+            except:
+                if debug:
+                    raise
+        if self.loader:
+            try:
+                self.loader.destroy()
+            except:
+                if debug:
+                    raise
+
+    def _get_thisfile(self):
+        return self.thistab.thisfile
+
+    def _set_thisfile(self, obj):
+        self.thistab.thisfile = obj
+
+    def _get_thisdir(self):
+        return self.thistab.thisdir
+
+    def _set_thisdir(self, obj):
+        self.thistab.thisdir = obj
+
+    thisfile = property(_get_thisfile, _set_thisfile)
+    thisdir  = property(_get_thisdir,  _set_thisdir)
+
+    def block_input(self, sec=0):
+        self.input_blocked = sec != 0
+        self.input_blocked_until = time() + sec
+
+    def input_is_blocked(self):
+        if self.input_blocked and time() > self.input_blocked_until:
+            self.input_blocked = False
+        return self.input_blocked
+
+    def copy_config_files(self, which):
+        if ranger.arg.clean:
+            sys.stderr.write("refusing to copy config files in clean mode\n")
+            return
+        import shutil
+        def copy(_from, to):
+            if os.path.exists(self.confpath(to)):
+                sys.stderr.write("already exists: %s\n" % self.confpath(to))
+            else:
+                sys.stderr.write("creating: %s\n" % self.confpath(to))
+                try:
+                    shutil.copy(self.relpath(_from), self.confpath(to))
+                except Exception as e:
+                    sys.stderr.write("  ERROR: %s\n" % str(e))
+        if which == 'rifle' or which == 'all':
+            copy('config/rifle.conf', 'rifle.conf')
+        if which == 'commands' or which == 'all':
+            copy('config/commands.py', 'commands.py')
+        if which == 'rc' or which == 'all':
+            copy('config/rc.conf', 'rc.conf')
+        if which == 'scope' or which == 'all':
+            copy('data/scope.sh', 'scope.sh')
+            os.chmod(self.confpath('scope.sh'),
+                os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR)
+        if which in ('all', 'rifle', 'scope', 'commands', 'rc'):
+            sys.stderr.write("\nPlease note that configuration files may "
+                "change as ranger evolves.\nIt's completely up to you to keep "
+                "them up to date.\n")
+        else:
+            sys.stderr.write("Unknown config file `%s'\n" % which)
+
+    def confpath(self, *paths):
+        """returns the path relative to rangers configuration directory"""
+        if ranger.arg.clean:
+            assert 0, "Should not access relpath_conf in clean mode!"
+        else:
+            return os.path.join(ranger.arg.confdir, *paths)
+
+    def relpath(self, *paths):
+        """returns the path relative to rangers library directory"""
+        return os.path.join(ranger.RANGERDIR, *paths)
+
+    def get_directory(self, path):
+        """Get the directory object at the given path"""
+        path = os.path.abspath(path)
+        try:
+            return self.directories[path]
+        except KeyError:
+            obj = Directory(path)
+            self.directories[path] = obj
+            return obj
+
+    def garbage_collect(self, age, tabs=None):  # tabs=None is for COMPATibility
+        """Delete unused directory objects"""
+        for key in tuple(self.directories):
+            value = self.directories[key]
+            if age != -1:
+                if not value.is_older_than(age) \
+                        or any(value in tab.pathway for tab in self.tabs.values()):
+                    continue
+            del self.directories[key]
+            if value.is_directory:
+                value.files = None
+        self.settings.signal_garbage_collect()
+        self.signal_garbage_collect()
+
+    def loop(self):
+        """
+        The main loop consists of:
+        1. reloading bookmarks if outdated
+        2. letting the loader work
+        3. drawing and finalizing ui
+        4. reading and handling user input
+        5. after X loops: collecting unused directory objects
+        """
+
+        self.enter_dir(self.thistab.path)
+
+        gc_tick = 0
+
+        # for faster lookup:
+        ui = self.ui
+        throbber = ui.throbber
+        loader = self.loader
+        has_throbber = hasattr(ui, 'throbber')
+        zombies = self.run.zombies
+
+        ranger.api.hook_ready(self)
+
+        try:
+            while True:
+                loader.work()
+                if has_throbber:
+                    if loader.has_work():
+                        throbber(loader.status)
+                    else:
+                        throbber(remove=True)
+
+                ui.redraw()
+
+                ui.set_load_mode(not loader.paused and loader.has_work())
+
+                ui.handle_input()
+
+                if zombies:
+                    for zombie in tuple(zombies):
+                        if zombie.poll() is not None:
+                            zombies.remove(zombie)
+
+                gc_tick += 1
+                if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE:
+                    gc_tick = 0
+                    self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE)
+
+        except KeyboardInterrupt:
+            # this only happens in --debug mode. By default, interrupts
+            # are caught in curses_interrupt_handler
+            raise SystemExit
+
+        finally:
+            if ranger.arg.choosedir and self.thisdir and self.thisdir.path:
+                # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character
+                # '\udcf6' in position 42: surrogates not allowed
+                open(ranger.arg.choosedir, 'w').write(self.thisdir.path)
+            self.bookmarks.remember(self.thisdir)
+            self.bookmarks.save()
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 4437d028..6219293f 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -11,349 +11,349 @@ import os.path
 import sys
 import select
 try:
-	import chardet
-	HAVE_CHARDET = True
+    import chardet
+    HAVE_CHARDET = True
 except:
-	HAVE_CHARDET = False
+    HAVE_CHARDET = False
 
 class Loadable(object):
-	paused = False
-	progressbar_supported = False
-	def __init__(self, gen, descr):
-		self.load_generator = gen
-		self.description = descr
-		self.percent = 0
+    paused = False
+    progressbar_supported = False
+    def __init__(self, gen, descr):
+        self.load_generator = gen
+        self.description = descr
+        self.percent = 0
 
-	def get_description(self):
-		return self.description
+    def get_description(self):
+        return self.description
 
-	def pause(self):
-		self.paused = True
+    def pause(self):
+        self.paused = True
 
-	def unpause(self):
-		try:
-			del self.paused
-		except:
-			pass
+    def unpause(self):
+        try:
+            del self.paused
+        except:
+            pass
 
-	def destroy(self):
-		pass
+    def destroy(self):
+        pass
 
 
 class CopyLoader(Loadable, FileManagerAware):
-	progressbar_supported = True
-	def __init__(self, copy_buffer, do_cut=False, overwrite=False):
-		self.copy_buffer = tuple(copy_buffer)
-		self.do_cut = do_cut
-		self.original_copy_buffer = copy_buffer
-		self.original_path = self.fm.thistab.path
-		self.overwrite = overwrite
-		self.percent = 0
-		if self.copy_buffer:
-			self.one_file = self.copy_buffer[0]
-		Loadable.__init__(self, self.generate(), 'Calculating size...')
-
-	def _calculate_size(self, step):
-		from os.path import join
-		size = 0
-		stack = [f.path for f in self.copy_buffer]
-		while stack:
-			fname = stack.pop()
-			if os.path.isdir(fname):
-				stack.extend([join(fname, item) for item in os.listdir(fname)])
-			else:
-				try:
-					fstat = os.stat(fname)
-				except:
-					continue
-				size += max(step, math.ceil(fstat.st_size / step) * step)
-		return size
-
-	def generate(self):
-		from ranger.ext import shutil_generatorized as shutil_g
-		if self.copy_buffer:
-			# TODO: Don't calculate size when renaming (needs detection)
-			bytes_per_tick = shutil_g.BLOCK_SIZE
-			size = max(1, self._calculate_size(bytes_per_tick))
-			bar_tick = 100.0 / (float(size) / bytes_per_tick)
-			if self.do_cut:
-				self.original_copy_buffer.clear()
-				if len(self.copy_buffer) == 1:
-					self.description = "moving: " + self.one_file.path
-				else:
-					self.description = "moving files from: " + self.one_file.dirname
-				for f in self.copy_buffer:
-					for _ in shutil_g.move(src=f.path,
-							dst=self.original_path,
-							overwrite=self.overwrite):
-						self.percent += bar_tick
-						yield
-			else:
-				if len(self.copy_buffer) == 1:
-					self.description = "copying: " + self.one_file.path
-				else:
-					self.description = "copying files from: " + self.one_file.dirname
-				for f in self.copy_buffer:
-					if os.path.isdir(f.path):
-						for _ in shutil_g.copytree(src=f.path,
-								dst=os.path.join(self.original_path, f.basename),
-								symlinks=True,
-								overwrite=self.overwrite):
-							self.percent += bar_tick
-							yield
-					else:
-						for _ in shutil_g.copy2(f.path, self.original_path,
-								symlinks=True,
-								overwrite=self.overwrite):
-							self.percent += bar_tick
-							yield
-			cwd = self.fm.get_directory(self.original_path)
-			cwd.load_content()
+    progressbar_supported = True
+    def __init__(self, copy_buffer, do_cut=False, overwrite=False):
+        self.copy_buffer = tuple(copy_buffer)
+        self.do_cut = do_cut
+        self.original_copy_buffer = copy_buffer
+        self.original_path = self.fm.thistab.path
+        self.overwrite = overwrite
+        self.percent = 0
+        if self.copy_buffer:
+            self.one_file = self.copy_buffer[0]
+        Loadable.__init__(self, self.generate(), 'Calculating size...')
+
+    def _calculate_size(self, step):
+        from os.path import join
+        size = 0
+        stack = [f.path for f in self.copy_buffer]
+        while stack:
+            fname = stack.pop()
+            if os.path.isdir(fname):
+                stack.extend([join(fname, item) for item in os.listdir(fname)])
+            else:
+                try:
+                    fstat = os.stat(fname)
+                except:
+                    continue
+                size += max(step, math.ceil(fstat.st_size / step) * step)
+        return size
+
+    def generate(self):
+        from ranger.ext import shutil_generatorized as shutil_g
+        if self.copy_buffer:
+            # TODO: Don't calculate size when renaming (needs detection)
+            bytes_per_tick = shutil_g.BLOCK_SIZE
+            size = max(1, self._calculate_size(bytes_per_tick))
+            bar_tick = 100.0 / (float(size) / bytes_per_tick)
+            if self.do_cut:
+                self.original_copy_buffer.clear()
+                if len(self.copy_buffer) == 1:
+                    self.description = "moving: " + self.one_file.path
+                else:
+                    self.description = "moving files from: " + self.one_file.dirname
+                for f in self.copy_buffer:
+                    for _ in shutil_g.move(src=f.path,
+                            dst=self.original_path,
+                            overwrite=self.overwrite):
+                        self.percent += bar_tick
+                        yield
+            else:
+                if len(self.copy_buffer) == 1:
+                    self.description = "copying: " + self.one_file.path
+                else:
+                    self.description = "copying files from: " + self.one_file.dirname
+                for f in self.copy_buffer:
+                    if os.path.isdir(f.path):
+                        for _ in shutil_g.copytree(src=f.path,
+                                dst=os.path.join(self.original_path, f.basename),
+                                symlinks=True,
+                                overwrite=self.overwrite):
+                            self.percent += bar_tick
+                            yield
+                    else:
+                        for _ in shutil_g.copy2(f.path, self.original_path,
+                                symlinks=True,
+                                overwrite=self.overwrite):
+                            self.percent += bar_tick
+                            yield
+            cwd = self.fm.get_directory(self.original_path)
+            cwd.load_content()
 
 
 class CommandLoader(Loadable, SignalDispatcher, FileManagerAware):
-	"""
-	Run an external command with the loader.
-
-	Output from stderr will be reported.  Ensure that the process doesn't
-	ever ask for input, otherwise the loader will be blocked until this
-	object is removed from the queue (type ^C in ranger)
-	"""
-	finished = False
-	process = None
-	def __init__(self, args, descr, silent=False, read=False):
-		SignalDispatcher.__init__(self)
-		Loadable.__init__(self, self.generate(), descr)
-		self.args = args
-		self.silent = silent
-		self.read = read
-		self.stdout_buffer = ""
-
-	def generate(self):
-		null = open(os.devnull, 'r')
-		self.process = process = Popen(self.args,
-				stdout=PIPE, stderr=PIPE, stdin=null)
-		self.signal_emit('before', process=process, loader=self)
-		if self.silent and not self.read:
-			while process.poll() is None:
-				yield
-				sleep(0.03)
-		else:
-			py3 = sys.version >= '3'
-			selectlist = []
-			if self.read:
-				selectlist.append(process.stdout)
-			if not self.silent:
-				selectlist.append(process.stderr)
-			while process.poll() is None:
-				yield
-				try:
-					rd, _, __ = select.select(selectlist, [], [], 0.03)
-					if rd:
-						rd = rd[0]
-						if rd == process.stderr:
-							read = rd.readline()
-							if py3:
-								read = safeDecode(read)
-							if read:
-								self.fm.notify(read, bad=True)
-						elif rd == process.stdout:
-							read = rd.read(512)
-							if py3:
-								read = safeDecode(read)
-							if read:
-								self.stdout_buffer += read
-				except select.error:
-					sleep(0.03)
-			if not self.silent:
-				for l in process.stderr.readlines():
-					if py3:
-						l = safeDecode(l)
-					self.fm.notify(l, bad=True)
-			if self.read:
-				read = process.stdout.read()
-				if py3:
-					read = safeDecode(read)
-				self.stdout_buffer += read
-		null.close()
-		self.finished = True
-		self.signal_emit('after', process=process, loader=self)
-
-	def pause(self):
-		if not self.finished and not self.paused:
-			try:
-				self.process.send_signal(20)
-			except:
-				pass
-			Loadable.pause(self)
-			self.signal_emit('pause', process=self.process, loader=self)
-
-	def unpause(self):
-		if not self.finished and self.paused:
-			try:
-				self.process.send_signal(18)
-			except:
-				pass
-			Loadable.unpause(self)
-			self.signal_emit('unpause', process=self.process, loader=self)
-
-	def destroy(self):
-		self.signal_emit('destroy', process=self.process, loader=self)
-		if self.process:
-			self.process.kill()
+    """
+    Run an external command with the loader.
+
+    Output from stderr will be reported.  Ensure that the process doesn't
+    ever ask for input, otherwise the loader will be blocked until this
+    object is removed from the queue (type ^C in ranger)
+    """
+    finished = False
+    process = None
+    def __init__(self, args, descr, silent=False, read=False):
+        SignalDispatcher.__init__(self)
+        Loadable.__init__(self, self.generate(), descr)
+        self.args = args
+        self.silent = silent
+        self.read = read
+        self.stdout_buffer = ""
+
+    def generate(self):
+        null = open(os.devnull, 'r')
+        self.process = process = Popen(self.args,
+                stdout=PIPE, stderr=PIPE, stdin=null)
+        self.signal_emit('before', process=process, loader=self)
+        if self.silent and not self.read:
+            while process.poll() is None:
+                yield
+                sleep(0.03)
+        else:
+            py3 = sys.version >= '3'
+            selectlist = []
+            if self.read:
+                selectlist.append(process.stdout)
+            if not self.silent:
+                selectlist.append(process.stderr)
+            while process.poll() is None:
+                yield
+                try:
+                    rd, _, __ = select.select(selectlist, [], [], 0.03)
+                    if rd:
+                        rd = rd[0]
+                        if rd == process.stderr:
+                            read = rd.readline()
+                            if py3:
+                                read = safeDecode(read)
+                            if read:
+                                self.fm.notify(read, bad=True)
+                        elif rd == process.stdout:
+                            read = rd.read(512)
+                            if py3:
+                                read = safeDecode(read)
+                            if read:
+                                self.stdout_buffer += read
+                except select.error:
+                    sleep(0.03)
+            if not self.silent:
+                for l in process.stderr.readlines():
+                    if py3:
+                        l = safeDecode(l)
+                    self.fm.notify(l, bad=True)
+            if self.read:
+                read = process.stdout.read()
+                if py3:
+                    read = safeDecode(read)
+                self.stdout_buffer += read
+        null.close()
+        self.finished = True
+        self.signal_emit('after', process=process, loader=self)
+
+    def pause(self):
+        if not self.finished and not self.paused:
+            try:
+                self.process.send_signal(20)
+            except:
+                pass
+            Loadable.pause(self)
+            self.signal_emit('pause', process=self.process, loader=self)
+
+    def unpause(self):
+        if not self.finished and self.paused:
+            try:
+                self.process.send_signal(18)
+            except:
+                pass
+            Loadable.unpause(self)
+            self.signal_emit('unpause', process=self.process, loader=self)
+
+    def destroy(self):
+        self.signal_emit('destroy', process=self.process, loader=self)
+        if self.process:
+            self.process.kill()
 
 
 def safeDecode(string):
-	try:
-		return string.decode("utf-8")
-	except (UnicodeDecodeError):
-		if HAVE_CHARDET:
-			return string.decode(chardet.detect(string)["encoding"])
-		else:
-			return ""
+    try:
+        return string.decode("utf-8")
+    except (UnicodeDecodeError):
+        if HAVE_CHARDET:
+            return string.decode(chardet.detect(string)["encoding"])
+        else:
+            return ""
 
 
 class Loader(FileManagerAware):
-	seconds_of_work_time = 0.03
-	throbber_chars = r'/-\|'
-	throbber_paused = '#'
-	paused = False
-
-	def __init__(self):
-		self.queue = deque()
-		self.item = None
-		self.load_generator = None
-		self.throbber_status = 0
-		self.rotate()
-		self.old_item = None
-
-	def rotate(self):
-		"""Rotate the throbber"""
-		# TODO: move all throbber logic to UI
-		self.throbber_status = \
-			(self.throbber_status + 1) % len(self.throbber_chars)
-		self.status = self.throbber_chars[self.throbber_status]
-
-	def add(self, obj):
-		"""
-		Add an object to the queue.
-		It should have a load_generator method.
-		"""
-		while obj in self.queue:
-			self.queue.remove(obj)
-		self.queue.appendleft(obj)
-		if self.paused:
-			obj.pause()
-		else:
-			obj.unpause()
-
-	def move(self, _from, to):
-		try:
-			item = self.queue[_from]
-		except IndexError:
-			return
-
-		del self.queue[_from]
-
-		if to == 0:
-			self.queue.appendleft(item)
-			if _from != 0:
-				self.queue[1].pause()
-		elif to == -1:
-			self.queue.append(item)
-		else:
-			raise NotImplementedError
-
-	def remove(self, item=None, index=None):
-		if item is not None and index is None:
-			for i, test in enumerate(self.queue):
-				if test == item:
-					index = i 
-					break
-			else:
-				return
-
-		if index is not None:
-			if item is None:
-				item = self.queue[index]
-			if hasattr(item, 'unload'):
-				item.unload()
-			item.destroy()
-			del self.queue[index]
-			if item.progressbar_supported:
-				self.fm.ui.status.request_redraw()
-
-	def pause(self, state):
-		"""
-		Change the pause-state to 1 (pause), 0 (no pause) or -1 (toggle)
-		"""
-		if state == -1:
-			state = not self.paused
-		elif state == self.paused:
-			return
-
-		self.paused = state
-
-		if not self.queue:
-			return
-
-		if state:
-			self.queue[0].pause()
-		else:
-			self.queue[0].unpause()
-
-	def work(self):
-		"""
-		Load items from the queue if there are any.
-		Stop after approximately self.seconds_of_work_time.
-		"""
-		if self.paused:
-			self.status = self.throbber_paused
-			return
-
-		while True:
-			# get the first item with a proper load_generator
-			try:
-				item = self.queue[0]
-				if item.load_generator is None:
-					self.queue.popleft()
-				else:
-					break
-			except IndexError:
-				return
-
-		item.unpause()
-
-		self.rotate()
-		if item != self.old_item:
-			if self.old_item:
-				self.old_item.pause()
-			self.old_item = item
-		item.unpause()
-
-		end_time = time() + self.seconds_of_work_time
-
-		try:
-			while time() < end_time:
-				next(item.load_generator)
-			if item.progressbar_supported:
-				self.fm.ui.status.request_redraw()
-		except StopIteration:
-			self._remove_current_process(item)
-		except Exception as err:
-			self.fm.notify(err)
-			self._remove_current_process(item)
-
-	def _remove_current_process(self, item):
-		item.load_generator = None
-		self.queue.remove(item)
-		if item.progressbar_supported:
-			self.fm.ui.status.request_redraw()
-
-	def has_work(self):
-		"""Is there anything to load?"""
-		return bool(self.queue)
-
-	def destroy(self):
-		while self.queue:
-			self.queue.pop().destroy()
+    seconds_of_work_time = 0.03
+    throbber_chars = r'/-\|'
+    throbber_paused = '#'
+    paused = False
+
+    def __init__(self):
+        self.queue = deque()
+        self.item = None
+        self.load_generator = None
+        self.throbber_status = 0
+        self.rotate()
+        self.old_item = None
+
+    def rotate(self):
+        """Rotate the throbber"""
+        # TODO: move all throbber logic to UI
+        self.throbber_status = \
+            (self.throbber_status + 1) % len(self.throbber_chars)
+        self.status = self.throbber_chars[self.throbber_status]
+
+    def add(self, obj):
+        """
+        Add an object to the queue.
+        It should have a load_generator method.
+        """
+        while obj in self.queue:
+            self.queue.remove(obj)
+        self.queue.appendleft(obj)
+        if self.paused:
+            obj.pause()
+        else:
+            obj.unpause()
+
+    def move(self, _from, to):
+        try:
+            item = self.queue[_from]
+        except IndexError:
+            return
+
+        del self.queue[_from]
+
+        if to == 0:
+            self.queue.appendleft(item)
+            if _from != 0:
+                self.queue[1].pause()
+        elif to == -1:
+            self.queue.append(item)
+        else:
+            raise NotImplementedError
+
+    def remove(self, item=None, index=None):
+        if item is not None and index is None:
+            for i, test in enumerate(self.queue):
+                if test == item:
+                    index = i 
+                    break
+            else:
+                return
+
+        if index is not None:
+            if item is None:
+                item = self.queue[index]
+            if hasattr(item, 'unload'):
+                item.unload()
+            item.destroy()
+            del self.queue[index]
+            if item.progressbar_supported:
+                self.fm.ui.status.request_redraw()
+
+    def pause(self, state):
+        """
+        Change the pause-state to 1 (pause), 0 (no pause) or -1 (toggle)
+        """
+        if state == -1:
+            state = not self.paused
+        elif state == self.paused:
+            return
+
+        self.paused = state
+
+        if not self.queue:
+            return
+
+        if state:
+            self.queue[0].pause()
+        else:
+            self.queue[0].unpause()
+
+    def work(self):
+        """
+        Load items from the queue if there are any.
+        Stop after approximately self.seconds_of_work_time.
+        """
+        if self.paused:
+            self.status = self.throbber_paused
+            return
+
+        while True:
+            # get the first item with a proper load_generator
+            try:
+                item = self.queue[0]
+                if item.load_generator is None:
+                    self.queue.popleft()
+                else:
+                    break
+            except IndexError:
+                return
+
+        item.unpause()
+
+        self.rotate()
+        if item != self.old_item:
+            if self.old_item:
+                self.old_item.pause()
+            self.old_item = item
+        item.unpause()
+
+        end_time = time() + self.seconds_of_work_time
+
+        try:
+            while time() < end_time:
+                next(item.load_generator)
+            if item.progressbar_supported:
+                self.fm.ui.status.request_redraw()
+        except StopIteration:
+            self._remove_current_process(item)
+        except Exception as err:
+            self.fm.notify(err)
+            self._remove_current_process(item)
+
+    def _remove_current_process(self, item):
+        item.load_generator = None
+        self.queue.remove(item)
+        if item.progressbar_supported:
+            self.fm.ui.status.request_redraw()
+
+    def has_work(self):
+        """Is there anything to load?"""
+        return bool(self.queue)
+
+    def destroy(self):
+        while self.queue:
+            self.queue.pop().destroy()
diff --git a/ranger/core/main.py b/ranger/core/main.py
index 226d93cd..1bd6f8da 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -11,294 +11,294 @@ import sys
 load_default_config = True
 
 def main():
-	"""initialize objects and run the filemanager"""
-	import locale
-	import ranger.api
-	from ranger.core.shared import FileManagerAware, SettingsAware
-	from ranger.core.fm import FM
-
-	if not sys.stdin.isatty():
-		sys.stderr.write("Error: Must run ranger from terminal\n")
-		raise SystemExit(1)
-
-	try:
-		locale.setlocale(locale.LC_ALL, '')
-	except:
-		print("Warning: Unable to set locale.  Expect encoding problems.")
-
-	# so that programs can know that ranger spawned them:
-	level = 'RANGER_LEVEL'
-	if level in os.environ and os.environ[level].isdigit():
-		os.environ[level] = str(int(os.environ[level]) + 1)
-	else:
-		os.environ[level] = '1'
-
-	if not 'SHELL' in os.environ:
-		os.environ['SHELL'] = 'bash'
-
-	ranger.arg = arg = parse_arguments()
-	if arg.copy_config is not None:
-		fm = FM()
-		fm.copy_config_files(arg.copy_config)
-		return 1 if arg.fail_unless_cd else 0
-	if arg.list_tagged_files:
-		fm = FM()
-		try:
-			f = open(fm.confpath('tagged'), 'r')
-		except:
-			pass
-		else:
-			for line in f.readlines():
-				if len(line) > 2 and line[1] == ':':
-					if line[0] in arg.list_tagged_files:
-						sys.stdout.write(line[2:])
-				elif len(line) > 0 and '*' in arg.list_tagged_files:
-					sys.stdout.write(line)
-		return 1 if arg.fail_unless_cd else 0
-
-	SettingsAware._setup(clean=arg.clean)
-
-	if arg.selectfile:
-		arg.selectfile = os.path.abspath(arg.selectfile)
-		arg.targets.insert(0, os.path.dirname(arg.selectfile))
-
-	targets = arg.targets or ['.']
-	target = targets[0]
-	if arg.targets:
-		if target.startswith('file://'):
-			target = target[7:]
-		if not os.access(target, os.F_OK):
-			print("File or directory doesn't exist: %s" % target)
-			return 1
-		elif os.path.isfile(target):
-			def print_function(string):
-				print(string)
-			from ranger.ext.rifle import Rifle
-			fm = FM()
-			if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')):
-				rifleconf = fm.confpath('rifle.conf')
-			else:
-				rifleconf = fm.relpath('config/rifle.conf')
-			rifle = Rifle(rifleconf)
-			rifle.reload_config()
-			rifle.execute(targets, number=ranger.arg.mode, flags=ranger.arg.flags)
-			return 1 if arg.fail_unless_cd else 0
-
-	crash_traceback = None
-	try:
-		# Initialize objects
-		fm = FM(paths=targets)
-		FileManagerAware.fm = fm
-		load_settings(fm, arg.clean)
-
-		if arg.list_unused_keys:
-			from ranger.ext.keybinding_parser import (special_keys,
-					reversed_special_keys)
-			maps = fm.ui.keymaps['browser']
-			for key in sorted(special_keys.values(), key=lambda x: str(x)):
-				if key not in maps:
-					print("<%s>" % reversed_special_keys[key])
-			for key in range(33, 127):
-				if key not in maps:
-					print(chr(key))
-			return 1 if arg.fail_unless_cd else 0
-
-		if fm.username == 'root':
-			fm.settings.preview_files = False
-			fm.settings.use_preview_script = False
-		if not arg.debug:
-			from ranger.ext import curses_interrupt_handler
-			curses_interrupt_handler.install_interrupt_handler()
-
-		# Run the file manager
-		fm.initialize()
-		ranger.api.hook_init(fm)
-		fm.ui.initialize()
-
-		if arg.selectfile:
-			fm.select_file(arg.selectfile)
-
-		if arg.cmd:
-			for command in arg.cmd:
-				fm.execute_console(command)
-
-		if ranger.arg.profile:
-			import cProfile
-			import pstats
-			profile = None
-			ranger.__fm = fm
-			cProfile.run('ranger.__fm.loop()', '/tmp/ranger_profile')
-			profile = pstats.Stats('/tmp/ranger_profile', stream=sys.stderr)
-		else:
-			fm.loop()
-	except Exception:
-		import traceback
-		crash_traceback = traceback.format_exc()
-	except SystemExit as error:
-		return error.args[0]
-	finally:
-		if crash_traceback:
-			try:
-				filepath = fm.thisfile.path if fm.thisfile else "None"
-			except:
-				filepath = "None"
-		try:
-			fm.ui.destroy()
-		except (AttributeError, NameError):
-			pass
-		if ranger.arg.profile and profile:
-			profile.strip_dirs().sort_stats('cumulative').print_callees()
-		if crash_traceback:
-			print("ranger version: %s, executed with python %s" %
-					(ranger.__version__, sys.version.split()[0]))
-			print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale()))
-			try:
-				print("Current file: %s" % filepath)
-			except:
-				pass
-			print(crash_traceback)
-			print("ranger crashed.  " \
-				"Please report this traceback at:")
-			print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem")
-			return 1
-		return 0
+    """initialize objects and run the filemanager"""
+    import locale
+    import ranger.api
+    from ranger.core.shared import FileManagerAware, SettingsAware
+    from ranger.core.fm import FM
+
+    if not sys.stdin.isatty():
+        sys.stderr.write("Error: Must run ranger from terminal\n")
+        raise SystemExit(1)
+
+    try:
+        locale.setlocale(locale.LC_ALL, '')
+    except:
+        print("Warning: Unable to set locale.  Expect encoding problems.")
+
+    # so that programs can know that ranger spawned them:
+    level = 'RANGER_LEVEL'
+    if level in os.environ and os.environ[level].isdigit():
+        os.environ[level] = str(int(os.environ[level]) + 1)
+    else:
+        os.environ[level] = '1'
+
+    if not 'SHELL' in os.environ:
+        os.environ['SHELL'] = 'bash'
+
+    ranger.arg = arg = parse_arguments()
+    if arg.copy_config is not None:
+        fm = FM()
+        fm.copy_config_files(arg.copy_config)
+        return 1 if arg.fail_unless_cd else 0
+    if arg.list_tagged_files:
+        fm = FM()
+        try:
+            f = open(fm.confpath('tagged'), 'r')
+        except:
+            pass
+        else:
+            for line in f.readlines():
+                if len(line) > 2 and line[1] == ':':
+                    if line[0] in arg.list_tagged_files:
+                        sys.stdout.write(line[2:])
+                elif len(line) > 0 and '*' in arg.list_tagged_files:
+                    sys.stdout.write(line)
+        return 1 if arg.fail_unless_cd else 0
+
+    SettingsAware._setup(clean=arg.clean)
+
+    if arg.selectfile:
+        arg.selectfile = os.path.abspath(arg.selectfile)
+        arg.targets.insert(0, os.path.dirname(arg.selectfile))
+
+    targets = arg.targets or ['.']
+    target = targets[0]
+    if arg.targets:
+        if target.startswith('file://'):
+            target = target[7:]
+        if not os.access(target, os.F_OK):
+            print("File or directory doesn't exist: %s" % target)
+            return 1
+        elif os.path.isfile(target):
+            def print_function(string):
+                print(string)
+            from ranger.ext.rifle import Rifle
+            fm = FM()
+            if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')):
+                rifleconf = fm.confpath('rifle.conf')
+            else:
+                rifleconf = fm.relpath('config/rifle.conf')
+            rifle = Rifle(rifleconf)
+            rifle.reload_config()
+            rifle.execute(targets, number=ranger.arg.mode, flags=ranger.arg.flags)
+            return 1 if arg.fail_unless_cd else 0
+
+    crash_traceback = None
+    try:
+        # Initialize objects
+        fm = FM(paths=targets)
+        FileManagerAware.fm = fm
+        load_settings(fm, arg.clean)
+
+        if arg.list_unused_keys:
+            from ranger.ext.keybinding_parser import (special_keys,
+                    reversed_special_keys)
+            maps = fm.ui.keymaps['browser']
+            for key in sorted(special_keys.values(), key=lambda x: str(x)):
+                if key not in maps:
+                    print("<%s>" % reversed_special_keys[key])
+            for key in range(33, 127):
+                if key not in maps:
+                    print(chr(key))
+            return 1 if arg.fail_unless_cd else 0
+
+        if fm.username == 'root':
+            fm.settings.preview_files = False
+            fm.settings.use_preview_script = False
+        if not arg.debug:
+            from ranger.ext import curses_interrupt_handler
+            curses_interrupt_handler.install_interrupt_handler()
+
+        # Run the file manager
+        fm.initialize()
+        ranger.api.hook_init(fm)
+        fm.ui.initialize()
+
+        if arg.selectfile:
+            fm.select_file(arg.selectfile)
+
+        if arg.cmd:
+            for command in arg.cmd:
+                fm.execute_console(command)
+
+        if ranger.arg.profile:
+            import cProfile
+            import pstats
+            profile = None
+            ranger.__fm = fm
+            cProfile.run('ranger.__fm.loop()', '/tmp/ranger_profile')
+            profile = pstats.Stats('/tmp/ranger_profile', stream=sys.stderr)
+        else:
+            fm.loop()
+    except Exception:
+        import traceback
+        crash_traceback = traceback.format_exc()
+    except SystemExit as error:
+        return error.args[0]
+    finally:
+        if crash_traceback:
+            try:
+                filepath = fm.thisfile.path if fm.thisfile else "None"
+            except:
+                filepath = "None"
+        try:
+            fm.ui.destroy()
+        except (AttributeError, NameError):
+            pass
+        if ranger.arg.profile and profile:
+            profile.strip_dirs().sort_stats('cumulative').print_callees()
+        if crash_traceback:
+            print("ranger version: %s, executed with python %s" %
+                    (ranger.__version__, sys.version.split()[0]))
+            print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale()))
+            try:
+                print("Current file: %s" % filepath)
+            except:
+                pass
+            print(crash_traceback)
+            print("ranger crashed.  " \
+                "Please report this traceback at:")
+            print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem")
+            return 1
+        return 0
 
 
 def parse_arguments():
-	"""Parse the program arguments"""
-	from optparse import OptionParser
-	from os.path import expanduser
-	from ranger import CONFDIR, USAGE, VERSION
-	from ranger.ext.openstruct import OpenStruct
-
-	if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
-		default_confdir = os.environ['XDG_CONFIG_HOME'] + '/ranger'
-	else:
-		default_confdir = CONFDIR
-
-	parser = OptionParser(usage=USAGE, version=VERSION)
-
-	parser.add_option('-d', '--debug', action='store_true',
-			help="activate debug mode")
-	parser.add_option('-c', '--clean', action='store_true',
-			help="don't touch/require any config files. ")
-	parser.add_option('--copy-config', type='string', metavar='which',
-			help="copy the default configs to the local config directory. "
-			"Possible values: all, rc, rifle, commands, scope")
-	parser.add_option('--fail-unless-cd', action='store_true',
-			help="experimental: return the exit code 1 if ranger is" \
-					"used to run a file (with `ranger filename`)")
-	parser.add_option('-r', '--confdir', type='string',
-			metavar='dir', default=default_confdir,
-			help="the configuration directory. (%default)")
-	parser.add_option('-m', '--mode', type='int', default=0, metavar='n',
-			help="if a filename is supplied, run it with this mode")
-	parser.add_option('-f', '--flags', type='string', default='',
-			metavar='string',
-			help="if a filename is supplied, run it with these flags.")
-	parser.add_option('--choosefile', type='string', metavar='TARGET',
-			help="Makes ranger act like a file chooser. When opening "
-			"a file, it will quit and write the name of the selected "
-			"file to TARGET.")
-	parser.add_option('--choosefiles', type='string', metavar='TARGET',
-			help="Makes ranger act like a file chooser for multiple files "
-			"at once. When opening a file, it will quit and write the name "
-			"of all selected files to TARGET.")
-	parser.add_option('--choosedir', type='string', metavar='TARGET',
-			help="Makes ranger act like a directory chooser. When ranger quits"
-			", it will write the name of the last visited directory to TARGET")
-	parser.add_option('--list-unused-keys', action='store_true',
-			help="List common keys which are not bound to any action.")
-	parser.add_option('--selectfile', type='string', metavar='filepath',
-			help="Open ranger with supplied file selected.")
-	parser.add_option('--list-tagged-files', type='string', default=None,
-			metavar='tag',
-			help="List all files which are tagged with the given tag, default: *")
-	parser.add_option('--profile', action='store_true',
-			help="Print statistics of CPU usage on exit.")
-	parser.add_option('--cmd', action='append', type='string', metavar='COMMAND',
-			help="Execute COMMAND after the configuration has been read. "
-			"Use this option multiple times to run multiple commands.")
-
-	options, positional = parser.parse_args()
-	arg = OpenStruct(options.__dict__, targets=positional)
-	arg.confdir = expanduser(arg.confdir)
-
-	return arg
+    """Parse the program arguments"""
+    from optparse import OptionParser
+    from os.path import expanduser
+    from ranger import CONFDIR, USAGE, VERSION
+    from ranger.ext.openstruct import OpenStruct
+
+    if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
+        default_confdir = os.environ['XDG_CONFIG_HOME'] + '/ranger'
+    else:
+        default_confdir = CONFDIR
+
+    parser = OptionParser(usage=USAGE, version=VERSION)
+
+    parser.add_option('-d', '--debug', action='store_true',
+            help="activate debug mode")
+    parser.add_option('-c', '--clean', action='store_true',
+            help="don't touch/require any config files. ")
+    parser.add_option('--copy-config', type='string', metavar='which',
+            help="copy the default configs to the local config directory. "
+            "Possible values: all, rc, rifle, commands, scope")
+    parser.add_option('--fail-unless-cd', action='store_true',
+            help="experimental: return the exit code 1 if ranger is" \
+                    "used to run a file (with `ranger filename`)")
+    parser.add_option('-r', '--confdir', type='string',
+            metavar='dir', default=default_confdir,
+            help="the configuration directory. (%default)")
+    parser.add_option('-m', '--mode', type='int', default=0, metavar='n',
+            help="if a filename is supplied, run it with this mode")
+    parser.add_option('-f', '--flags', type='string', default='',
+            metavar='string',
+            help="if a filename is supplied, run it with these flags.")
+    parser.add_option('--choosefile', type='string', metavar='TARGET',
+            help="Makes ranger act like a file chooser. When opening "
+            "a file, it will quit and write the name of the selected "
+            "file to TARGET.")
+    parser.add_option('--choosefiles', type='string', metavar='TARGET',
+            help="Makes ranger act like a file chooser for multiple files "
+            "at once. When opening a file, it will quit and write the name "
+            "of all selected files to TARGET.")
+    parser.add_option('--choosedir', type='string', metavar='TARGET',
+            help="Makes ranger act like a directory chooser. When ranger quits"
+            ", it will write the name of the last visited directory to TARGET")
+    parser.add_option('--list-unused-keys', action='store_true',
+            help="List common keys which are not bound to any action.")
+    parser.add_option('--selectfile', type='string', metavar='filepath',
+            help="Open ranger with supplied file selected.")
+    parser.add_option('--list-tagged-files', type='string', default=None,
+            metavar='tag',
+            help="List all files which are tagged with the given tag, default: *")
+    parser.add_option('--profile', action='store_true',
+            help="Print statistics of CPU usage on exit.")
+    parser.add_option('--cmd', action='append', type='string', metavar='COMMAND',
+            help="Execute COMMAND after the configuration has been read. "
+            "Use this option multiple times to run multiple commands.")
+
+    options, positional = parser.parse_args()
+    arg = OpenStruct(options.__dict__, targets=positional)
+    arg.confdir = expanduser(arg.confdir)
+
+    return arg
 
 
 def load_settings(fm, clean):
-	global load_default_config
-	from ranger.core.actions import Actions
-	import ranger.core.shared
-	import ranger.api.commands
-	from ranger.config import commands
-
-	# Load default commands
-	fm.commands = ranger.api.commands.CommandContainer()
-	exclude = ['settings']
-	include = [name for name in dir(Actions) if name not in exclude]
-	fm.commands.load_commands_from_object(fm, include)
-	fm.commands.load_commands_from_module(commands)
-
-	if not clean:
-		allow_access_to_confdir(ranger.arg.confdir, True)
-
-		# Load custom commands
-		if os.path.exists(fm.confpath('commands.py')):
-			try:
-				import commands
-				fm.commands.load_commands_from_module(commands)
-			except ImportError:
-				pass
-
-		allow_access_to_confdir(ranger.arg.confdir, False)
-
-		# Load rc.conf
-		custom_conf = fm.confpath('rc.conf')
-		default_conf = fm.relpath('config', 'rc.conf')
-
-		if load_default_config:
-			fm.source(default_conf)
-		if os.access(custom_conf, os.R_OK):
-			fm.source(custom_conf)
-
-		allow_access_to_confdir(ranger.arg.confdir, True)
-
-		# XXX Load plugins (experimental)
-		try:
-			plugindir = fm.confpath('plugins')
-			plugins = [p[:-3] for p in os.listdir(plugindir) \
-					if p.endswith('.py') and not p.startswith('_')]
-		except:
-			pass
-		else:
-			if not os.path.exists(fm.confpath('plugins', '__init__.py')):
-				f = open(fm.confpath('plugins', '__init__.py'), 'w')
-				f.close()
-
-			ranger.fm = fm
-			for plugin in sorted(plugins):
-				try:
-					module = __import__('plugins', fromlist=[plugin])
-					fm.log.append("Loaded plugin '%s'." % module)
-				except Exception as e:
-					fm.log.append("Error in plugin '%s'" % plugin)
-					import traceback
-					for line in traceback.format_exception_only(type(e), e):
-						fm.log.append(line)
-			ranger.fm = None
-
-		# COMPAT: Load the outdated options.py
-		# options.py[oc] are deliberately ignored
-		if os.path.exists(fm.confpath("options.py")):  
-			module = __import__('options')
-			from ranger.container.settingobject import ALLOWED_SETTINGS
-			for setting in ALLOWED_SETTINGS:
-				if hasattr(module, setting):
-					fm.settings[setting] = getattr(module, setting)
-
-			sys.stderr.write(
+    global load_default_config
+    from ranger.core.actions import Actions
+    import ranger.core.shared
+    import ranger.api.commands
+    from ranger.config import commands
+
+    # Load default commands
+    fm.commands = ranger.api.commands.CommandContainer()
+    exclude = ['settings']
+    include = [name for name in dir(Actions) if name not in exclude]
+    fm.commands.load_commands_from_object(fm, include)
+    fm.commands.load_commands_from_module(commands)
+
+    if not clean:
+        allow_access_to_confdir(ranger.arg.confdir, True)
+
+        # Load custom commands
+        if os.path.exists(fm.confpath('commands.py')):
+            try:
+                import commands
+                fm.commands.load_commands_from_module(commands)
+            except ImportError:
+                pass
+
+        allow_access_to_confdir(ranger.arg.confdir, False)
+
+        # Load rc.conf
+        custom_conf = fm.confpath('rc.conf')
+        default_conf = fm.relpath('config', 'rc.conf')
+
+        if load_default_config:
+            fm.source(default_conf)
+        if os.access(custom_conf, os.R_OK):
+            fm.source(custom_conf)
+
+        allow_access_to_confdir(ranger.arg.confdir, True)
+
+        # XXX Load plugins (experimental)
+        try:
+            plugindir = fm.confpath('plugins')
+            plugins = [p[:-3] for p in os.listdir(plugindir) \
+                    if p.endswith('.py') and not p.startswith('_')]
+        except:
+            pass
+        else:
+            if not os.path.exists(fm.confpath('plugins', '__init__.py')):
+                f = open(fm.confpath('plugins', '__init__.py'), 'w')
+                f.close()
+
+            ranger.fm = fm
+            for plugin in sorted(plugins):
+                try:
+                    module = __import__('plugins', fromlist=[plugin])
+                    fm.log.append("Loaded plugin '%s'." % module)
+                except Exception as e:
+                    fm.log.append("Error in plugin '%s'" % plugin)
+                    import traceback
+                    for line in traceback.format_exception_only(type(e), e):
+                        fm.log.append(line)
+            ranger.fm = None
+
+        # COMPAT: Load the outdated options.py
+        # options.py[oc] are deliberately ignored
+        if os.path.exists(fm.confpath("options.py")):  
+            module = __import__('options')
+            from ranger.container.settingobject import ALLOWED_SETTINGS
+            for setting in ALLOWED_SETTINGS:
+                if hasattr(module, setting):
+                    fm.settings[setting] = getattr(module, setting)
+
+            sys.stderr.write(
 """******************************
 Warning: The configuration file 'options.py' is deprecated.
 Please move all settings to the file 'rc.conf', converting lines like
@@ -310,27 +310,27 @@ copy & paste it to a .py file in ~/.config/ranger/plugins/.
 Remove the options.py or discard stderr to get rid of this warning.
 ******************************\n""")
 
-		allow_access_to_confdir(ranger.arg.confdir, False)
-	else:
-		fm.source(fm.relpath('config', 'rc.conf'))
+        allow_access_to_confdir(ranger.arg.confdir, False)
+    else:
+        fm.source(fm.relpath('config', 'rc.conf'))
 
 
 def allow_access_to_confdir(confdir, allow):
-	import sys
-	from errno import EEXIST
-
-	if allow:
-		try:
-			os.makedirs(confdir)
-		except OSError as err:
-			if err.errno != EEXIST:  # EEXIST means it already exists
-				print("This configuration directory could not be created:")
-				print(confdir)
-				print("To run ranger without the need for configuration")
-				print("files, use the --clean option.")
-				raise SystemExit()
-		if not confdir in sys.path:
-			sys.path[0:0] = [confdir]
-	else:
-		if sys.path[0] == confdir:
-			del sys.path[0]
+    import sys
+    from errno import EEXIST
+
+    if allow:
+        try:
+            os.makedirs(confdir)
+        except OSError as err:
+            if err.errno != EEXIST:  # EEXIST means it already exists
+                print("This configuration directory could not be created:")
+                print(confdir)
+                print("To run ranger without the need for configuration")
+                print("files, use the --clean option.")
+                raise SystemExit()
+        if not confdir in sys.path:
+            sys.path[0:0] = [confdir]
+    else:
+        if sys.path[0] == confdir:
+            del sys.path[0]
diff --git a/ranger/core/runner.py b/ranger/core/runner.py
index f652c15f..223bc1b6 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -36,202 +36,202 @@ ALLOWED_FLAGS = 'cfrtCFRT'
 
 
 def press_enter():
-	"""Wait for an ENTER-press"""
-	sys.stdout.write("Press ENTER to continue")
-	try:
-		waitfnc = raw_input
-	except NameError:
-		# "raw_input" not available in python3
-		waitfnc = input
-	waitfnc()
+    """Wait for an ENTER-press"""
+    sys.stdout.write("Press ENTER to continue")
+    try:
+        waitfnc = raw_input
+    except NameError:
+        # "raw_input" not available in python3
+        waitfnc = input
+    waitfnc()
 
 
 class Context(object):
-	"""
-	A context object contains data on how to run a process.
-
-	The attributes are:
-	action -- a string with a command or a list of arguments for
-		the Popen call.
-	app -- the name of the app function. ("vim" for app_vim.)
-		app is used to get an action if the user didn't specify one.
-	mode -- a number, mainly used in determining the action in app_xyz()
-	flags -- a string with flags which change the way programs are run
-	files -- a list containing files, mainly used in app_xyz
-	file -- an arbitrary file from that list (or None)
-	fm -- the filemanager instance
-	wait -- boolean, wait for the end or execute programs in parallel?
-	popen_kws -- keyword arguments which are directly passed to Popen
-	"""
-
-	def __init__(self, **keywords):
-		self.__dict__ = keywords
-
-	@property
-	def filepaths(self):
-		try:
-			return [f.path for f in self.files]
-		except:
-			return []
-
-	def __iter__(self):
-		"""Iterate over file paths"""
-		for item in self.filepaths:
-			yield item
-
-	def squash_flags(self):
-		"""Remove duplicates and lowercase counterparts of uppercase flags"""
-		for flag in self.flags:
-			if ord(flag) <= 90:
-				bad = flag + flag.lower()
-				self.flags = ''.join(c for c in self.flags if c not in bad)
+    """
+    A context object contains data on how to run a process.
+
+    The attributes are:
+    action -- a string with a command or a list of arguments for
+        the Popen call.
+    app -- the name of the app function. ("vim" for app_vim.)
+        app is used to get an action if the user didn't specify one.
+    mode -- a number, mainly used in determining the action in app_xyz()
+    flags -- a string with flags which change the way programs are run
+    files -- a list containing files, mainly used in app_xyz
+    file -- an arbitrary file from that list (or None)
+    fm -- the filemanager instance
+    wait -- boolean, wait for the end or execute programs in parallel?
+    popen_kws -- keyword arguments which are directly passed to Popen
+    """
+
+    def __init__(self, **keywords):
+        self.__dict__ = keywords
+
+    @property
+    def filepaths(self):
+        try:
+            return [f.path for f in self.files]
+        except:
+            return []
+
+    def __iter__(self):
+        """Iterate over file paths"""
+        for item in self.filepaths:
+            yield item
+
+    def squash_flags(self):
+        """Remove duplicates and lowercase counterparts of uppercase flags"""
+        for flag in self.flags:
+            if ord(flag) <= 90:
+                bad = flag + flag.lower()
+                self.flags = ''.join(c for c in self.flags if c not in bad)
 
 
 class Runner(object):
-	def __init__(self, ui=None, logfunc=None, fm=None):
-		self.ui = ui
-		self.fm = fm
-		self.logfunc = logfunc
-		self.zombies = set()
-
-	def _log(self, text):
-		try:
-			self.logfunc(text)
-		except TypeError:
-			pass
-		return False
-
-	def _activate_ui(self, boolean):
-		if self.ui is not None:
-			if boolean:
-				try: self.ui.initialize()
-				except: self._log("Failed to initialize UI")
-			else:
-				try: self.ui.suspend()
-				except: self._log("Failed to suspend UI")
-
-	def __call__(self, action=None, try_app_first=False,
-			app='default', files=None, mode=0,
-			flags='', wait=True, **popen_kws):
-		"""
-		Run the application in the way specified by the options.
-
-		Returns False if nothing can be done, None if there was an error,
-		otherwise the process object returned by Popen().
-
-		This function tries to find an action if none is defined.
-		"""
-
-		# Find an action if none was supplied by
-		# creating a Context object and passing it to
-		# an Application object.
-
-		context = Context(app=app, files=files, mode=mode, fm=self.fm,
-				flags=flags, wait=wait, popen_kws=popen_kws,
-				file=files and files[0] or None)
-
-		if action is None:
-			return self._log("No way of determining the action!")
-
-		# Preconditions
-
-		context.squash_flags()
-		popen_kws = context.popen_kws  # shortcut
-
-		toggle_ui = True
-		pipe_output = False
-		wait_for_enter = False
-		devnull = None
-
-		if 'shell' not in popen_kws:
-			popen_kws['shell'] = isinstance(action, str)
-		if 'stdout' not in popen_kws:
-			popen_kws['stdout'] = sys.stdout
-		if 'stderr' not in popen_kws:
-			popen_kws['stderr'] = sys.stderr
-
-		# Evaluate the flags to determine keywords
-		# for Popen() and other variables
-
-		if 'p' in context.flags:
-			popen_kws['stdout'] = PIPE
-			popen_kws['stderr'] = PIPE
-			toggle_ui = False
-			pipe_output = True
-			context.wait = False
-		if 's' in context.flags:
-			devnull_writable = open(os.devnull, 'w')
-			devnull_readable = open(os.devnull, 'r')
-			for key in ('stdout', 'stderr'):
-				popen_kws[key] = devnull_writable
-			popen_kws['stdin'] = devnull_readable
-		if 'f' in context.flags:
-			toggle_ui = False
-			context.wait = False
-		if 'w' in context.flags:
-			if not pipe_output and context.wait: # <-- sanity check
-				wait_for_enter = True
-		if 'r' in context.flags:
-			# TODO: make 'r' flag work with pipes
-			if 'sudo' not in get_executables():
-				return self._log("Can not run with 'r' flag, sudo is not installed!")
-			f_flag = ('f' in context.flags)
-			if isinstance(action, str):
-				action = 'sudo ' + (f_flag and '-b ' or '') + action
-			else:
-				action = ['sudo'] + (f_flag and ['-b'] or []) + action
-			toggle_ui = True
-			context.wait = True
-		if 't' in context.flags:
-			if 'DISPLAY' not in os.environ:
-				return self._log("Can not run with 't' flag, no display found!")
-			term = os.environ.get('TERMCMD', os.environ.get('TERM'))
-			if term not in get_executables():
-				term = 'x-terminal-emulator'
-			if term not in get_executables():
-				term = 'xterm'
-			if isinstance(action, str):
-				action = term + ' -e ' + action
-			else:
-				action = [term, '-e'] + action
-			toggle_ui = False
-			context.wait = False
-
-		popen_kws['args'] = action
-		# Finally, run it
-
-		if toggle_ui:
-			self._activate_ui(False)
-		try:
-			error = None
-			process = None
-			self.fm.signal_emit('runner.execute.before',
-					popen_kws=popen_kws, context=context)
-			try:
-				if 'f' in context.flags:
-					# This can fail and return False if os.fork() is not
-					# supported, but we assume it is, since curses is used.
-					Popen_forked(**popen_kws)
-				else:
-					process = Popen(**popen_kws)
-			except Exception as e:
-				error = e
-				self._log("Failed to run: %s\n%s" % (str(action), str(e)))
-			else:
-				if context.wait:
-					process.wait()
-				elif process:
-					self.zombies.add(process)
-				if wait_for_enter:
-					press_enter()
-		finally:
-			self.fm.signal_emit('runner.execute.after',
-					popen_kws=popen_kws, context=context, error=error)
-			if devnull:
-				devnull.close()
-			if toggle_ui:
-				self._activate_ui(True)
-			if pipe_output and process:
-				return self(action='less', app='pager', try_app_first=True,
-						stdin=process.stdout)
-			return process
+    def __init__(self, ui=None, logfunc=None, fm=None):
+        self.ui = ui
+        self.fm = fm
+        self.logfunc = logfunc
+        self.zombies = set()
+
+    def _log(self, text):
+        try:
+            self.logfunc(text)
+        except TypeError:
+            pass
+        return False
+
+    def _activate_ui(self, boolean):
+        if self.ui is not None:
+            if boolean:
+                try: self.ui.initialize()
+                except: self._log("Failed to initialize UI")
+            else:
+                try: self.ui.suspend()
+                except: self._log("Failed to suspend UI")
+
+    def __call__(self, action=None, try_app_first=False,
+            app='default', files=None, mode=0,
+            flags='', wait=True, **popen_kws):
+        """
+        Run the application in the way specified by the options.
+
+        Returns False if nothing can be done, None if there was an error,
+        otherwise the process object returned by Popen().
+
+        This function tries to find an action if none is defined.
+        """
+
+        # Find an action if none was supplied by
+        # creating a Context object and passing it to
+        # an Application object.
+
+        context = Context(app=app, files=files, mode=mode, fm=self.fm,
+                flags=flags, wait=wait, popen_kws=popen_kws,
+                file=files and files[0] or None)
+
+        if action is None:
+            return self._log("No way of determining the action!")
+
+        # Preconditions
+
+        context.squash_flags()
+        popen_kws = context.popen_kws  # shortcut
+
+        toggle_ui = True
+        pipe_output = False
+        wait_for_enter = False
+        devnull = None
+
+        if 'shell' not in popen_kws:
+            popen_kws['shell'] = isinstance(action, str)
+        if 'stdout' not in popen_kws:
+            popen_kws['stdout'] = sys.stdout
+        if 'stderr' not in popen_kws:
+            popen_kws['stderr'] = sys.stderr
+
+        # Evaluate the flags to determine keywords
+        # for Popen() and other variables
+
+        if 'p' in context.flags:
+            popen_kws['stdout'] = PIPE
+            popen_kws['stderr'] = PIPE
+            toggle_ui = False
+            pipe_output = True
+            context.wait = False
+        if 's' in context.flags:
+            devnull_writable = open(os.devnull, 'w')
+            devnull_readable = open(os.devnull, 'r')
+            for key in ('stdout', 'stderr'):
+                popen_kws[key] = devnull_writable
+            popen_kws['stdin'] = devnull_readable
+        if 'f' in context.flags:
+            toggle_ui = False
+            context.wait = False
+        if 'w' in context.flags:
+            if not pipe_output and context.wait: # <-- sanity check
+                wait_for_enter = True
+        if 'r' in context.flags:
+            # TODO: make 'r' flag work with pipes
+            if 'sudo' not in get_executables():
+                return self._log("Can not run with 'r' flag, sudo is not installed!")
+            f_flag = ('f' in context.flags)
+            if isinstance(action, str):
+                action = 'sudo ' + (f_flag and '-b ' or '') + action
+            else:
+                action = ['sudo'] + (f_flag and ['-b'] or []) + action
+            toggle_ui = True
+            context.wait = True
+        if 't' in context.flags:
+            if 'DISPLAY' not in os.environ:
+                return self._log("Can not run with 't' flag, no display found!")
+            term = os.environ.get('TERMCMD', os.environ.get('TERM'))
+            if term not in get_executables():
+                term = 'x-terminal-emulator'
+            if term not in get_executables():
+                term = 'xterm'
+            if isinstance(action, str):
+                action = term + ' -e ' + action
+            else:
+                action = [term, '-e'] + action
+            toggle_ui = False
+            context.wait = False
+
+        popen_kws['args'] = action
+        # Finally, run it
+
+        if toggle_ui:
+            self._activate_ui(False)
+        try:
+            error = None
+            process = None
+            self.fm.signal_emit('runner.execute.before',
+                    popen_kws=popen_kws, context=context)
+            try:
+                if 'f' in context.flags:
+                    # This can fail and return False if os.fork() is not
+                    # supported, but we assume it is, since curses is used.
+                    Popen_forked(**popen_kws)
+                else:
+                    process = Popen(**popen_kws)
+            except Exception as e:
+                error = e
+                self._log("Failed to run: %s\n%s" % (str(action), str(e)))
+            else:
+                if context.wait:
+                    process.wait()
+                elif process:
+                    self.zombies.add(process)
+                if wait_for_enter:
+                    press_enter()
+        finally:
+            self.fm.signal_emit('runner.execute.after',
+                    popen_kws=popen_kws, context=context, error=error)
+            if devnull:
+                devnull.close()
+            if toggle_ui:
+                self._activate_ui(True)
+            if pipe_output and process:
+                return self(action='less', app='pager', try_app_first=True,
+                        stdin=process.stdout)
+            return process
diff --git a/ranger/core/shared.py b/ranger/core/shared.py
index d7bf90b6..7b679bb0 100644
--- a/ranger/core/shared.py
+++ b/ranger/core/shared.py
@@ -8,64 +8,64 @@ from ranger.ext.lazy_property import lazy_property
 import os.path
 
 class Awareness(object):
-	pass
+    pass
 
 class EnvironmentAware(Awareness):
-	# This creates an instance implicitly, mainly for unit tests
-	@lazy_property
-	def env(self):
-		from ranger.core.environment import Environment
-		return Environment(".")
+    # This creates an instance implicitly, mainly for unit tests
+    @lazy_property
+    def env(self):
+        from ranger.core.environment import Environment
+        return Environment(".")
 
 class FileManagerAware(Awareness):
-	# This creates an instance implicitly, mainly for unit tests
-	@lazy_property
-	def fm(self):
-		from ranger.core.fm import FM
-		return FM()
+    # This creates an instance implicitly, mainly for unit tests
+    @lazy_property
+    def fm(self):
+        from ranger.core.fm import FM
+        return FM()
 
 class SettingsAware(Awareness):
-	# This creates an instance implicitly, mainly for unit tests
-	@lazy_property
-	def settings(self):
-		from ranger.ext.openstruct import OpenStruct
-		return OpenStruct()
+    # This creates an instance implicitly, mainly for unit tests
+    @lazy_property
+    def settings(self):
+        from ranger.ext.openstruct import OpenStruct
+        return OpenStruct()
 
-	@staticmethod
-	def _setup(clean=True):
-		from ranger.container.settingobject import SettingObject
-		import ranger
-		import sys
-		settings = SettingObject()
+    @staticmethod
+    def _setup(clean=True):
+        from ranger.container.settingobject import SettingObject
+        import ranger
+        import sys
+        settings = SettingObject()
 
-		from ranger.gui.colorscheme import _colorscheme_name_to_class
-		settings.signal_bind('setopt.colorscheme',
-				_colorscheme_name_to_class, priority=1)
+        from ranger.gui.colorscheme import _colorscheme_name_to_class
+        settings.signal_bind('setopt.colorscheme',
+                _colorscheme_name_to_class, priority=1)
 
-		settings.signal_bind('setopt.column_ratios',
-				_sanitize_setting_column_ratios, priority=1)
+        settings.signal_bind('setopt.column_ratios',
+                _sanitize_setting_column_ratios, priority=1)
 
-		def after_setting_preview_script(signal):
-			if isinstance(signal.value, str):
-				signal.value = os.path.expanduser(signal.value)
-				if not os.path.exists(signal.value):
-					signal.value = None
-		settings.signal_bind('setopt.preview_script',
-				after_setting_preview_script, priority=1)
-		def after_setting_use_preview_script(signal):
-			if signal.fm.settings.preview_script is None and signal.value \
-					and not signal.previous:
-				signal.fm.notify("Preview script undefined or not found!",
-						bad=True)
-		settings.signal_bind('setopt.use_preview_script',
-				after_setting_use_preview_script, priority=1)
+        def after_setting_preview_script(signal):
+            if isinstance(signal.value, str):
+                signal.value = os.path.expanduser(signal.value)
+                if not os.path.exists(signal.value):
+                    signal.value = None
+        settings.signal_bind('setopt.preview_script',
+                after_setting_preview_script, priority=1)
+        def after_setting_use_preview_script(signal):
+            if signal.fm.settings.preview_script is None and signal.value \
+                    and not signal.previous:
+                signal.fm.notify("Preview script undefined or not found!",
+                        bad=True)
+        settings.signal_bind('setopt.use_preview_script',
+                after_setting_use_preview_script, priority=1)
 
-		SettingsAware.settings = settings
+        SettingsAware.settings = settings
 
 def _sanitize_setting_column_ratios(signal):
-	if isinstance(signal.value, tuple):
-		signal.value = list(signal.value)
-	if not isinstance(signal.value, list) or len(signal.value) < 2:
-		signal.value = [1,1]
-	else:
-		signal.value = [int(i) if str(i).isdigit() else 1 for i in signal.value]
+    if isinstance(signal.value, tuple):
+        signal.value = list(signal.value)
+    if not isinstance(signal.value, list) or len(signal.value) < 2:
+        signal.value = [1,1]
+    else:
+        signal.value = [int(i) if str(i).isdigit() else 1 for i in signal.value]
diff --git a/ranger/core/tab.py b/ranger/core/tab.py
index 443b2037..ae9d9073 100644
--- a/ranger/core/tab.py
+++ b/ranger/core/tab.py
@@ -10,156 +10,156 @@ from ranger.core.shared import FileManagerAware, SettingsAware
 from ranger.ext.signals import SignalDispatcher
 
 class Tab(FileManagerAware, SettingsAware):
-	def __init__(self, path):
-		self.thisdir = None  # Current Working Directory
-		self._thisfile = None  # Current File
-		self.history = History(self.settings.max_history_size, unique=False)
-		self.last_search = None
-		self.pointer = 0
-		self.path = abspath(expanduser(path))
-		self.pathway = ()
-		# NOTE: in the line below, weak=True works only in python3.  In python2,
-		# weak references are not equal to the original object when tested with
-		# "==", and this breaks _set_thisfile_from_signal and _on_tab_change.
-		self.fm.signal_bind('move', self._set_thisfile_from_signal, priority=0.1,
-				weak=(sys.version > '3'))
-		self.fm.signal_bind('tab.change', self._on_tab_change,
-				weak=(sys.version > '3'))
-
-	def _set_thisfile_from_signal(self, signal):
-		if self == signal.tab:
-			self._thisfile = signal.new
-			if self == self.fm.thistab:
-				self.pointer = self.thisdir.pointer
-
-	def _on_tab_change(self, signal):
-		if self == signal.new and self.thisdir:
-			# restore the pointer whenever this tab is reopened
-			self.thisdir.pointer = self.pointer
-			self.thisdir.correct_pointer()
-
-	def _set_thisfile(self, value):
-		if value is not self._thisfile:
-			previous = self._thisfile
-			self.fm.signal_emit('move', previous=previous, new=value, tab=self)
-
-	def _get_thisfile(self):
-		return self._thisfile
-
-	thisfile = property(_get_thisfile, _set_thisfile)
-
-	def at_level(self, level):
-		"""
-		Returns the FileSystemObject at the given level.
-		level >0 => previews
-		level 0 => current file/directory
-		level <0 => parent directories
-		"""
-		if level <= 0:
-			try:
-				return self.pathway[level - 1]
-			except IndexError:
-				return None
-		else:
-			directory = self.thisdir
-			for i in range(level):
-				if directory is None:
-					return None
-				if directory.is_directory:
-					directory = directory.pointed_obj
-				else:
-					return None
-			return directory
-
-	def get_selection(self):
-		if self.thisdir:
-			if self.thisdir.marked_items:
-				return self.thisdir.get_selection()
-			elif self._thisfile:
-				return [self._thisfile]
-			else:
-				return []
-		return set()
-
-	def assign_cursor_positions_for_subdirs(self):
-		"""Assign correct cursor positions for subdirectories"""
-		last_path = None
-		for path in reversed(self.pathway):
-			if last_path is None:
-				last_path = path
-				continue
-
-			path.move_to_obj(last_path)
-			last_path = path
-
-	def ensure_correct_pointer(self):
-		if self.thisdir:
-			self.thisdir.correct_pointer()
-
-	def history_go(self, relative):
-		"""Move relative in history"""
-		if self.history:
-			self.history.move(relative).go(history=False)
-
-	def inherit_history(self, other_history):
-		self.history.rebase(other_history)
-
-	def enter_dir(self, path, history = True):
-		"""Enter given path"""
-		# TODO: Ensure that there is always a self.thisdir
-		if path is None: return
-		path = str(path)
-
-		previous = self.thisdir
-
-		# get the absolute path
-		path = normpath(join(self.path, expanduser(path)))
-
-		if not isdir(path):
-			return False
-		new_thisdir = self.fm.get_directory(path)
-
-		try:
-			os.chdir(path)
-		except:
-			return True
-		self.path = path
-		self.thisdir = new_thisdir
-
-		self.thisdir.load_content_if_outdated()
-
-		# build the pathway, a tuple of directory objects which lie
-		# on the path to the current directory.
-		if path == '/':
-			self.pathway = (self.fm.get_directory('/'), )
-		else:
-			pathway = []
-			currentpath = '/'
-			for dir in path.split('/'):
-				currentpath = join(currentpath, dir)
-				pathway.append(self.fm.get_directory(currentpath))
-			self.pathway = tuple(pathway)
-
-		self.assign_cursor_positions_for_subdirs()
-
-		# set the current file.
-		self.thisdir.sort_directories_first = self.fm.settings.sort_directories_first
-		self.thisdir.sort_reverse = self.fm.settings.sort_reverse
-		self.thisdir.sort_if_outdated()
-		if previous and previous.path != path:
-			self.thisfile = self.thisdir.pointed_obj
-		else:
-			# This avoids setting self.pointer (through the 'move' signal) and
-			# is required so that you can use enter_dir when switching tabs
-			# without messing up the pointer.
-			self._thisfile = self.thisdir.pointed_obj
-
-		if history:
-			self.history.add(new_thisdir)
-
-		self.fm.signal_emit('cd', previous=previous, new=self.thisdir)
-
-		return True
-
-	def __repr__(self):
-		return "<Tab '%s'>" % self.thisdir
+    def __init__(self, path):
+        self.thisdir = None  # Current Working Directory
+        self._thisfile = None  # Current File
+        self.history = History(self.settings.max_history_size, unique=False)
+        self.last_search = None
+        self.pointer = 0
+        self.path = abspath(expanduser(path))
+        self.pathway = ()
+        # NOTE: in the line below, weak=True works only in python3.  In python2,
+        # weak references are not equal to the original object when tested with
+        # "==", and this breaks _set_thisfile_from_signal and _on_tab_change.
+        self.fm.signal_bind('move', self._set_thisfile_from_signal, priority=0.1,
+                weak=(sys.version > '3'))
+        self.fm.signal_bind('tab.change', self._on_tab_change,
+                weak=(sys.version > '3'))
+
+    def _set_thisfile_from_signal(self, signal):
+        if self == signal.tab:
+            self._thisfile = signal.new
+            if self == self.fm.thistab:
+                self.pointer = self.thisdir.pointer
+
+    def _on_tab_change(self, signal):
+        if self == signal.new and self.thisdir:
+            # restore the pointer whenever this tab is reopened
+            self.thisdir.pointer = self.pointer
+            self.thisdir.correct_pointer()
+
+    def _set_thisfile(self, value):
+        if value is not self._thisfile:
+            previous = self._thisfile
+            self.fm.signal_emit('move', previous=previous, new=value, tab=self)
+
+    def _get_thisfile(self):
+        return self._thisfile
+
+    thisfile = property(_get_thisfile, _set_thisfile)
+
+    def at_level(self, level):
+        """
+        Returns the FileSystemObject at the given level.
+        level >0 => previews
+        level 0 => current file/directory
+        level <0 => parent directories
+        """
+        if level <= 0:
+            try:
+                return self.pathway[level - 1]
+            except IndexError:
+                return None
+        else:
+            directory = self.thisdir
+            for i in range(level):
+                if directory is None:
+                    return None
+                if directory.is_directory:
+                    directory = directory.pointed_obj
+                else:
+                    return None
+            return directory
+
+    def get_selection(self):
+        if self.thisdir:
+            if self.thisdir.marked_items:
+                return self.thisdir.get_selection()
+            elif self._thisfile:
+                return [self._thisfile]
+            else:
+                return []
+        return set()
+
+    def assign_cursor_positions_for_subdirs(self):
+        """Assign correct cursor positions for subdirectories"""
+        last_path = None
+        for path in reversed(self.pathway):
+            if last_path is None:
+                last_path = path
+                continue
+
+            path.move_to_obj(last_path)
+            last_path = path
+
+    def ensure_correct_pointer(self):
+        if self.thisdir:
+            self.thisdir.correct_pointer()
+
+    def history_go(self, relative):
+        """Move relative in history"""
+        if self.history:
+            self.history.move(relative).go(history=False)
+
+    def inherit_history(self, other_history):
+        self.history.rebase(other_history)
+
+    def enter_dir(self, path, history = True):
+        """Enter given path"""
+        # TODO: Ensure that there is always a self.thisdir
+        if path is None: return
+        path = str(path)
+
+        previous = self.thisdir
+
+        # get the absolute path
+        path = normpath(join(self.path, expanduser(path)))
+
+        if not isdir(path):
+            return False
+        new_thisdir = self.fm.get_directory(path)
+
+        try:
+            os.chdir(path)
+        except:
+            return True
+        self.path = path
+        self.thisdir = new_thisdir
+
+        self.thisdir.load_content_if_outdated()
+
+        # build the pathway, a tuple of directory objects which lie
+        # on the path to the current directory.
+        if path == '/':
+            self.pathway = (self.fm.get_directory('/'), )
+        else:
+            pathway = []
+            currentpath = '/'
+            for dir in path.split('/'):
+                currentpath = join(currentpath, dir)
+                pathway.append(self.fm.get_directory(currentpath))
+            self.pathway = tuple(pathway)
+
+        self.assign_cursor_positions_for_subdirs()
+
+        # set the current file.
+        self.thisdir.sort_directories_first = self.fm.settings.sort_directories_first
+        self.thisdir.sort_reverse = self.fm.settings.sort_reverse
+        self.thisdir.sort_if_outdated()
+        if previous and previous.path != path:
+            self.thisfile = self.thisdir.pointed_obj
+        else:
+            # This avoids setting self.pointer (through the 'move' signal) and
+            # is required so that you can use enter_dir when switching tabs
+            # without messing up the pointer.
+            self._thisfile = self.thisdir.pointed_obj
+
+        if history:
+            self.history.add(new_thisdir)
+
+        self.fm.signal_emit('cd', previous=previous, new=self.thisdir)
+
+        return True
+
+    def __repr__(self):
+        return "<Tab '%s'>" % self.thisdir
diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py
index 7665c99f..a027ecc2 100644
--- a/ranger/ext/accumulator.py
+++ b/ranger/ext/accumulator.py
@@ -4,93 +4,93 @@
 from ranger.ext.direction import Direction
 
 class Accumulator(object):
-	def __init__(self):
-		self.pointer = 0
-		self.pointed_obj = None
-
-	def move(self, narg=None, **keywords):
-		direction = Direction(keywords)
-		lst = self.get_list()
-		if not lst:
-			return self.pointer
-		pointer = direction.move(
-				direction=direction.down(),
-				maximum=len(lst),
-				override=narg,
-				pagesize=self.get_height(),
-				current=self.pointer)
-		self.pointer = pointer
-		self.correct_pointer()
-		return pointer
-
-	def move_to_obj(self, arg, attr=None):
-		if not arg:
-			return
-
-		lst = self.get_list()
-
-		if not lst:
-			return
-
-		do_get_attr = isinstance(attr, str)
-
-		good = arg
-		if do_get_attr:
-			try:
-				good = getattr(arg, attr)
-			except (TypeError, AttributeError):
-				pass
-
-		for obj, i in zip(lst, range(len(lst))):
-			if do_get_attr:
-				try:
-					test = getattr(obj, attr)
-				except AttributeError:
-					continue
-			else:
-				test = obj
-
-			if test == good:
-				self.move(to=i)
-				return True
-
-		return self.move(to=self.pointer)
-
-	# XXX Is this still necessary?  move() ensures correct pointer position
-	def correct_pointer(self):
-		lst = self.get_list()
-
-		if not lst:
-			self.pointer = 0
-			self.pointed_obj = None
-
-		else:
-			i = self.pointer
-
-			if i is None:
-				i = 0
-			if i >= len(lst):
-				i = len(lst) - 1
-			if i < 0:
-				i = 0
-
-			self.pointer = i
-			self.pointed_obj = lst[i]
-
-	def pointer_is_synced(self):
-		lst = self.get_list()
-		try:
-			return lst[self.pointer] == self.pointed_obj
-		except (IndexError, KeyError):
-			return False
-
-	def sync_index(self, **kw):
-		self.move_to_obj(self.pointed_obj, **kw)
-
-	def get_list(self):
-		"""OVERRIDE THIS"""
-		return []
-
-	def get_height(self):
-		"""OVERRIDE THIS"""
-		return 25
+    def __init__(self):
+        self.pointer = 0
+        self.pointed_obj = None
+
+    def move(self, narg=None, **keywords):
+        direction = Direction(keywords)
+        lst = self.get_list()
+        if not lst:
+            return self.pointer
+        pointer = direction.move(
+                direction=direction.down(),
+                maximum=len(lst),
+                override=narg,
+                pagesize=self.get_height(),
+                current=self.pointer)
+        self.pointer = pointer
+        self.correct_pointer()
+        return pointer
+
+    def move_to_obj(self, arg, attr=None):
+        if not arg:
+            return
+
+        lst = self.get_list()
+
+        if not lst:
+            return
+
+        do_get_attr = isinstance(attr, str)
+
+        good = arg
+        if do_get_attr:
+            try:
+                good = getattr(arg, attr)
+            except (TypeError, AttributeError):
+                pass
+
+        for obj, i in zip(lst, range(len(lst))):
+            if do_get_attr:
+                try:
+                    test = getattr(obj, attr)
+                except AttributeError:
+                    continue
+            else:
+                test = obj
+
+            if test == good:
+                self.move(to=i)
+                return True
+
+        return self.move(to=self.pointer)
+
+    # XXX Is this still necessary?  move() ensures correct pointer position
+    def correct_pointer(self):
+        lst = self.get_list()
+
+        if not lst:
+            self.pointer = 0
+            self.pointed_obj = None
+
+        else:
+            i = self.pointer
+
+            if i is None:
+                i = 0
+            if i >= len(lst):
+                i = len(lst) - 1
+            if i < 0:
+                i = 0
+
+            self.pointer = i
+            self.pointed_obj = lst[i]
+
+    def pointer_is_synced(self):
+        lst = self.get_list()
+        try:
+            return lst[self.pointer] == self.pointed_obj
+        except (IndexError, KeyError):
+            return False
+
+    def sync_index(self, **kw):
+        self.move_to_obj(self.pointed_obj, **kw)
+
+    def get_list(self):
+        """OVERRIDE THIS"""
+        return []
+
+    def get_height(self):
+        """OVERRIDE THIS"""
+        return 25
diff --git a/ranger/ext/curses_interrupt_handler.py b/ranger/ext/curses_interrupt_handler.py
index eb86e0a9..50caebf7 100644
--- a/ranger/ext/curses_interrupt_handler.py
+++ b/ranger/ext/curses_interrupt_handler.py
@@ -13,27 +13,27 @@ import signal
 _do_catch_interrupt = True
 
 def catch_interrupt(boolean=True):
-	"""Should interrupts be caught and simulate a ^C press in curses?"""
-	global _do_catch_interrupt
-	old_value = _do_catch_interrupt
-	_do_catch_interrupt = bool(boolean)
-	return old_value
+    """Should interrupts be caught and simulate a ^C press in curses?"""
+    global _do_catch_interrupt
+    old_value = _do_catch_interrupt
+    _do_catch_interrupt = bool(boolean)
+    return old_value
 
 # The handler which will be used in signal.signal()
 def _interrupt_handler(a1, a2):
-	global _do_catch_interrupt
-	# if a keyboard-interrupt occurs...
-	if _do_catch_interrupt:
-		# push a Ctrl+C (ascii value 3) to the curses getch stack
-		curses.ungetch(3)
-	else:
-		# use the default handler
-		signal.default_int_handler(a1, a2)
+    global _do_catch_interrupt
+    # if a keyboard-interrupt occurs...
+    if _do_catch_interrupt:
+        # push a Ctrl+C (ascii value 3) to the curses getch stack
+        curses.ungetch(3)
+    else:
+        # use the default handler
+        signal.default_int_handler(a1, a2)
 
 def install_interrupt_handler():
-	"""Install the custom interrupt_handler"""
-	signal.signal(signal.SIGINT, _interrupt_handler)
+    """Install the custom interrupt_handler"""
+    signal.signal(signal.SIGINT, _interrupt_handler)
 
 def restore_interrupt_handler():
-	"""Restore the default_int_handler"""
-	signal.signal(signal.SIGINT, signal.default_int_handler)
+    """Restore the default_int_handler"""
+    signal.signal(signal.SIGINT, signal.default_int_handler)
diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py
index 742f54da..eb359afb 100644
--- a/ranger/ext/direction.py
+++ b/ranger/ext/direction.py
@@ -20,122 +20,122 @@ False
 """
 
 class Direction(dict):
-	def __init__(self, dictionary=None, **keywords):
-		if dictionary is not None:
-			dict.__init__(self, dictionary)
-		else:
-			dict.__init__(self, keywords)
-		if 'to' in self:
-			self['down'] = self['to']
-			self['absolute'] = True
-
-	def copy(self):
-		return Direction(**self)
-
-	def _get_bool(self, first, second, fallback=None):
-		try: return self[first]
-		except:
-			try: return not self[second]
-			except: return fallback
-
-	def _get_direction(self, first, second, fallback=0):
-		try: return self[first]
-		except:
-			try: return -self[second]
-			except: return fallback
-
-	def up(self):
-		return -Direction.down(self)
-
-	def down(self):
-		return Direction._get_direction(self, 'down', 'up')
-
-	def right(self):
-		return Direction._get_direction(self, 'right', 'left')
-
-	def absolute(self):
-		return Direction._get_bool(self, 'absolute', 'relative')
-
-	def left(self):
-		return -Direction.right(self)
-
-	def relative(self):
-		return not Direction.absolute(self)
-
-	def vertical_direction(self):
-		down = Direction.down(self)
-		return (down > 0) - (down < 0)
-
-	def horizontal_direction(self):
-		right = Direction.right(self)
-		return (right > 0) - (right < 0)
-
-	def vertical(self):
-		return set(self) & set(['up', 'down'])
-
-	def horizontal(self):
-		return set(self) & set(['left', 'right'])
-
-	def pages(self):
-		return 'pages' in self and self['pages']
-
-	def percentage(self):
-		return 'percentage' in self and self['percentage']
-
-	def multiply(self, n):
-		for key in ('up', 'right', 'down', 'left'):
-			try:
-				self[key] *= n
-			except:
-				pass
-
-	def set(self, n):
-		for key in ('up', 'right', 'down', 'left'):
-			if key in self:
-				self[key] = n
-
-	def move(self, direction, override=None, minimum=0, maximum=9999,
-			current=0, pagesize=1, offset=0):
-		"""
-		Calculates the new position in a given boundary.
-
-		Example:
-		>>> d = Direction(pages=True)
-		>>> d.move(direction=3)
-		3
-		>>> d.move(direction=3, current=2)
-		5
-		>>> d.move(direction=3, pagesize=5)
-		15
-		>>> # Note: we start to count at zero.
-		>>> d.move(direction=3, pagesize=5, maximum=10)
-		9
-		>>> d.move(direction=9, override=2)
-		18
-		"""
-		pos = direction
-		if override is not None:
-			if self.absolute():
-				pos = override
-			else:
-				pos *= override
-		if self.pages():
-			pos *= pagesize
-		elif self.percentage():
-			pos *= maximum / 100.0
-		if self.absolute():
-			if pos < minimum:
-				pos += maximum
-		else:
-			pos += current
-		return int(max(min(pos, maximum + offset - 1), minimum))
-
-	def select(self, lst, current, pagesize, override=None, offset=1):
-		dest = self.move(direction=self.down(), override=override,
-			current=current, pagesize=pagesize, minimum=0, maximum=len(lst))
-		selection = lst[min(current, dest):max(current, dest) + offset]
-		return dest + offset - 1, selection
+    def __init__(self, dictionary=None, **keywords):
+        if dictionary is not None:
+            dict.__init__(self, dictionary)
+        else:
+            dict.__init__(self, keywords)
+        if 'to' in self:
+            self['down'] = self['to']
+            self['absolute'] = True
+
+    def copy(self):
+        return Direction(**self)
+
+    def _get_bool(self, first, second, fallback=None):
+        try: return self[first]
+        except:
+            try: return not self[second]
+            except: return fallback
+
+    def _get_direction(self, first, second, fallback=0):
+        try: return self[first]
+        except:
+            try: return -self[second]
+            except: return fallback
+
+    def up(self):
+        return -Direction.down(self)
+
+    def down(self):
+        return Direction._get_direction(self, 'down', 'up')
+
+    def right(self):
+        return Direction._get_direction(self, 'right', 'left')
+
+    def absolute(self):
+        return Direction._get_bool(self, 'absolute', 'relative')
+
+    def left(self):
+        return -Direction.right(self)
+
+    def relative(self):
+        return not Direction.absolute(self)
+
+    def vertical_direction(self):
+        down = Direction.down(self)
+        return (down > 0) - (down < 0)
+
+    def horizontal_direction(self):
+        right = Direction.right(self)
+        return (right > 0) - (right < 0)
+
+    def vertical(self):
+        return set(self) & set(['up', 'down'])
+
+    def horizontal(self):
+        return set(self) & set(['left', 'right'])
+
+    def pages(self):
+        return 'pages' in self and self['pages']
+
+    def percentage(self):
+        return 'percentage' in self and self['percentage']
+
+    def multiply(self, n):
+        for key in ('up', 'right', 'down', 'left'):
+            try:
+                self[key] *= n
+            except:
+                pass
+
+    def set(self, n):
+        for key in ('up', 'right', 'down', 'left'):
+            if key in self:
+                self[key] = n
+
+    def move(self, direction, override=None, minimum=0, maximum=9999,
+            current=0, pagesize=1, offset=0):
+        """
+        Calculates the new position in a given boundary.
+
+        Example:
+        >>> d = Direction(pages=True)
+        >>> d.move(direction=3)
+        3
+        >>> d.move(direction=3, current=2)
+        5
+        >>> d.move(direction=3, pagesize=5)
+        15
+        >>> # Note: we start to count at zero.
+        >>> d.move(direction=3, pagesize=5, maximum=10)
+        9
+        >>> d.move(direction=9, override=2)
+        18
+        """
+        pos = direction
+        if override is not None:
+            if self.absolute():
+                pos = override
+            else:
+                pos *= override
+        if self.pages():
+            pos *= pagesize
+        elif self.percentage():
+            pos *= maximum / 100.0
+        if self.absolute():
+            if pos < minimum:
+                pos += maximum
+        else:
+            pos += current
+        return int(max(min(pos, maximum + offset - 1), minimum))
+
+    def select(self, lst, current, pagesize, override=None, offset=1):
+        dest = self.move(direction=self.down(), override=override,
+            current=current, pagesize=pagesize, minimum=0, maximum=len(lst))
+        selection = lst[min(current, dest):max(current, dest) + offset]
+        return dest + offset - 1, selection
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/ext/get_executables.py b/ranger/ext/get_executables.py
index 0c01ffd6..cf0d2a15 100644
--- a/ranger/ext/get_executables.py
+++ b/ranger/ext/get_executables.py
@@ -10,41 +10,41 @@ _cached_executables = None
 
 
 def get_executables():
-	"""
-	Return all executable files in $PATH. Cached version.
-	"""
-	global _cached_executables
-	if _cached_executables is None:
-		_cached_executables = get_executables_uncached()
-	return _cached_executables
+    """
+    Return all executable files in $PATH. Cached version.
+    """
+    global _cached_executables
+    if _cached_executables is None:
+        _cached_executables = get_executables_uncached()
+    return _cached_executables
 
 
 def get_executables_uncached(*paths):
-	"""
-	Return all executable files in each of the given directories.
-
-	Looks in $PATH by default.
-	"""
-	if not paths:
-		try:
-			pathstring = environ['PATH']
-		except KeyError:
-			return ()
-		paths = unique(pathstring.split(':'))
-
-	executables = set()
-	for path in paths:
-		try:
-			content = listdir(path)
-		except:
-			continue
-		for item in content:
-			abspath = path + '/' + item
-			try:
-				filestat = stat(abspath)
-			except:
-				continue
-			if filestat.st_mode & (S_IXOTH | S_IFREG):
-				executables.add(item)
-	return executables
+    """
+    Return all executable files in each of the given directories.
+
+    Looks in $PATH by default.
+    """
+    if not paths:
+        try:
+            pathstring = environ['PATH']
+        except KeyError:
+            return ()
+        paths = unique(pathstring.split(':'))
+
+    executables = set()
+    for path in paths:
+        try:
+            content = listdir(path)
+        except:
+            continue
+        for item in content:
+            abspath = path + '/' + item
+            try:
+                filestat = stat(abspath)
+            except:
+                continue
+            if filestat.st_mode & (S_IXOTH | S_IFREG):
+                executables.add(item)
+    return executables
 
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index 9667421a..0e3f3d4b 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -2,46 +2,46 @@
 # This software is distributed under the terms of the GNU GPL version 3.
 
 def human_readable(byte, separator=' '):
-	"""
-	Convert a large number of bytes to an easily readable format.
+    """
+    Convert a large number of bytes to an easily readable format.
 
-	>>> human_readable(54)
-	'54 B'
-	>>> human_readable(1500)
-	'1.46 K'
-	>>> human_readable(2 ** 20 * 1023)
-	'1023 M'
-	"""
+    >>> human_readable(54)
+    '54 B'
+    >>> human_readable(1500)
+    '1.46 K'
+    >>> human_readable(2 ** 20 * 1023)
+    '1023 M'
+    """
 
-	# I know this can be written much shorter, but this long version
-	# performs much better than what I had before.  If you attempt to
-	# shorten this code, take performance into consideration.
-	if byte <= 0:
-		return '0'
-	if byte < 2**10:
-		return '%d%sB'   % (byte, separator)
-	if byte < 2**10 * 999:
-		return '%.3g%sK' % (byte / 2**10.0, separator)
-	if byte < 2**20:
-		return '%.4g%sK' % (byte / 2**10.0, separator)
-	if byte < 2**20 * 999:
-		return '%.3g%sM' % (byte / 2**20.0, separator)
-	if byte < 2**30:
-		return '%.4g%sM' % (byte / 2**20.0, separator)
-	if byte < 2**30 * 999:
-		return '%.3g%sG' % (byte / 2**30.0, separator)
-	if byte < 2**40:
-		return '%.4g%sG' % (byte / 2**30.0, separator)
-	if byte < 2**40 * 999:
-		return '%.3g%sT' % (byte / 2**40.0, separator)
-	if byte < 2**50:
-		return '%.4g%sT' % (byte / 2**40.0, separator)
-	if byte < 2**50 * 999:
-		return '%.3g%sP' % (byte / 2**50.0, separator)
-	if byte < 2**60:
-		return '%.4g%sP' % (byte / 2**50.0, separator)
-	return '>9000'
+    # I know this can be written much shorter, but this long version
+    # performs much better than what I had before.  If you attempt to
+    # shorten this code, take performance into consideration.
+    if byte <= 0:
+        return '0'
+    if byte < 2**10:
+        return '%d%sB'   % (byte, separator)
+    if byte < 2**10 * 999:
+        return '%.3g%sK' % (byte / 2**10.0, separator)
+    if byte < 2**20:
+        return '%.4g%sK' % (byte / 2**10.0, separator)
+    if byte < 2**20 * 999:
+        return '%.3g%sM' % (byte / 2**20.0, separator)
+    if byte < 2**30:
+        return '%.4g%sM' % (byte / 2**20.0, separator)
+    if byte < 2**30 * 999:
+        return '%.3g%sG' % (byte / 2**30.0, separator)
+    if byte < 2**40:
+        return '%.4g%sG' % (byte / 2**30.0, separator)
+    if byte < 2**40 * 999:
+        return '%.3g%sT' % (byte / 2**40.0, separator)
+    if byte < 2**50:
+        return '%.4g%sT' % (byte / 2**40.0, separator)
+    if byte < 2**50 * 999:
+        return '%.3g%sP' % (byte / 2**50.0, separator)
+    if byte < 2**60:
+        return '%.4g%sP' % (byte / 2**50.0, separator)
+    return '>9000'
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index b56c7729..191f5aa4 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -16,79 +16,79 @@ W3MIMGDISPLAY_PATH = '/usr/lib/w3m/w3mimgdisplay'
 W3MIMGDISPLAY_OPTIONS = []
 
 def _get_font_dimensions():
-	"""
-	Get the height and width of a character displayed in the terminal in
-	pixels.
-	"""
-	s = struct.pack("HHHH", 0, 0, 0, 0)
-	fd_stdout = sys.stdout.fileno()
-	x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
-	rows, cols, xpixels, ypixels = struct.unpack("HHHH", x)
+    """
+    Get the height and width of a character displayed in the terminal in
+    pixels.
+    """
+    s = struct.pack("HHHH", 0, 0, 0, 0)
+    fd_stdout = sys.stdout.fileno()
+    x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
+    rows, cols, xpixels, ypixels = struct.unpack("HHHH", x)
 
-	return (xpixels // cols), (ypixels // rows)
+    return (xpixels // cols), (ypixels // rows)
 
 
 def _w3mimgdisplay(commands):
-	"""
-	Invoke w3mimgdisplay and send commands on its standard input.
-	"""
-	process = Popen([W3MIMGDISPLAY_PATH] + W3MIMGDISPLAY_OPTIONS, stdin=PIPE,
-			stdout=PIPE, universal_newlines=True)
+    """
+    Invoke w3mimgdisplay and send commands on its standard input.
+    """
+    process = Popen([W3MIMGDISPLAY_PATH] + W3MIMGDISPLAY_OPTIONS, stdin=PIPE,
+            stdout=PIPE, universal_newlines=True)
 
-	# wait for the external program to finish
-	output, _ = process.communicate(input=commands)
+    # wait for the external program to finish
+    output, _ = process.communicate(input=commands)
 
-	return output
+    return output
 
 def draw(path, start_x, start_y, max_width, max_height):
-	"""
-	Draw an image file in the terminal.
-
-	start_x, start_y, max_height and max_width specify the drawing area.
-	They are expressed in number of characters.
-	"""
-	fontw, fonth = _get_font_dimensions()
-
-	max_width_pixels = max_width * fontw
-	max_height_pixels = max_height * fonth
-
-	# get image size
-	cmd = "5;{}".format(path)
-	output = _w3mimgdisplay(cmd).split()
-
-	if len(output) != 2:
-		raise Exception('Failed to execute w3mimgdisplay')
-
-	width = int(output[0])
-	height = int(output[1])
-
-	# get the maximum image size preserving ratio
-	if width > max_width_pixels:
-		height = (height * max_width_pixels) // width
-		width = max_width_pixels
-	if height > max_height_pixels:
-		width = (width * max_height_pixels) // height
-		height = max_height_pixels
-
-	# draw
-	cmd = "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;".format(
-			x = start_x * fontw,
-			y = start_y * fonth,
-			w = width,
-			h = height,
-			filename = path)
-	_w3mimgdisplay(cmd)
+    """
+    Draw an image file in the terminal.
+
+    start_x, start_y, max_height and max_width specify the drawing area.
+    They are expressed in number of characters.
+    """
+    fontw, fonth = _get_font_dimensions()
+
+    max_width_pixels = max_width * fontw
+    max_height_pixels = max_height * fonth
+
+    # get image size
+    cmd = "5;{}".format(path)
+    output = _w3mimgdisplay(cmd).split()
+
+    if len(output) != 2:
+        raise Exception('Failed to execute w3mimgdisplay')
+
+    width = int(output[0])
+    height = int(output[1])
+
+    # get the maximum image size preserving ratio
+    if width > max_width_pixels:
+        height = (height * max_width_pixels) // width
+        width = max_width_pixels
+    if height > max_height_pixels:
+        width = (width * max_height_pixels) // height
+        height = max_height_pixels
+
+    # draw
+    cmd = "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;".format(
+            x = start_x * fontw,
+            y = start_y * fonth,
+            w = width,
+            h = height,
+            filename = path)
+    _w3mimgdisplay(cmd)
 
 def clear(start_x, start_y, width, height):
-	"""
-	Clear a part of terminal display.
-	"""
-	fontw, fonth = _get_font_dimensions()
-
-	cmd = "6;{x};{y};{w};{h}\n4;\n3;".format(
-			x = start_x * fontw,
-			y = start_y * fonth,
-			w = width * fontw,
-			h = height * fonth)
-
-	_w3mimgdisplay(cmd)
+    """
+    Clear a part of terminal display.
+    """
+    fontw, fonth = _get_font_dimensions()
+
+    cmd = "6;{x};{y};{w};{h}\n4;\n3;".format(
+            x = start_x * fontw,
+            y = start_y * fonth,
+            w = width * fontw,
+            h = height * fonth)
+
+    _w3mimgdisplay(cmd)
diff --git a/ranger/ext/iter_tools.py b/ranger/ext/iter_tools.py
index 341230f4..9e8fcd74 100644
--- a/ranger/ext/iter_tools.py
+++ b/ranger/ext/iter_tools.py
@@ -4,44 +4,44 @@
 from collections import deque
 
 def flatten(lst):
-	"""
-	Flatten an iterable.
-
-	All contained tuples, lists, deques and sets are replaced by their
-	elements and flattened as well.
-
-	>>> l = [1, 2, [3, [4], [5, 6]], 7]
-	>>> list(flatten(l))
-	[1, 2, 3, 4, 5, 6, 7]
-	>>> list(flatten(()))
-	[]
-	"""
-	for elem in lst:
-		if isinstance(elem, (tuple, list, set, deque)):
-			for subelem in flatten(elem):
-				yield subelem
-		else:
-			yield elem
+    """
+    Flatten an iterable.
+
+    All contained tuples, lists, deques and sets are replaced by their
+    elements and flattened as well.
+
+    >>> l = [1, 2, [3, [4], [5, 6]], 7]
+    >>> list(flatten(l))
+    [1, 2, 3, 4, 5, 6, 7]
+    >>> list(flatten(()))
+    []
+    """
+    for elem in lst:
+        if isinstance(elem, (tuple, list, set, deque)):
+            for subelem in flatten(elem):
+                yield subelem
+        else:
+            yield elem
 
 def unique(iterable):
-	"""
-	Return an iterable of the same type which contains unique items.
-
-	This function assumes that:
-	type(iterable)(list(iterable)) == iterable
-	which is true for tuples, lists and deques (but not for strings)
-
-	>>> unique([1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 1, 1, 2])
-	[1, 2, 3, 4]
-	>>> unique(('w', 't', 't', 'f', 't', 'w'))
-	('w', 't', 'f')
-	"""
-	already_seen = []
-	for item in iterable:
-		if item not in already_seen:
-			already_seen.append(item)
-	return type(iterable)(already_seen)
+    """
+    Return an iterable of the same type which contains unique items.
+
+    This function assumes that:
+    type(iterable)(list(iterable)) == iterable
+    which is true for tuples, lists and deques (but not for strings)
+
+    >>> unique([1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 1, 1, 2])
+    [1, 2, 3, 4]
+    >>> unique(('w', 't', 't', 'f', 't', 'w'))
+    ('w', 't', 'f')
+    """
+    already_seen = []
+    for item in iterable:
+        if item not in already_seen:
+            already_seen.append(item)
+    return type(iterable)(already_seen)
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py
index eed85a2b..3439fa80 100644
--- a/ranger/ext/keybinding_parser.py
+++ b/ranger/ext/keybinding_parser.py
@@ -12,47 +12,47 @@ digits = set(range(ord('0'), ord('9')+1))
 ANYKEY, PASSIVE_ACTION, ALT_KEY, QUANT_KEY = range(9001, 9005)
 
 special_keys = {
-	'bs': curses.KEY_BACKSPACE,
-	'backspace': curses.KEY_BACKSPACE,
-	'backspace2': curses.ascii.DEL,
-	'delete': curses.KEY_DC,
-	'insert': curses.KEY_IC,
-	'cr': ord("\n"),
-	'enter': ord("\n"),
-	'return': ord("\n"),
-	'space': ord(" "),
-	'esc': curses.ascii.ESC,
-	'escape': curses.ascii.ESC,
-	'down': curses.KEY_DOWN,
-	'up': curses.KEY_UP,
-	'left': curses.KEY_LEFT,
-	'right': curses.KEY_RIGHT,
-	'pagedown': curses.KEY_NPAGE,
-	'pageup': curses.KEY_PPAGE,
-	'home': curses.KEY_HOME,
-	'end': curses.KEY_END,
-	'tab': ord('\t'),
-	's-tab': curses.KEY_BTAB,
+    'bs': curses.KEY_BACKSPACE,
+    'backspace': curses.KEY_BACKSPACE,
+    'backspace2': curses.ascii.DEL,
+    'delete': curses.KEY_DC,
+    'insert': curses.KEY_IC,
+    'cr': ord("\n"),
+    'enter': ord("\n"),
+    'return': ord("\n"),
+    'space': ord(" "),
+    'esc': curses.ascii.ESC,
+    'escape': curses.ascii.ESC,
+    'down': curses.KEY_DOWN,
+    'up': curses.KEY_UP,
+    'left': curses.KEY_LEFT,
+    'right': curses.KEY_RIGHT,
+    'pagedown': curses.KEY_NPAGE,
+    'pageup': curses.KEY_PPAGE,
+    'home': curses.KEY_HOME,
+    'end': curses.KEY_END,
+    'tab': ord('\t'),
+    's-tab': curses.KEY_BTAB,
 }
 
 very_special_keys = {
-	'any': ANYKEY,
-	'alt': ALT_KEY,
-	'bg': PASSIVE_ACTION,
-	'allow_quantifiers': QUANT_KEY,
+    'any': ANYKEY,
+    'alt': ALT_KEY,
+    'bg': PASSIVE_ACTION,
+    'allow_quantifiers': QUANT_KEY,
 }
 
 for key, val in tuple(special_keys.items()):
-	special_keys['a-' + key] = (ALT_KEY, val)
+    special_keys['a-' + key] = (ALT_KEY, val)
 
 for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':
-	special_keys['a-' + char] = (ALT_KEY, ord(char))
+    special_keys['a-' + char] = (ALT_KEY, ord(char))
 
 for char in 'abcdefghijklmnopqrstuvwxyz':
-	special_keys['c-' + char] = ord(char) - 96
+    special_keys['c-' + char] = ord(char) - 96
 
 for n in range(64):
-	special_keys['f' + str(n)] = curses.KEY_F0 + n
+    special_keys['f' + str(n)] = curses.KEY_F0 + n
 
 special_keys.update(very_special_keys)
 del very_special_keys
@@ -60,191 +60,191 @@ reversed_special_keys = dict((v, k) for k, v in special_keys.items())
 
 
 def parse_keybinding(obj):
-	"""
-	Translate a keybinding to a sequence of integers
-
-	Example:
-	lol<CR>   =>   (ord('l'), ord('o'), ord('l'), ord('\\n'))
-	          =>   (108, 111, 108, 10)
-	x<A-Left> =>   (120, (27, curses.KEY_LEFT))
-	"""
-	assert isinstance(obj, (tuple, int, str))
-	if isinstance(obj, tuple):
-		for char in obj:
-			yield char
-	elif isinstance(obj, int):
-		yield obj
-	elif isinstance(obj, str):
-		in_brackets = False
-		bracket_content = None
-		for char in obj:
-			if in_brackets:
-				if char == '>':
-					in_brackets = False
-					string = ''.join(bracket_content).lower()
-					try:
-						keys = special_keys[string]
-						for key in keys:
-							yield key
-					except KeyError:
-						yield ord('<')
-						for c in bracket_content:
-							yield ord(c)
-						yield ord('>')
-					except TypeError:
-						yield keys  # it was no tuple, just an int
-				else:
-					bracket_content.append(char)
-			else:
-				if char == '<':
-					in_brackets = True
-					bracket_content = []
-				else:
-					yield ord(char)
-		if in_brackets:
-			yield ord('<')
-			for c in bracket_content:
-				yield ord(c)
+    """
+    Translate a keybinding to a sequence of integers
+
+    Example:
+    lol<CR>   =>   (ord('l'), ord('o'), ord('l'), ord('\\n'))
+              =>   (108, 111, 108, 10)
+    x<A-Left> =>   (120, (27, curses.KEY_LEFT))
+    """
+    assert isinstance(obj, (tuple, int, str))
+    if isinstance(obj, tuple):
+        for char in obj:
+            yield char
+    elif isinstance(obj, int):
+        yield obj
+    elif isinstance(obj, str):
+        in_brackets = False
+        bracket_content = None
+        for char in obj:
+            if in_brackets:
+                if char == '>':
+                    in_brackets = False
+                    string = ''.join(bracket_content).lower()
+                    try:
+                        keys = special_keys[string]
+                        for key in keys:
+                            yield key
+                    except KeyError:
+                        yield ord('<')
+                        for c in bracket_content:
+                            yield ord(c)
+                        yield ord('>')
+                    except TypeError:
+                        yield keys  # it was no tuple, just an int
+                else:
+                    bracket_content.append(char)
+            else:
+                if char == '<':
+                    in_brackets = True
+                    bracket_content = []
+                else:
+                    yield ord(char)
+        if in_brackets:
+            yield ord('<')
+            for c in bracket_content:
+                yield ord(c)
 
 
 def construct_keybinding(iterable):
-	"""
-	Does the reverse of parse_keybinding
-	"""
-	return ''.join(key_to_string(c) for c in iterable)
+    """
+    Does the reverse of parse_keybinding
+    """
+    return ''.join(key_to_string(c) for c in iterable)
 
 
 def key_to_string(key):
-	if key in range(33, 127):
-		return chr(key)
-	if key in reversed_special_keys:
-		return "<%s>" % reversed_special_keys[key]
-	return "<%s>" % str(key)
+    if key in range(33, 127):
+        return chr(key)
+    if key in reversed_special_keys:
+        return "<%s>" % reversed_special_keys[key]
+    return "<%s>" % str(key)
 
 
 def _unbind_traverse(pointer, keys, pos=0):
-	if keys[pos] not in pointer:
-		return
-	if len(keys) > pos+1 and isinstance(pointer, dict):
-		_unbind_traverse(pointer[keys[pos]], keys, pos=pos+1)
-		if not pointer[keys[pos]]:
-			del pointer[keys[pos]]
-	elif len(keys) == pos+1:
-		try:
-			del pointer[keys[pos]]
-			keys.pop()
-		except:
-			pass
+    if keys[pos] not in pointer:
+        return
+    if len(keys) > pos+1 and isinstance(pointer, dict):
+        _unbind_traverse(pointer[keys[pos]], keys, pos=pos+1)
+        if not pointer[keys[pos]]:
+            del pointer[keys[pos]]
+    elif len(keys) == pos+1:
+        try:
+            del pointer[keys[pos]]
+            keys.pop()
+        except:
+            pass
 
 class KeyMaps(dict):
-	def __init__(self, keybuffer=None):
-		dict.__init__(self)
-		self.keybuffer = keybuffer
-		self.used_keymap = None
-
-	def use_keymap(self, keymap_name):
-		self.keybuffer.keymap = self.get(keymap_name, dict())
-		if self.used_keymap != keymap_name:
-			self.used_keymap = keymap_name
-			self.keybuffer.clear()
-
-	def _clean_input(self, context, keys):
-		try:
-			pointer = self[context]
-		except:
-			self[context] = pointer = dict()
-		if PY3:
-			keys = keys.encode('utf-8').decode('latin-1')
-		return list(parse_keybinding(keys)), pointer
-
-	def bind(self, context, keys, leaf):
-		keys, pointer = self._clean_input(context, keys)
-		if not keys:
-			return
-		last_key = keys[-1]
-		for key in keys[:-1]:
-			try:
-				if isinstance(pointer[key], dict):
-					pointer = pointer[key]
-				else:
-					pointer[key] = pointer = dict()
-			except:
-				pointer[key] = pointer = dict()
-		pointer[last_key] = leaf
-
-	def copy(self, context, source, target):
-		clean_source, pointer = self._clean_input(context, source)
-		if not source:
-			return
-		for key in clean_source:
-			try:
-				pointer = pointer[key]
-			except:
-				raise KeyError("Tried to copy the keybinding `%s',"
-						" but it was not found." % source)
-		self.bind(context, target, copy.deepcopy(pointer))
-
-	def unbind(self, context, keys):
-		keys, pointer = self._clean_input(context, keys)
-		if not keys:
-			return
-		_unbind_traverse(pointer, keys)
+    def __init__(self, keybuffer=None):
+        dict.__init__(self)
+        self.keybuffer = keybuffer
+        self.used_keymap = None
+
+    def use_keymap(self, keymap_name):
+        self.keybuffer.keymap = self.get(keymap_name, dict())
+        if self.used_keymap != keymap_name:
+            self.used_keymap = keymap_name
+            self.keybuffer.clear()
+
+    def _clean_input(self, context, keys):
+        try:
+            pointer = self[context]
+        except:
+            self[context] = pointer = dict()
+        if PY3:
+            keys = keys.encode('utf-8').decode('latin-1')
+        return list(parse_keybinding(keys)), pointer
+
+    def bind(self, context, keys, leaf):
+        keys, pointer = self._clean_input(context, keys)
+        if not keys:
+            return
+        last_key = keys[-1]
+        for key in keys[:-1]:
+            try:
+                if isinstance(pointer[key], dict):
+                    pointer = pointer[key]
+                else:
+                    pointer[key] = pointer = dict()
+            except:
+                pointer[key] = pointer = dict()
+        pointer[last_key] = leaf
+
+    def copy(self, context, source, target):
+        clean_source, pointer = self._clean_input(context, source)
+        if not source:
+            return
+        for key in clean_source:
+            try:
+                pointer = pointer[key]
+            except:
+                raise KeyError("Tried to copy the keybinding `%s',"
+                        " but it was not found." % source)
+        self.bind(context, target, copy.deepcopy(pointer))
+
+    def unbind(self, context, keys):
+        keys, pointer = self._clean_input(context, keys)
+        if not keys:
+            return
+        _unbind_traverse(pointer, keys)
 
 
 class KeyBuffer(object):
-	any_key             = ANYKEY
-	passive_key         = PASSIVE_ACTION
-	quantifier_key      = QUANT_KEY
-	exclude_from_anykey = [27]
-
-	def __init__(self, keymap=None):
-		self.keymap = keymap
-		self.clear()
-
-	def clear(self):
-		self.keys = []
-		self.wildcards = []
-		self.pointer = self.keymap
-		self.result = None
-		self.quantifier = None
-		self.finished_parsing_quantifier = False
-		self.finished_parsing = False
-		self.parse_error = False
-
-		if self.keymap and self.quantifier_key in self.keymap:
-			if self.keymap[self.quantifier_key] == 'false':
-				self.finished_parsing_quantifier = True
-
-	def add(self, key):
-		self.keys.append(key)
-		self.result = None
-		if not self.finished_parsing_quantifier and key in digits:
-			if self.quantifier is None:
-				self.quantifier = 0
-			self.quantifier = self.quantifier * 10 + key - 48 # (48 = ord(0))
-		else:
-			self.finished_parsing_quantifier = True
-
-			moved = True
-			if key in self.pointer:
-				self.pointer = self.pointer[key]
-			elif self.any_key in self.pointer and \
-					key not in self.exclude_from_anykey:
-				self.wildcards.append(key)
-				self.pointer = self.pointer[self.any_key]
-			else:
-				moved = False
-
-			if moved:
-				if isinstance(self.pointer, dict):
-					if self.passive_key in self.pointer:
-						self.result = self.pointer[self.passive_key]
-				else:
-					self.result = self.pointer
-					self.finished_parsing = True
-			else:
-				self.finished_parsing = True
-				self.parse_error = True
-
-	def __str__(self):
-		return "".join(key_to_string(c) for c in self.keys)
+    any_key             = ANYKEY
+    passive_key         = PASSIVE_ACTION
+    quantifier_key      = QUANT_KEY
+    exclude_from_anykey = [27]
+
+    def __init__(self, keymap=None):
+        self.keymap = keymap
+        self.clear()
+
+    def clear(self):
+        self.keys = []
+        self.wildcards = []
+        self.pointer = self.keymap
+        self.result = None
+        self.quantifier = None
+        self.finished_parsing_quantifier = False
+        self.finished_parsing = False
+        self.parse_error = False
+
+        if self.keymap and self.quantifier_key in self.keymap:
+            if self.keymap[self.quantifier_key] == 'false':
+                self.finished_parsing_quantifier = True
+
+    def add(self, key):
+        self.keys.append(key)
+        self.result = None
+        if not self.finished_parsing_quantifier and key in digits:
+            if self.quantifier is None:
+                self.quantifier = 0
+            self.quantifier = self.quantifier * 10 + key - 48 # (48 = ord(0))
+        else:
+            self.finished_parsing_quantifier = True
+
+            moved = True
+            if key in self.pointer:
+                self.pointer = self.pointer[key]
+            elif self.any_key in self.pointer and \
+                    key not in self.exclude_from_anykey:
+                self.wildcards.append(key)
+                self.pointer = self.pointer[self.any_key]
+            else:
+                moved = False
+
+            if moved:
+                if isinstance(self.pointer, dict):
+                    if self.passive_key in self.pointer:
+                        self.result = self.pointer[self.passive_key]
+                else:
+                    self.result = self.pointer
+                    self.finished_parsing = True
+            else:
+                self.finished_parsing = True
+                self.parse_error = True
+
+    def __str__(self):
+        return "".join(key_to_string(c) for c in self.keys)
diff --git a/ranger/ext/lazy_property.py b/ranger/ext/lazy_property.py
index 734d9616..60d3c802 100644
--- a/ranger/ext/lazy_property.py
+++ b/ranger/ext/lazy_property.py
@@ -1,34 +1,34 @@
 # From http://blog.pythonisito.com/2008/08/lazy-descriptors.html
 
 class lazy_property(object):
-	"""
-	A @property-like decorator with lazy evaluation
+    """
+    A @property-like decorator with lazy evaluation
 
-	>>> class Foo:
-	... 	@lazy_property
-	... 	def answer(self):
-	... 		print("calculating answer...")
-	... 		return 2*3*7
-	>>> foo = Foo()
-	>>> foo.answer
-	calculating answer...
-	42
-	>>> foo.answer
-	42
-	"""
+    >>> class Foo:
+    ...     @lazy_property
+    ...     def answer(self):
+    ...         print("calculating answer...")
+    ...         return 2*3*7
+    >>> foo = Foo()
+    >>> foo.answer
+    calculating answer...
+    42
+    >>> foo.answer
+    42
+    """
 
-	def __init__(self, method):
-		self._method = method
-		self.__name__ = method.__name__
-		self.__doc__ = method.__doc__
+    def __init__(self, method):
+        self._method = method
+        self.__name__ = method.__name__
+        self.__doc__ = method.__doc__
 
-	def __get__(self, obj, cls=None):
-		if obj is None:  # to fix issues with pydoc
-			return None
-		result = self._method(obj)
-		obj.__dict__[self.__name__] = result
-		return result
+    def __get__(self, obj, cls=None):
+        if obj is None:  # to fix issues with pydoc
+            return None
+        result = self._method(obj)
+        obj.__dict__[self.__name__] = result
+        return result
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/ext/mount_path.py b/ranger/ext/mount_path.py
index 19747eba..2114957a 100644
--- a/ranger/ext/mount_path.py
+++ b/ranger/ext/mount_path.py
@@ -4,10 +4,10 @@
 from os.path import realpath, abspath, dirname, ismount
 
 def mount_path(path):
-	"""Get the mount root of a directory"""
-	path = abspath(realpath(path))
-	while path != '/':
-		if ismount(path):
-			return path
-		path = dirname(path)
-	return '/'
+    """Get the mount root of a directory"""
+    path = abspath(realpath(path))
+    while path != '/':
+        if ismount(path):
+            return path
+        path = dirname(path)
+    return '/'
diff --git a/ranger/ext/next_available_filename.py b/ranger/ext/next_available_filename.py
index 705e1558..67d56126 100644
--- a/ranger/ext/next_available_filename.py
+++ b/ranger/ext/next_available_filename.py
@@ -4,15 +4,15 @@
 import os.path
 
 def next_available_filename(fname, directory="."):
-	existing_files = os.listdir(directory)
+    existing_files = os.listdir(directory)
 
-	if fname not in existing_files:
-		return fname
-	if not fname.endswith("_"):
-		fname += "_"
-		if fname not in existing_files:
-			return fname
+    if fname not in existing_files:
+        return fname
+    if not fname.endswith("_"):
+        fname += "_"
+        if fname not in existing_files:
+            return fname
 
-	for i in range(1, len(existing_files) + 1):
-		if fname + str(i) not in existing_files:
-			return fname + str(i)
+    for i in range(1, len(existing_files) + 1):
+        if fname + str(i) not in existing_files:
+            return fname + str(i)
diff --git a/ranger/ext/openstruct.py b/ranger/ext/openstruct.py
index fa521d34..af2e027e 100644
--- a/ranger/ext/openstruct.py
+++ b/ranger/ext/openstruct.py
@@ -5,7 +5,7 @@
 # or "keywords" as a keyword argument.
 
 class OpenStruct(dict):
-	"""The fusion of dict and struct"""
-	def __init__(self, *__args, **__keywords):
-		dict.__init__(self, *__args, **__keywords)
-		self.__dict__ = self
+    """The fusion of dict and struct"""
+    def __init__(self, *__args, **__keywords):
+        dict.__init__(self, *__args, **__keywords)
+        self.__dict__ = self
diff --git a/ranger/ext/popen_forked.py b/ranger/ext/popen_forked.py
index a9cf11a1..6c40d552 100644
--- a/ranger/ext/popen_forked.py
+++ b/ranger/ext/popen_forked.py
@@ -5,21 +5,21 @@ import os
 import subprocess
 
 def Popen_forked(*args, **kwargs):
-	"""
-	Forks process and runs Popen with the given args and kwargs.
+    """
+    Forks process and runs Popen with the given args and kwargs.
 
-	Returns True if forking succeeded, otherwise False.
-	"""
-	try:
-		pid = os.fork()
-	except OSError:
-		return False
-	if pid == 0:
-		os.setsid()
-		kwargs['stdin'] = open(os.devnull, 'r')
-		kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
-		subprocess.Popen(*args, **kwargs)
-		os._exit(0)
-	else:
-		os.wait()
-	return True
+    Returns True if forking succeeded, otherwise False.
+    """
+    try:
+        pid = os.fork()
+    except OSError:
+        return False
+    if pid == 0:
+        os.setsid()
+        kwargs['stdin'] = open(os.devnull, 'r')
+        kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
+        subprocess.Popen(*args, **kwargs)
+        os._exit(0)
+    else:
+        os.wait()
+    return True
diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py
index de1cb908..347aad8c 100644
--- a/ranger/ext/relative_symlink.py
+++ b/ranger/ext/relative_symlink.py
@@ -4,23 +4,23 @@
 from os import symlink, sep
 
 def relative_symlink(src, dst):
-	common_base = get_common_base(src, dst)
-	symlink(get_relative_source_file(src, dst, common_base), dst)
+    common_base = get_common_base(src, dst)
+    symlink(get_relative_source_file(src, dst, common_base), dst)
 
 def get_relative_source_file(src, dst, common_base=None):
-	if common_base is None:
-		common_base = get_common_base(src, dst)
-	return '../' * dst.count('/', len(common_base)) + src[len(common_base):]
+    if common_base is None:
+        common_base = get_common_base(src, dst)
+    return '../' * dst.count('/', len(common_base)) + src[len(common_base):]
 
 def get_common_base(src, dst):
-	if not src or not dst:
-		return '/'
-	i = 0
-	while True:
-		new_i = src.find(sep, i + 1)
-		if new_i == -1:
-			break
-		if not dst.startswith(src[:new_i + 1]):
-			break
-		i = new_i
-	return src[:i + 1]
+    if not src or not dst:
+        return '/'
+    i = 0
+    while True:
+        new_i = src.find(sep, i + 1)
+        if new_i == -1:
+            break
+        if not dst.startswith(src[:new_i + 1]):
+            break
+        i = new_i
+    return src[:i + 1]
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 4f551765..c94dd74b 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -10,9 +10,9 @@ When used together with ranger, it doesn't have to be installed to $PATH.
 
 Example usage:
 
-	rifle = Rifle("rilfe.conf")
-	rifle.reload_config()
-	rifle.execute(["file1", "file2"])
+    rifle = Rifle("rilfe.conf")
+    rifle.reload_config()
+    rifle.execute(["file1", "file2"])
 """
 
 import os.path
@@ -29,403 +29,403 @@ ENCODING = 'utf-8'
 # Imports from ranger library, plus reimplementations in case ranger is not
 # installed so rifle can be run as a standalone program.
 try:
-	from ranger.ext.get_executables import get_executables
+    from ranger.ext.get_executables import get_executables
 except ImportError:
-	_cached_executables = None
-
-	def get_executables():
-		"""
-		Return all executable files in $PATH + Cache them.
-		"""
-		global _cached_executables
-		if _cached_executables is not None:
-			return _cached_executables
-
-		if 'PATH' in os.environ:
-			paths = os.environ['PATH'].split(':')
-		else:
-			paths = ['/usr/bin', '/bin']
-
-		from stat import S_IXOTH, S_IFREG
-		paths_seen = set()
-		_cached_executables = set()
-		for path in paths:
-			if path in paths_seen:
-				continue
-			paths_seen.add(path)
-			try:
-				content = os.listdir(path)
-			except OSError:
-				continue
-			for item in content:
-				abspath = path + '/' + item
-				try:
-					filestat = os.stat(abspath)
-				except OSError:
-					continue
-				if filestat.st_mode & (S_IXOTH | S_IFREG):
-					_cached_executables.add(item)
-		return _cached_executables
+    _cached_executables = None
+
+    def get_executables():
+        """
+        Return all executable files in $PATH + Cache them.
+        """
+        global _cached_executables
+        if _cached_executables is not None:
+            return _cached_executables
+
+        if 'PATH' in os.environ:
+            paths = os.environ['PATH'].split(':')
+        else:
+            paths = ['/usr/bin', '/bin']
+
+        from stat import S_IXOTH, S_IFREG
+        paths_seen = set()
+        _cached_executables = set()
+        for path in paths:
+            if path in paths_seen:
+                continue
+            paths_seen.add(path)
+            try:
+                content = os.listdir(path)
+            except OSError:
+                continue
+            for item in content:
+                abspath = path + '/' + item
+                try:
+                    filestat = os.stat(abspath)
+                except OSError:
+                    continue
+                if filestat.st_mode & (S_IXOTH | S_IFREG):
+                    _cached_executables.add(item)
+        return _cached_executables
 
 
 try:
-	from ranger.ext.popen_forked import Popen_forked
+    from ranger.ext.popen_forked import Popen_forked
 except ImportError:
-	def Popen_forked(*args, **kwargs):
-		"""
-		Forks process and runs Popen with the given args and kwargs.
-		"""
-		try:
-			pid = os.fork()
-		except OSError:
-			return False
-		if pid == 0:
-			os.setsid()
-			kwargs['stdin'] = open(os.devnull, 'r')
-			kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
-			Popen(*args, **kwargs)
-			os._exit(0)
-		return True
+    def Popen_forked(*args, **kwargs):
+        """
+        Forks process and runs Popen with the given args and kwargs.
+        """
+        try:
+            pid = os.fork()
+        except OSError:
+            return False
+        if pid == 0:
+            os.setsid()
+            kwargs['stdin'] = open(os.devnull, 'r')
+            kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
+            Popen(*args, **kwargs)
+            os._exit(0)
+        return True
 
 
 def _is_terminal():
-	# Check if stdin (file descriptor 0), stdout (fd 1) and
-	# stderr (fd 2) are connected to a terminal
-	try:
-		os.ttyname(0)
-		os.ttyname(1)
-		os.ttyname(2)
-	except:
-		return False
-	return True
+    # Check if stdin (file descriptor 0), stdout (fd 1) and
+    # stderr (fd 2) are connected to a terminal
+    try:
+        os.ttyname(0)
+        os.ttyname(1)
+        os.ttyname(2)
+    except:
+        return False
+    return True
 
 
 def squash_flags(flags):
-	"""
-	Remove lowercase flags if the respective uppercase flag exists
+    """
+    Remove lowercase flags if the respective uppercase flag exists
 
-	>>> squash_flags('abc')
-	'abc'
-	>>> squash_flags('abcC')
-	'ab'
-	>>> squash_flags('CabcAd')
-	'bd'
-	"""
-	exclude = ''.join(f.upper() + f.lower() for f in flags if f == f.upper())
-	return ''.join(f for f in flags if f not in exclude)
+    >>> squash_flags('abc')
+    'abc'
+    >>> squash_flags('abcC')
+    'ab'
+    >>> squash_flags('CabcAd')
+    'bd'
+    """
+    exclude = ''.join(f.upper() + f.lower() for f in flags if f == f.upper())
+    return ''.join(f for f in flags if f not in exclude)
 
 
 class Rifle(object):
-	delimiter1 = '='
-	delimiter2 = ','
-
-	# TODO: Test all of the hooks properly
-	def hook_before_executing(self, command, mimetype, flags):
-		pass
-
-	def hook_after_executing(self, command, mimetype, flags):
-		pass
-
-	def hook_command_preprocessing(self, command):
-		return command
-
-	def hook_command_postprocessing(self, command):
-		return command
-
-	def hook_environment(self, env):
-		return env
-
-	def hook_logger(self, string):
-		sys.stderr.write(string + "\n")
-
-	def __init__(self, config_file):
-		self.config_file = config_file
-		self._app_flags = ''
-		self._app_label = None
-		self._initialized_mimetypes = False
-
-		# get paths for mimetype files
-		self._mimetype_known_files = [
-				os.path.expanduser("~/.mime.types")]
-		if __file__.endswith("ranger/ext/rifle.py"):
-			# Add ranger's default mimetypes when run from ranger directory
-			self._mimetype_known_files.append(
-					__file__.replace("ext/rifle.py", "data/mime.types"))
-
-	def reload_config(self, config_file=None):
-		"""Replace the current configuration with the one in config_file"""
-		if config_file is None:
-			config_file = self.config_file
-		f = open(config_file, 'r')
-		self.rules = []
-		lineno = 1
-		for line in f:
-			if line.startswith('#') or line == '\n':
-				continue
-			line = line.strip()
-			try:
-				if self.delimiter1 not in line:
-					raise Exception("Line without delimiter")
-				tests, command = line.split(self.delimiter1, 1)
-				tests = tests.split(self.delimiter2)
-				tests = tuple(tuple(f.strip().split(None, 1)) for f in tests)
-				command = command.strip()
-				self.rules.append((command, tests))
-			except Exception as e:
-				self.hook_logger("Syntax error in %s line %d (%s)" % \
-					(config_file, lineno, str(e)))
-			lineno += 1
-		f.close()
-
-	def _eval_condition(self, condition, files, label):
-		# Handle the negation of conditions starting with an exclamation mark,
-		# then pass on the arguments to _eval_condition2().
-
-		if not condition:
-			return True
-		if condition[0].startswith('!'):
-			new_condition = tuple([condition[0][1:]]) + tuple(condition[1:])
-			return not self._eval_condition2(new_condition, files, label)
-		return self._eval_condition2(condition, files, label)
-
-	def _eval_condition2(self, rule, files, label):
-		# This function evaluates the condition, after _eval_condition() handled
-		# negation of conditions starting with a "!".
-
-		if not files:
-			return False
-
-		function = rule[0]
-		argument = rule[1] if len(rule) > 1 else ''
-
-		if function == 'ext':
-			extension = os.path.basename(files[0]).rsplit('.', 1)[-1].lower()
-			return bool(re.search('^(' + argument + ')$', extension))
-		elif function == 'name':
-			return bool(re.search(argument, os.path.basename(files[0])))
-		elif function == 'match':
-			return bool(re.search(argument, files[0]))
-		elif function == 'file':
-			return os.path.isfile(files[0])
-		elif function == 'directory':
-			return os.path.isdir(files[0])
-		elif function == 'path':
-			return bool(re.search(argument, os.path.abspath(files[0])))
-		elif function == 'mime':
-			return bool(re.search(argument, self._get_mimetype(files[0])))
-		elif function == 'has':
-			return argument in get_executables()
-		elif function == 'terminal':
-			return _is_terminal()
-		elif function == 'number':
-			if argument.isdigit():
-				self._skip = int(argument)
-			return True
-		elif function == 'label':
-			self._app_label = argument
-			if label:
-				return argument == label
-			return True
-		elif function == 'flag':
-			self._app_flags = argument
-			return True
-		elif function == 'X':
-			return 'DISPLAY' in os.environ
-		elif function == 'else':
-			return True
-
-	def _get_mimetype(self, fname):
-		# Spawn "file" to determine the mime-type of the given file.
-		if self._mimetype:
-			return self._mimetype
-
-		import mimetypes
-		for path in self._mimetype_known_files:
-			if path not in mimetypes.knownfiles:
-				mimetypes.knownfiles.append(path)
-		self._mimetype, encoding = mimetypes.guess_type(fname)
-
-		if not self._mimetype:
-			process = Popen(["file", "--mime-type", "-Lb", fname],
-					stdout=PIPE, stderr=PIPE)
-			mimetype, _ = process.communicate()
-			self._mimetype = mimetype.decode(ENCODING).strip()
-		return self._mimetype
-
-	def _build_command(self, files, action, flags):
-		# Get the flags
-		if isinstance(flags, str):
-			self._app_flags += flags
-		self._app_flags = squash_flags(self._app_flags)
-		filenames = "' '".join(f.replace("'", "'\\\''") for f in files
-				if "\x00" not in f)
-		return "set -- '%s'; %s" % (filenames, action)
-
-	def list_commands(self, files, mimetype=None):
-		"""
-		Returns one 4-tuple for all currently applicable commands
-		The 4-tuple contains (count, command, label, flags).
-		count is the index, counted from 0 upwards,
-		command is the command that will be executed.
-		label and flags are the label and flags specified in the rule.
-		"""
-		self._mimetype = mimetype
-		count = -1
-		for cmd, tests in self.rules:
-			self._skip = None
-			self._app_flags = ''
-			self._app_label = None
-			for test in tests:
-				if not self._eval_condition(test, files, None):
-					break
-			else:
-				if self._skip is None:
-					count += 1
-				else:
-					count = self._skip
-				yield (count, cmd, self._app_label, self._app_flags)
-
-	def execute(self, files, number=0, label=None, flags="", mimetype=None):
-		"""
-		Executes the given list of files.
-
-		By default, this executes the first command where all conditions apply,
-		but by specifying number=N you can run the 1+Nth command.
-
-		If a label is specified, only rules with this label will be considered.
-
-		If you specify the mimetype, rifle will not try to determine it itself.
-
-		By specifying a flag, you extend the flag that is defined in the rule.
-		Uppercase flags negate the respective lowercase flags.
-		For example: if the flag in the rule is "pw" and you specify "Pf", then
-		the "p" flag is negated and the "f" flag is added, resulting in "wf".
-		"""
-		command = None
-		found_at_least_one = None
-
-		# Determine command
-		for count, cmd, lbl, flgs in self.list_commands(files, mimetype):
-			if label and label == lbl or not label and count == number:
-				cmd = self.hook_command_preprocessing(cmd)
-				if cmd == ASK_COMMAND:
-					return ASK_COMMAND
-				command = self._build_command(files, cmd, flags + flgs)
-				flags = self._app_flags
-				break
-			else:
-				found_at_least_one = True
-		else:
-			if label and label in get_executables():
-				cmd = '%s "$@"' % label
-				command = self._build_command(files, cmd, flags)
-
-		# Execute command
-		if command is None:
-			if found_at_least_one:
-				if label:
-					self.hook_logger("Label '%s' is undefined" % label)
-				else:
-					self.hook_logger("Method number %d is undefined." % number)
-			else:
-				self.hook_logger("No action found.")
-		else:
-			if 'PAGER' not in os.environ:
-				os.environ['PAGER'] = DEFAULT_PAGER
-			if 'EDITOR' not in os.environ:
-				os.environ['EDITOR'] = DEFAULT_EDITOR
-			command = self.hook_command_postprocessing(command)
-			self.hook_before_executing(command, self._mimetype, self._app_flags)
-			try:
-				if 'r' in flags:
-					prefix = ['sudo', '-E', 'su', '-mc']
-				else:
-					prefix = ['/bin/sh', '-c']
-
-				cmd = prefix + [command]
-				if 't' in flags:
-					if 'TERMCMD' not in os.environ:
-						term = os.environ['TERM']
-						if term.startswith('rxvt-unicode'):
-							term = 'urxvt'
-						if term not in get_executables():
-							self.hook_logger("Can not determine terminal command.  "
-								"Please set $TERMCMD manually.")
-							# A fallback terminal that is likely installed:
-							term = 'xterm'
-						os.environ['TERMCMD'] = term
-					cmd = [os.environ['TERMCMD'], '-e'] + cmd
-				if 'f' in flags or 't' in flags:
-					Popen_forked(cmd, env=self.hook_environment(os.environ))
-				else:
-					p = Popen(cmd, env=self.hook_environment(os.environ))
-					p.wait()
-			finally:
-				self.hook_after_executing(command, self._mimetype, self._app_flags)
+    delimiter1 = '='
+    delimiter2 = ','
+
+    # TODO: Test all of the hooks properly
+    def hook_before_executing(self, command, mimetype, flags):
+        pass
+
+    def hook_after_executing(self, command, mimetype, flags):
+        pass
+
+    def hook_command_preprocessing(self, command):
+        return command
+
+    def hook_command_postprocessing(self, command):
+        return command
+
+    def hook_environment(self, env):
+        return env
+
+    def hook_logger(self, string):
+        sys.stderr.write(string + "\n")
+
+    def __init__(self, config_file):
+        self.config_file = config_file
+        self._app_flags = ''
+        self._app_label = None
+        self._initialized_mimetypes = False
+
+        # get paths for mimetype files
+        self._mimetype_known_files = [
+                os.path.expanduser("~/.mime.types")]
+        if __file__.endswith("ranger/ext/rifle.py"):
+            # Add ranger's default mimetypes when run from ranger directory
+            self._mimetype_known_files.append(
+                    __file__.replace("ext/rifle.py", "data/mime.types"))
+
+    def reload_config(self, config_file=None):
+        """Replace the current configuration with the one in config_file"""
+        if config_file is None:
+            config_file = self.config_file
+        f = open(config_file, 'r')
+        self.rules = []
+        lineno = 1
+        for line in f:
+            if line.startswith('#') or line == '\n':
+                continue
+            line = line.strip()
+            try:
+                if self.delimiter1 not in line:
+                    raise Exception("Line without delimiter")
+                tests, command = line.split(self.delimiter1, 1)
+                tests = tests.split(self.delimiter2)
+                tests = tuple(tuple(f.strip().split(None, 1)) for f in tests)
+                command = command.strip()
+                self.rules.append((command, tests))
+            except Exception as e:
+                self.hook_logger("Syntax error in %s line %d (%s)" % \
+                    (config_file, lineno, str(e)))
+            lineno += 1
+        f.close()
+
+    def _eval_condition(self, condition, files, label):
+        # Handle the negation of conditions starting with an exclamation mark,
+        # then pass on the arguments to _eval_condition2().
+
+        if not condition:
+            return True
+        if condition[0].startswith('!'):
+            new_condition = tuple([condition[0][1:]]) + tuple(condition[1:])
+            return not self._eval_condition2(new_condition, files, label)
+        return self._eval_condition2(condition, files, label)
+
+    def _eval_condition2(self, rule, files, label):
+        # This function evaluates the condition, after _eval_condition() handled
+        # negation of conditions starting with a "!".
+
+        if not files:
+            return False
+
+        function = rule[0]
+        argument = rule[1] if len(rule) > 1 else ''
+
+        if function == 'ext':
+            extension = os.path.basename(files[0]).rsplit('.', 1)[-1].lower()
+            return bool(re.search('^(' + argument + ')$', extension))
+        elif function == 'name':
+            return bool(re.search(argument, os.path.basename(files[0])))
+        elif function == 'match':
+            return bool(re.search(argument, files[0]))
+        elif function == 'file':
+            return os.path.isfile(files[0])
+        elif function == 'directory':
+            return os.path.isdir(files[0])
+        elif function == 'path':
+            return bool(re.search(argument, os.path.abspath(files[0])))
+        elif function == 'mime':
+            return bool(re.search(argument, self._get_mimetype(files[0])))
+        elif function == 'has':
+            return argument in get_executables()
+        elif function == 'terminal':
+            return _is_terminal()
+        elif function == 'number':
+            if argument.isdigit():
+                self._skip = int(argument)
+            return True
+        elif function == 'label':
+            self._app_label = argument
+            if label:
+                return argument == label
+            return True
+        elif function == 'flag':
+            self._app_flags = argument
+            return True
+        elif function == 'X':
+            return 'DISPLAY' in os.environ
+        elif function == 'else':
+            return True
+
+    def _get_mimetype(self, fname):
+        # Spawn "file" to determine the mime-type of the given file.
+        if self._mimetype:
+            return self._mimetype
+
+        import mimetypes
+        for path in self._mimetype_known_files:
+            if path not in mimetypes.knownfiles:
+                mimetypes.knownfiles.append(path)
+        self._mimetype, encoding = mimetypes.guess_type(fname)
+
+        if not self._mimetype:
+            process = Popen(["file", "--mime-type", "-Lb", fname],
+                    stdout=PIPE, stderr=PIPE)
+            mimetype, _ = process.communicate()
+            self._mimetype = mimetype.decode(ENCODING).strip()
+        return self._mimetype
+
+    def _build_command(self, files, action, flags):
+        # Get the flags
+        if isinstance(flags, str):
+            self._app_flags += flags
+        self._app_flags = squash_flags(self._app_flags)
+        filenames = "' '".join(f.replace("'", "'\\\''") for f in files
+                if "\x00" not in f)
+        return "set -- '%s'; %s" % (filenames, action)
+
+    def list_commands(self, files, mimetype=None):
+        """
+        Returns one 4-tuple for all currently applicable commands
+        The 4-tuple contains (count, command, label, flags).
+        count is the index, counted from 0 upwards,
+        command is the command that will be executed.
+        label and flags are the label and flags specified in the rule.
+        """
+        self._mimetype = mimetype
+        count = -1
+        for cmd, tests in self.rules:
+            self._skip = None
+            self._app_flags = ''
+            self._app_label = None
+            for test in tests:
+                if not self._eval_condition(test, files, None):
+                    break
+            else:
+                if self._skip is None:
+                    count += 1
+                else:
+                    count = self._skip
+                yield (count, cmd, self._app_label, self._app_flags)
+
+    def execute(self, files, number=0, label=None, flags="", mimetype=None):
+        """
+        Executes the given list of files.
+
+        By default, this executes the first command where all conditions apply,
+        but by specifying number=N you can run the 1+Nth command.
+
+        If a label is specified, only rules with this label will be considered.
+
+        If you specify the mimetype, rifle will not try to determine it itself.
+
+        By specifying a flag, you extend the flag that is defined in the rule.
+        Uppercase flags negate the respective lowercase flags.
+        For example: if the flag in the rule is "pw" and you specify "Pf", then
+        the "p" flag is negated and the "f" flag is added, resulting in "wf".
+        """
+        command = None
+        found_at_least_one = None
+
+        # Determine command
+        for count, cmd, lbl, flgs in self.list_commands(files, mimetype):
+            if label and label == lbl or not label and count == number:
+                cmd = self.hook_command_preprocessing(cmd)
+                if cmd == ASK_COMMAND:
+                    return ASK_COMMAND
+                command = self._build_command(files, cmd, flags + flgs)
+                flags = self._app_flags
+                break
+            else:
+                found_at_least_one = True
+        else:
+            if label and label in get_executables():
+                cmd = '%s "$@"' % label
+                command = self._build_command(files, cmd, flags)
+
+        # Execute command
+        if command is None:
+            if found_at_least_one:
+                if label:
+                    self.hook_logger("Label '%s' is undefined" % label)
+                else:
+                    self.hook_logger("Method number %d is undefined." % number)
+            else:
+                self.hook_logger("No action found.")
+        else:
+            if 'PAGER' not in os.environ:
+                os.environ['PAGER'] = DEFAULT_PAGER
+            if 'EDITOR' not in os.environ:
+                os.environ['EDITOR'] = DEFAULT_EDITOR
+            command = self.hook_command_postprocessing(command)
+            self.hook_before_executing(command, self._mimetype, self._app_flags)
+            try:
+                if 'r' in flags:
+                    prefix = ['sudo', '-E', 'su', '-mc']
+                else:
+                    prefix = ['/bin/sh', '-c']
+
+                cmd = prefix + [command]
+                if 't' in flags:
+                    if 'TERMCMD' not in os.environ:
+                        term = os.environ['TERM']
+                        if term.startswith('rxvt-unicode'):
+                            term = 'urxvt'
+                        if term not in get_executables():
+                            self.hook_logger("Can not determine terminal command.  "
+                                "Please set $TERMCMD manually.")
+                            # A fallback terminal that is likely installed:
+                            term = 'xterm'
+                        os.environ['TERMCMD'] = term
+                    cmd = [os.environ['TERMCMD'], '-e'] + cmd
+                if 'f' in flags or 't' in flags:
+                    Popen_forked(cmd, env=self.hook_environment(os.environ))
+                else:
+                    p = Popen(cmd, env=self.hook_environment(os.environ))
+                    p.wait()
+            finally:
+                self.hook_after_executing(command, self._mimetype, self._app_flags)
 
 
 def main():
-	"""The main function which is run when you start this program direectly."""
-	import sys
-
-	# Find configuration file path
-	if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
-		conf_path = os.environ['XDG_CONFIG_HOME'] + '/ranger/rifle.conf'
-	else:
-		conf_path = os.path.expanduser('~/.config/ranger/rifle.conf')
-	if not os.path.isfile(conf_path):
-		conf_path = os.path.normpath(os.path.join(os.path.dirname(__file__),
-			'../config/rifle.conf'))
-
-	# Evaluate arguments
-	from optparse import OptionParser
-	parser = OptionParser(usage="%prog [-fhlpw] [files]")
-	parser.add_option('-f', type="string", default="", metavar="FLAGS",
-			help="use additional flags: f=fork, r=root, t=terminal. "
-			"Uppercase flag negates respective lowercase flags.")
-	parser.add_option('-l', action="store_true",
-			help="list possible ways to open the files (id:label:flags:command)")
-	parser.add_option('-p', type='string', default='0', metavar="KEYWORD",
-			help="pick a method to open the files.  KEYWORD is either the "
-			"number listed by 'rifle -l' or a string that matches a label in "
-			"the configuration file")
-	parser.add_option('-w', type='string', default=None, metavar="PROGRAM",
-			help="open the files with PROGRAM")
-	options, positional = parser.parse_args()
-	if not positional:
-		parser.print_help()
-		raise SystemExit(1)
-
-	if options.p.isdigit():
-		number = int(options.p)
-		label = None
-	else:
-		number = 0
-		label = options.p
-
-	if options.w is not None and not options.l:
-		p = Popen([options.w] + list(positional))
-		p.wait()
-	else:
-		# Start up rifle
-		rifle = Rifle(conf_path)
-		rifle.reload_config()
-		#print(rifle.list_commands(sys.argv[1:]))
-		if options.l:
-			for count, cmd, label, flags in rifle.list_commands(positional):
-				print("%d:%s:%s:%s" % (count, label or '', flags, cmd))
-		else:
-			result = rifle.execute(positional, number=number, label=label,
-					flags=options.f)
-			if result == ASK_COMMAND:
-				# TODO: implement interactive asking for file type?
-				print("Unknown file type: %s" % rifle._get_mimetype(positional[0]))
+    """The main function which is run when you start this program direectly."""
+    import sys
+
+    # Find configuration file path
+    if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
+        conf_path = os.environ['XDG_CONFIG_HOME'] + '/ranger/rifle.conf'
+    else:
+        conf_path = os.path.expanduser('~/.config/ranger/rifle.conf')
+    if not os.path.isfile(conf_path):
+        conf_path = os.path.normpath(os.path.join(os.path.dirname(__file__),
+            '../config/rifle.conf'))
+
+    # Evaluate arguments
+    from optparse import OptionParser
+    parser = OptionParser(usage="%prog [-fhlpw] [files]")
+    parser.add_option('-f', type="string", default="", metavar="FLAGS",
+            help="use additional flags: f=fork, r=root, t=terminal. "
+            "Uppercase flag negates respective lowercase flags.")
+    parser.add_option('-l', action="store_true",
+            help="list possible ways to open the files (id:label:flags:command)")
+    parser.add_option('-p', type='string', default='0', metavar="KEYWORD",
+            help="pick a method to open the files.  KEYWORD is either the "
+            "number listed by 'rifle -l' or a string that matches a label in "
+            "the configuration file")
+    parser.add_option('-w', type='string', default=None, metavar="PROGRAM",
+            help="open the files with PROGRAM")
+    options, positional = parser.parse_args()
+    if not positional:
+        parser.print_help()
+        raise SystemExit(1)
+
+    if options.p.isdigit():
+        number = int(options.p)
+        label = None
+    else:
+        number = 0
+        label = options.p
+
+    if options.w is not None and not options.l:
+        p = Popen([options.w] + list(positional))
+        p.wait()
+    else:
+        # Start up rifle
+        rifle = Rifle(conf_path)
+        rifle.reload_config()
+        #print(rifle.list_commands(sys.argv[1:]))
+        if options.l:
+            for count, cmd, label, flags in rifle.list_commands(positional):
+                print("%d:%s:%s:%s" % (count, label or '', flags, cmd))
+        else:
+            result = rifle.execute(positional, number=number, label=label,
+                    flags=options.f)
+            if result == ASK_COMMAND:
+                # TODO: implement interactive asking for file type?
+                print("Unknown file type: %s" % rifle._get_mimetype(positional[0]))
 
 
 
 if __name__ == '__main__':
-	if 'RANGER_DOCTEST' in os.environ:
-		import doctest
-		doctest.testmod()
-	else:
-		main()
+    if 'RANGER_DOCTEST' in os.environ:
+        import doctest
+        doctest.testmod()
+    else:
+        main()
diff --git a/ranger/ext/run_forked.py b/ranger/ext/run_forked.py
index 0dd52252..f08494b9 100644
--- a/ranger/ext/run_forked.py
+++ b/ranger/ext/run_forked.py
@@ -5,22 +5,22 @@ import os
 import subprocess
 
 def Popen_forked(*args, **kwargs):
-	"""
-	Forks process and runs Popen with the given args and kwargs.
+    """
+    Forks process and runs Popen with the given args and kwargs.
 
-	If os.fork() is not supported, runs Popen without forking and returns the
-	process object returned by Popen.
-	Otherwise, returns None.
-	"""
-	try:
-		pid = os.fork()
-	except:
-		# fall back to not forking if os.fork() is not supported
-		return subprocess.Popen(*args, **kwargs)
-	else:
-		if pid == 0:
-			os.setsid()
-			kwargs['stdin'] = open(os.devnull, 'r')
-			kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
-			subprocess.Popen(*args, **kwargs)
-			os._exit(0)
+    If os.fork() is not supported, runs Popen without forking and returns the
+    process object returned by Popen.
+    Otherwise, returns None.
+    """
+    try:
+        pid = os.fork()
+    except:
+        # fall back to not forking if os.fork() is not supported
+        return subprocess.Popen(*args, **kwargs)
+    else:
+        if pid == 0:
+            os.setsid()
+            kwargs['stdin'] = open(os.devnull, 'r')
+            kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
+            subprocess.Popen(*args, **kwargs)
+            os._exit(0)
diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py
index c9a22074..d67b9083 100644
--- a/ranger/ext/shell_escape.py
+++ b/ranger/ext/shell_escape.py
@@ -6,21 +6,21 @@ Functions to escape metacharacters of arguments for shell commands.
 """
 
 META_CHARS = (' ', "'", '"', '`', '&', '|', ';',
-		'$', '!', '(', ')', '[', ']', '<', '>', '\t')
+        '$', '!', '(', ')', '[', ']', '<', '>', '\t')
 UNESCAPABLE = set(map(chr, list(range(9)) + list(range(10, 32)) \
-		+ list(range(127, 256))))
+        + list(range(127, 256))))
 META_DICT = dict([(mc, '\\' + mc) for mc in META_CHARS])
 
 def shell_quote(string):
-	"""Escapes by quoting"""
-	return "'" + str(string).replace("'", "'\\''") + "'"
+    """Escapes by quoting"""
+    return "'" + str(string).replace("'", "'\\''") + "'"
 
 def shell_escape(arg):
-	"""Escapes by adding backslashes"""
-	arg = str(arg)
-	if UNESCAPABLE & set(arg):
-		return shell_quote(arg)
-	arg = arg.replace('\\', '\\\\') # make sure this comes at the start
-	for k, v in META_DICT.items():
-		arg = arg.replace(k, v)
-	return arg
+    """Escapes by adding backslashes"""
+    arg = str(arg)
+    if UNESCAPABLE & set(arg):
+        return shell_quote(arg)
+    arg = arg.replace('\\', '\\\\') # make sure this comes at the start
+    for k, v in META_DICT.items():
+        arg = arg.replace(k, v)
+    return arg
diff --git a/ranger/ext/signals.py b/ranger/ext/signals.py
index c3e546e3..e42e740d 100644
--- a/ranger/ext/signals.py
+++ b/ranger/ext/signals.py
@@ -19,12 +19,12 @@ called, they accumulate and should be manually deleted with
 signal_garbage_collect().
 
 >>> def test_function(signal):
-... 	if 'display' in signal:
-... 		print(signal.display)
-... 	else:
-... 		signal.stop()
+...     if 'display' in signal:
+...         print(signal.display)
+...     else:
+...         signal.stop()
 >>> def temporary_function():
-... 	print("A temporary function")
+...     print("A temporary function")
 
 >>> sig = SignalDispatcher()
 
@@ -62,218 +62,218 @@ import weakref
 from types import MethodType
 
 class Signal(dict):
-	"""
-	Signals are passed to the bound functions as an argument.
+    """
+    Signals are passed to the bound functions as an argument.
 
-	They contain the attributes "origin", which is a reference to the
-	signal dispatcher, and "name", the name of the signal that was emitted.
-	You can call signal_emit with any keyword arguments, which will be
-	turned into attributes of this object as well.
+    They contain the attributes "origin", which is a reference to the
+    signal dispatcher, and "name", the name of the signal that was emitted.
+    You can call signal_emit with any keyword arguments, which will be
+    turned into attributes of this object as well.
 
-	To delete a signal handler from inside a signal, raise a ReferenceError.
-	"""
-	stopped = False
-	def __init__(self, **keywords):
-		dict.__init__(self, keywords)
-		self.__dict__ = self
+    To delete a signal handler from inside a signal, raise a ReferenceError.
+    """
+    stopped = False
+    def __init__(self, **keywords):
+        dict.__init__(self, keywords)
+        self.__dict__ = self
 
-	def stop(self):
-		""" Stop the propagation of the signal to the next handlers.  """
-		self.stopped = True
+    def stop(self):
+        """ Stop the propagation of the signal to the next handlers.  """
+        self.stopped = True
 
 
 class SignalHandler:
-	"""
-	Signal Handlers contain information about a signal binding.
+    """
+    Signal Handlers contain information about a signal binding.
 
-	They are returned by signal_bind() and have to be passed to signal_unbind()
-	in order to remove the handler again.
+    They are returned by signal_bind() and have to be passed to signal_unbind()
+    in order to remove the handler again.
 
-	You can disable a handler without removing it by setting the attribute
-	"active" to False.
-	"""
-	active = True
-	def __init__(self, signal_name, function, priority, pass_signal):
-		self._priority = max(0, min(1, priority))
-		self._signal_name = signal_name
-		self._function = function
-		self._pass_signal = pass_signal
+    You can disable a handler without removing it by setting the attribute
+    "active" to False.
+    """
+    active = True
+    def __init__(self, signal_name, function, priority, pass_signal):
+        self._priority = max(0, min(1, priority))
+        self._signal_name = signal_name
+        self._function = function
+        self._pass_signal = pass_signal
 
 
 class SignalDispatcher(object):
-	"""
-	This abstract class handles the binding and emitting of signals.
-	"""
-	def __init__(self):
-		self._signals = dict()
-
-	def signal_clear(self):
-		""" Remove all signals.  """
-		for handler_list in self._signals.values():
-			for handler in handler_list:
-				handler._function = None
-		self._signals = dict()
-
-	def signal_bind(self, signal_name, function, priority=0.5, weak=False, autosort=True):
-		"""
-		Bind a function to the signal.
-
-		signal_name:  Any string to name the signal
-		function:  Any function with either one or zero arguments which will be
-			called when the signal is emitted.  If it takes one argument, a
-			Signal object will be passed to it.
-		priority:  Optional, any number.  When signals are emitted, handlers will
-			be called in order of priority.  (highest priority first)
-		weak:  Use a weak reference of "function" so it can be garbage collected
-			properly when it's deleted.
-
-		Returns a SignalHandler which can be used to remove this binding by
-		passing it to signal_unbind().
-		"""
-		assert isinstance(signal_name, str)
-		assert hasattr(function, '__call__')
-		assert hasattr(function, '__code__')
-		assert isinstance(priority, (int, float))
-		assert isinstance(weak, bool)
-		try:
-			handlers = self._signals[signal_name]
-		except:
-			handlers = self._signals[signal_name] = []
-		nargs = function.__code__.co_argcount
-
-		if getattr(function, '__self__', None):
-			nargs -= 1
-			if weak:
-				function = (function.__func__, weakref.proxy(function.__self__))
-		elif weak:
-			function = weakref.proxy(function)
-
-		handler = SignalHandler(signal_name, function, priority, nargs > 0)
-		handlers.append(handler)
-		if autosort:
-			handlers.sort(key=lambda handler: -handler._priority)
-		return handler
-
-	def signal_force_sort(self, signal_name=None):
-		"""
-		Forces a sorting of signal handlers by priority.
-
-		This is only necessary if you used signal_bind with autosort=False
-		after finishing to bind many signals at once.
-		"""
-		if signal_name is None:
-			for handlers in self._signals.values():
-				handlers.sort(key=lambda handler: -handler._priority)
-		elif signal_name in self._signals:
-			self._signals[signal_name].sort(key=lambda handler: -handler._priority)
-		else:
-			return False
-
-	def signal_unbind(self, signal_handler):
-		"""
-		Removes a signal binding.
-
-		This requires the SignalHandler that has been originally returned by
-		signal_bind().
-		"""
-		try:
-			handlers = self._signals[signal_handler._signal_name]
-		except:
-			pass
-		else:
-			try:
-				signal_handler._function = None
-				handlers.remove(signal_handler)
-			except:
-				pass
-
-	def signal_garbage_collect(self):
-		"""
-		Remove all handlers with deleted weak references.
-
-		Usually this is not needed; every time you emit a signal, its handlers
-		are automatically checked in this way.  However, if you can't be sure
-		that a signal is ever emitted AND you keep binding weakly referenced
-		functions to the signal, this method should be regularly called to
-		avoid memory leaks in self._signals.
-
-		>>> sig = SignalDispatcher()
-
-		>>> # lambda:None is an anonymous function which has no references
-		>>> # so it should get deleted immediately
-		>>> handler = sig.signal_bind('test', lambda: None, weak=True)
-		>>> len(sig._signals['test'])
-		1
-		>>> # need to call garbage collect so that it's removed from the list.
-		>>> sig.signal_garbage_collect()
-		>>> len(sig._signals['test'])
-		0
-		>>> # This demonstrates that garbage collecting is not necessary
-		>>> # when using signal_emit().
-		>>> handler = sig.signal_bind('test', lambda: None, weak=True)
-		>>> sig.signal_emit('another_signal')
-		True
-		>>> len(sig._signals['test'])
-		1
-		>>> sig.signal_emit('test')
-		True
-		>>> len(sig._signals['test'])
-		0
-		"""
-		for handler_list in self._signals.values():
-			i = len(handler_list)
-			while i:
-				i -= 1
-				handler = handler_list[i]
-				try:
-					if isinstance(handler._function, tuple):
-						handler._function[1].__class__
-					else:
-						handler._function.__class__
-				except ReferenceError:
-					handler._function = None
-					del handler_list[i]
-
-	def signal_emit(self, signal_name, **kw):
-		"""
-		Emits a signal and call every function that was bound to that signal.
-
-		You can call this method with any key words.  They will be turned into
-		attributes of the Signal object that is passed to the functions.
-		If a function calls signal.stop(), no further functions will be called.
-		If a function raises a ReferenceError, the handler will be deleted.
-
-		Returns False if signal.stop() was called and True otherwise.
-		"""
-		assert isinstance(signal_name, str)
-		if signal_name not in self._signals:
-			return True
-		handlers = self._signals[signal_name]
-		if not handlers:
-			return True
-
-		signal = Signal(origin=self, name=signal_name, **kw)
-
-		# propagate
-		for handler in tuple(handlers):
-			if handler.active:
-				try:
-					if isinstance(handler._function, tuple):
-						fnc = MethodType(*handler._function)
-					else:
-						fnc = handler._function
-					if handler._pass_signal:
-						fnc(signal)
-					else:
-						fnc()
-				except ReferenceError:
-					handler._function = None
-					handlers.remove(handler)
-				if signal.stopped:
-					return False
-		return True
+    """
+    This abstract class handles the binding and emitting of signals.
+    """
+    def __init__(self):
+        self._signals = dict()
+
+    def signal_clear(self):
+        """ Remove all signals.  """
+        for handler_list in self._signals.values():
+            for handler in handler_list:
+                handler._function = None
+        self._signals = dict()
+
+    def signal_bind(self, signal_name, function, priority=0.5, weak=False, autosort=True):
+        """
+        Bind a function to the signal.
+
+        signal_name:  Any string to name the signal
+        function:  Any function with either one or zero arguments which will be
+            called when the signal is emitted.  If it takes one argument, a
+            Signal object will be passed to it.
+        priority:  Optional, any number.  When signals are emitted, handlers will
+            be called in order of priority.  (highest priority first)
+        weak:  Use a weak reference of "function" so it can be garbage collected
+            properly when it's deleted.
+
+        Returns a SignalHandler which can be used to remove this binding by
+        passing it to signal_unbind().
+        """
+        assert isinstance(signal_name, str)
+        assert hasattr(function, '__call__')
+        assert hasattr(function, '__code__')
+        assert isinstance(priority, (int, float))
+        assert isinstance(weak, bool)
+        try:
+            handlers = self._signals[signal_name]
+        except:
+            handlers = self._signals[signal_name] = []
+        nargs = function.__code__.co_argcount
+
+        if getattr(function, '__self__', None):
+            nargs -= 1
+            if weak:
+                function = (function.__func__, weakref.proxy(function.__self__))
+        elif weak:
+            function = weakref.proxy(function)
+
+        handler = SignalHandler(signal_name, function, priority, nargs > 0)
+        handlers.append(handler)
+        if autosort:
+            handlers.sort(key=lambda handler: -handler._priority)
+        return handler
+
+    def signal_force_sort(self, signal_name=None):
+        """
+        Forces a sorting of signal handlers by priority.
+
+        This is only necessary if you used signal_bind with autosort=False
+        after finishing to bind many signals at once.
+        """
+        if signal_name is None:
+            for handlers in self._signals.values():
+                handlers.sort(key=lambda handler: -handler._priority)
+        elif signal_name in self._signals:
+            self._signals[signal_name].sort(key=lambda handler: -handler._priority)
+        else:
+            return False
+
+    def signal_unbind(self, signal_handler):
+        """
+        Removes a signal binding.
+
+        This requires the SignalHandler that has been originally returned by
+        signal_bind().
+        """
+        try:
+            handlers = self._signals[signal_handler._signal_name]
+        except:
+            pass
+        else:
+            try:
+                signal_handler._function = None
+                handlers.remove(signal_handler)
+            except:
+                pass
+
+    def signal_garbage_collect(self):
+        """
+        Remove all handlers with deleted weak references.
+
+        Usually this is not needed; every time you emit a signal, its handlers
+        are automatically checked in this way.  However, if you can't be sure
+        that a signal is ever emitted AND you keep binding weakly referenced
+        functions to the signal, this method should be regularly called to
+        avoid memory leaks in self._signals.
+
+        >>> sig = SignalDispatcher()
+
+        >>> # lambda:None is an anonymous function which has no references
+        >>> # so it should get deleted immediately
+        >>> handler = sig.signal_bind('test', lambda: None, weak=True)
+        >>> len(sig._signals['test'])
+        1
+        >>> # need to call garbage collect so that it's removed from the list.
+        >>> sig.signal_garbage_collect()
+        >>> len(sig._signals['test'])
+        0
+        >>> # This demonstrates that garbage collecting is not necessary
+        >>> # when using signal_emit().
+        >>> handler = sig.signal_bind('test', lambda: None, weak=True)
+        >>> sig.signal_emit('another_signal')
+        True
+        >>> len(sig._signals['test'])
+        1
+        >>> sig.signal_emit('test')
+        True
+        >>> len(sig._signals['test'])
+        0
+        """
+        for handler_list in self._signals.values():
+            i = len(handler_list)
+            while i:
+                i -= 1
+                handler = handler_list[i]
+                try:
+                    if isinstance(handler._function, tuple):
+                        handler._function[1].__class__
+                    else:
+                        handler._function.__class__
+                except ReferenceError:
+                    handler._function = None
+                    del handler_list[i]
+
+    def signal_emit(self, signal_name, **kw):
+        """
+        Emits a signal and call every function that was bound to that signal.
+
+        You can call this method with any key words.  They will be turned into
+        attributes of the Signal object that is passed to the functions.
+        If a function calls signal.stop(), no further functions will be called.
+        If a function raises a ReferenceError, the handler will be deleted.
+
+        Returns False if signal.stop() was called and True otherwise.
+        """
+        assert isinstance(signal_name, str)
+        if signal_name not in self._signals:
+            return True
+        handlers = self._signals[signal_name]
+        if not handlers:
+            return True
+
+        signal = Signal(origin=self, name=signal_name, **kw)
+
+        # propagate
+        for handler in tuple(handlers):
+            if handler.active:
+                try:
+                    if isinstance(handler._function, tuple):
+                        fnc = MethodType(*handler._function)
+                    else:
+                        fnc = handler._function
+                    if handler._pass_signal:
+                        fnc(signal)
+                    else:
+                        fnc()
+                except ReferenceError:
+                    handler._function = None
+                    handlers.remove(handler)
+                if signal.stopped:
+                    return False
+        return True
 
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py
index 0f3a629a..13fbd252 100644
--- a/ranger/ext/spawn.py
+++ b/ranger/ext/spawn.py
@@ -5,13 +5,13 @@ from subprocess import Popen, PIPE
 ENCODING = 'utf-8'
 
 def spawn(*args):
-	"""Runs a program, waits for its termination and returns its stdout"""
-	if len(args) == 1:
-		popen_arguments = args[0]
-		shell = isinstance(popen_arguments, str)
-	else:
-		popen_arguments = args
-		shell = False
-	process = Popen(popen_arguments, stdout=PIPE, shell=shell)
-	stdout, stderr = process.communicate()
-	return stdout.decode(ENCODING)
+    """Runs a program, waits for its termination and returns its stdout"""
+    if len(args) == 1:
+        popen_arguments = args[0]
+        shell = isinstance(popen_arguments, str)
+    else:
+        popen_arguments = args
+        shell = False
+    process = Popen(popen_arguments, stdout=PIPE, shell=shell)
+    stdout, stderr = process.communicate()
+    return stdout.decode(ENCODING)
diff --git a/ranger/ext/widestring.py b/ranger/ext/widestring.py
index fbd84b0d..dac54efd 100644
--- a/ranger/ext/widestring.py
+++ b/ranger/ext/widestring.py
@@ -12,140 +12,140 @@ WIDE = 2
 WIDE_SYMBOLS = set('WF')
 
 def uwid(string):
-	"""Return the width of a string"""
-	if not PY3:
-		string = string.decode('utf-8', 'ignore')
-	return sum(utf_char_width(c) for c in string)
+    """Return the width of a string"""
+    if not PY3:
+        string = string.decode('utf-8', 'ignore')
+    return sum(utf_char_width(c) for c in string)
 
 
 def utf_char_width(string):
-	"""Return the width of a single character"""
-	if east_asian_width(string) in WIDE_SYMBOLS:
-		return WIDE
-	return NARROW
+    """Return the width of a single character"""
+    if east_asian_width(string) in WIDE_SYMBOLS:
+        return WIDE
+    return NARROW
 
 
 def string_to_charlist(string):
-	"""Return a list of characters with extra empty strings after wide chars"""
-	if not set(string) - ASCIIONLY:
-		return list(string)
-	result = []
-	if PY3:
-		for c in string:
-			result.append(c)
-			if east_asian_width(c) in WIDE_SYMBOLS:
-				result.append('')
-	else:
-		string = string.decode('utf-8', 'ignore')
-		for c in string:
-			result.append(c.encode('utf-8'))
-			if east_asian_width(c) in WIDE_SYMBOLS:
-				result.append('')
-	return result
+    """Return a list of characters with extra empty strings after wide chars"""
+    if not set(string) - ASCIIONLY:
+        return list(string)
+    result = []
+    if PY3:
+        for c in string:
+            result.append(c)
+            if east_asian_width(c) in WIDE_SYMBOLS:
+                result.append('')
+    else:
+        string = string.decode('utf-8', 'ignore')
+        for c in string:
+            result.append(c.encode('utf-8'))
+            if east_asian_width(c) in WIDE_SYMBOLS:
+                result.append('')
+    return result
 
 
 class WideString(object):
-	def __init__(self, string, chars=None):
-		self.string = string
-		if chars is None:
-			self.chars = string_to_charlist(string)
-		else:
-			self.chars = chars
-
-	def __add__(self, string):
-		"""
-		>>> (WideString("a") + WideString("b")).string
-		'ab'
-		>>> (WideString("a") + WideString("b")).chars
-		['a', 'b']
-		>>> (WideString("afd") + "bc").chars
-		['a', 'f', 'd', 'b', 'c']
-		"""
-		if isinstance(string, str):
-			return WideString(self.string + string)
-		elif isinstance(string, WideString):
-			return WideString(self.string + string.string,
-					self.chars + string.chars)
-
-	def __radd__(self, string):
-		"""
-		>>> ("bc" + WideString("afd")).chars
-		['b', 'c', 'a', 'f', 'd']
-		"""
-		if isinstance(string, str):
-			return WideString(string + self.string)
-		elif isinstance(string, WideString):
-			return WideString(string.string + self.string,
-					string.chars + self.chars)
-
-	def __str__(self):
-		return self.string
-
-	def __repr__(self):
-		return '<' + self.__class__.__name__ + " '" + self.string + "'>"
-
-	def __getslice__(self, a, z):
-		"""
-		>>> WideString("asdf")[1:3]
-		<WideString 'sd'>
-		>>> WideString("asdf")[1:-100]
-		<WideString ''>
-		>>> WideString("モヒカン")[2:4]
-		<WideString 'ヒ'>
-		>>> WideString("モヒカン")[2:5]
-		<WideString 'ヒ '>
-		>>> WideString("モabカン")[2:5]
-		<WideString 'ab '>
-		>>> WideString("モヒカン")[1:5]
-		<WideString ' ヒ '>
-		>>> WideString("モヒカン")[:]
-		<WideString 'モヒカン'>
-		>>> WideString("aモ")[0:3]
-		<WideString 'aモ'>
-		>>> WideString("aモ")[0:2]
-		<WideString 'a '>
-		>>> WideString("aモ")[0:1]
-		<WideString 'a'>
-		"""
-		if z is None or z > len(self.chars):
-			z = len(self.chars)
-		if z < 0:
-			z = len(self.chars) + z
-		if z < 0:
-			return WideString("")
-		if a is None or a < 0:
-			a = 0
-		if z < len(self.chars) and self.chars[z] == '':
-			if self.chars[a] == '':
-				return WideString(' ' + ''.join(self.chars[a:z - 1]) + ' ')
-			return WideString(''.join(self.chars[a:z - 1]) + ' ')
-		if self.chars[a] == '':
-			return WideString(' ' + ''.join(self.chars[a:z - 1]))
-		return WideString(''.join(self.chars[a:z]))
-
-	def __getitem__(self, i):
-		"""
-		>>> WideString("asdf")[2]
-		<WideString 'd'>
-		>>> WideString("……")[0]
-		<WideString '…'>
-		>>> WideString("……")[1]
-		<WideString '…'>
-		"""
-		if isinstance(i, slice):
-			return self.__getslice__(i.start, i.stop)
-		return self.__getslice__(i, i+1)
-
-	def __len__(self):
-		"""
-		>>> len(WideString("poo"))
-		3
-		>>> len(WideString("モヒカン"))
-		8
-		"""
-		return len(self.chars)
+    def __init__(self, string, chars=None):
+        self.string = string
+        if chars is None:
+            self.chars = string_to_charlist(string)
+        else:
+            self.chars = chars
+
+    def __add__(self, string):
+        """
+        >>> (WideString("a") + WideString("b")).string
+        'ab'
+        >>> (WideString("a") + WideString("b")).chars
+        ['a', 'b']
+        >>> (WideString("afd") + "bc").chars
+        ['a', 'f', 'd', 'b', 'c']
+        """
+        if isinstance(string, str):
+            return WideString(self.string + string)
+        elif isinstance(string, WideString):
+            return WideString(self.string + string.string,
+                    self.chars + string.chars)
+
+    def __radd__(self, string):
+        """
+        >>> ("bc" + WideString("afd")).chars
+        ['b', 'c', 'a', 'f', 'd']
+        """
+        if isinstance(string, str):
+            return WideString(string + self.string)
+        elif isinstance(string, WideString):
+            return WideString(string.string + self.string,
+                    string.chars + self.chars)
+
+    def __str__(self):
+        return self.string
+
+    def __repr__(self):
+        return '<' + self.__class__.__name__ + " '" + self.string + "'>"
+
+    def __getslice__(self, a, z):
+        """
+        >>> WideString("asdf")[1:3]
+        <WideString 'sd'>
+        >>> WideString("asdf")[1:-100]
+        <WideString ''>
+        >>> WideString("モヒカン")[2:4]
+        <WideString 'ヒ'>
+        >>> WideString("モヒカン")[2:5]
+        <WideString 'ヒ '>
+        >>> WideString("モabカン")[2:5]
+        <WideString 'ab '>
+        >>> WideString("モヒカン")[1:5]
+        <WideString ' ヒ '>
+        >>> WideString("モヒカン")[:]
+        <WideString 'モヒカン'>
+        >>> WideString("aモ")[0:3]
+        <WideString 'aモ'>
+        >>> WideString("aモ")[0:2]
+        <WideString 'a '>
+        >>> WideString("aモ")[0:1]
+        <WideString 'a'>
+        """
+        if z is None or z > len(self.chars):
+            z = len(self.chars)
+        if z < 0:
+            z = len(self.chars) + z
+        if z < 0:
+            return WideString("")
+        if a is None or a < 0:
+            a = 0
+        if z < len(self.chars) and self.chars[z] == '':
+            if self.chars[a] == '':
+                return WideString(' ' + ''.join(self.chars[a:z - 1]) + ' ')
+            return WideString(''.join(self.chars[a:z - 1]) + ' ')
+        if self.chars[a] == '':
+            return WideString(' ' + ''.join(self.chars[a:z - 1]))
+        return WideString(''.join(self.chars[a:z]))
+
+    def __getitem__(self, i):
+        """
+        >>> WideString("asdf")[2]
+        <WideString 'd'>
+        >>> WideString("……")[0]
+        <WideString '…'>
+        >>> WideString("……")[1]
+        <WideString '…'>
+        """
+        if isinstance(i, slice):
+            return self.__getslice__(i.start, i.stop)
+        return self.__getslice__(i, i+1)
+
+    def __len__(self):
+        """
+        >>> len(WideString("poo"))
+        3
+        >>> len(WideString("モヒカン"))
+        8
+        """
+        return len(self.chars)
 
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index f8271b6d..2aa47a64 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -19,531 +19,531 @@ from ranger.ext.human_readable import human_readable
 from ranger.container.settingobject import LocalSettingObject
 
 def sort_by_basename(path):
-	"""returns path.basename (for sorting)"""
-	return path.basename
+    """returns path.basename (for sorting)"""
+    return path.basename
 
 def sort_by_basename_icase(path):
-	"""returns case-insensitive path.basename (for sorting)"""
-	return path.basename_lower
+    """returns case-insensitive path.basename (for sorting)"""
+    return path.basename_lower
 
 def sort_by_directory(path):
-	"""returns 0 if path is a directory, otherwise 1 (for sorting)"""
-	return 1 - path.is_directory
+    """returns 0 if path is a directory, otherwise 1 (for sorting)"""
+    return 1 - path.is_directory
 
 def sort_naturally(path):
-	return path.basename_natural
+    return path.basename_natural
 
 def sort_naturally_icase(path):
-	return path.basename_natural_lower
+    return path.basename_natural_lower
 
 def accept_file(fname, dirname, hidden_filter, name_filter):
-	if hidden_filter:
-		try:
-			if hidden_filter.search(fname):
-				return False
-		except:
-			if hidden_filter in fname:
-				return False
-	if name_filter and name_filter not in fname:
-		return False
-	return True
+    if hidden_filter:
+        try:
+            if hidden_filter.search(fname):
+                return False
+        except:
+            if hidden_filter in fname:
+                return False
+    if name_filter and name_filter not in fname:
+        return False
+    return True
 
 class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
-	is_directory = True
-	enterable = False
-	load_generator = None
-	cycle_list = None
-	loading = False
-	progressbar_supported = True
-
-	filenames = None
-	files = None
-	filter = None
-	marked_items = None
-	scroll_begin = 0
-
-	mount_path = '/'
-	disk_usage = 0
-
-	last_update_time = -1
-	load_content_mtime = -1
-
-	order_outdated = False
-	content_outdated = False
-	content_loaded = False
-
-	_cumulative_size_calculated = False
-
-	sort_dict = {
-		'basename': sort_by_basename,
-		'natural': sort_naturally,
-		'size': lambda path: -path.size,
-		'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
-		'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1),
-		'atime': lambda path: -(path.stat and path.stat.st_atime or 1),
-		'type': lambda path: path.mimetype or '',
-	}
-
-	def __init__(self, path, **kw):
-		assert not os.path.isfile(path), "No directory given!"
-
-		Loadable.__init__(self, None, None)
-		Accumulator.__init__(self)
-		FileSystemObject.__init__(self, path, **kw)
-
-		self.marked_items = list()
-
-		for opt in ('sort_directories_first', 'sort', 'sort_reverse',
-				'sort_case_insensitive'):
-			self.settings.signal_bind('setopt.' + opt,
-					self.request_resort, weak=True, autosort=False)
-
-		for opt in ('hidden_filter', 'show_hidden'):
-			self.settings.signal_bind('setopt.' + opt,
-				self.request_reload, weak=True, autosort=False)
-
-		self.settings = LocalSettingObject(path, self.settings)
-
-		self.use()
-
-	def request_resort(self):
-		self.order_outdated = True
-
-	def request_reload(self):
-		self.content_outdated = True
-
-	def get_list(self):
-		return self.files
-
-	def mark_item(self, item, val):
-		item._mark(val)
-		if val:
-			if item in self.files and not item in self.marked_items:
-				self.marked_items.append(item)
-		else:
-			while True:
-				try:
-					self.marked_items.remove(item)
-				except ValueError:
-					break
-
-	def toggle_mark(self, item):
-		self.mark_item(item, not item.marked)
-
-	def toggle_all_marks(self):
-		for item in self.files:
-			self.toggle_mark(item)
-
-	def mark_all(self, val):
-		for item in self.files:
-			self.mark_item(item, val)
-
-		if not val:
-			del self.marked_items[:]
-			self._clear_marked_items()
-
-	# XXX: Is it really necessary to have the marked items in a list?
-	# Can't we just recalculate them with [f for f in self.files if f.marked]?
-	def _gc_marked_items(self):
-		for item in list(self.marked_items):
-			if item.path not in self.filenames:
-				self.marked_items.remove(item)
-
-	def _clear_marked_items(self):
-		for item in self.marked_items:
-			item._mark(False)
-		del self.marked_items[:]
-
-	def get_selection(self):
-		"""READ ONLY"""
-		self._gc_marked_items()
-		if self.marked_items:
-			return [item for item in self.files if item.marked]
-		elif self.pointed_obj:
-			return [self.pointed_obj]
-		else:
-			return []
-
-	# XXX: Check for possible race conditions
-	def load_bit_by_bit(self):
-		"""
-		Returns a generator which load a part of the directory
-		in each iteration.
-		"""
-
-		self.loading = True
-		self.percent = 0
-		self.load_if_outdated()
-
-		try:
-			if self.runnable:
-				yield
-				mypath = self.path
-
-				self.mount_path = mount_path(mypath)
-
-				if not self.settings.show_hidden and self.settings.hidden_filter:
-					# COMPAT
-					# hidden_filter used to be a regex, not a string.  If an
-					# old config is used, we don't need to re.compile it.
-					if hasattr(self.settings.hidden_filter, 'search'):
-						hidden_filter = self.settings.hidden_filter
-					else:
-						hidden_filter = re.compile(self.settings.hidden_filter)
-				else:
-					hidden_filter = None
-
-				filelist = os.listdir(mypath)
-
-				if self._cumulative_size_calculated:
-					# If self.content_loaded is true, this is not the first
-					# time loading.  So I can't really be sure if the
-					# size has changed and I'll add a "?".
-					if self.content_loaded:
-						if self.fm.settings.autoupdate_cumulative_size:
-							self.look_up_cumulative_size()
-						else:
-							self.infostring = ' %s' % human_readable(
-								self.size, separator='? ')
-					else:
-						self.infostring = ' %s' % human_readable(self.size)
-				else:
-					self.size = len(filelist)
-					self.infostring = ' %d' % self.size
-				if self.is_link:
-					self.infostring = '->' + self.infostring
-
-				filenames = [mypath + (mypath == '/' and fname or '/' + fname)\
-						for fname in filelist if accept_file(
-							fname, mypath, hidden_filter, self.filter)]
-				yield
-
-				self.load_content_mtime = os.stat(mypath).st_mtime
-
-				marked_paths = [obj.path for obj in self.marked_items]
-
-				files = []
-				disk_usage = 0
-				for name in filenames:
-					try:
-						file_lstat = os_lstat(name)
-						if file_lstat.st_mode & 0o170000 == 0o120000:
-							file_stat = os_stat(name)
-						else:
-							file_stat = file_lstat
-						stats = (file_stat, file_lstat)
-						is_a_dir = file_stat.st_mode & 0o170000 == 0o040000
-					except:
-						stats = None
-						is_a_dir = False
-					if is_a_dir:
-						try:
-							item = self.fm.get_directory(name)
-							item.load_if_outdated()
-						except:
-							item = Directory(name, preload=stats,
-									path_is_abs=True)
-							item.load()
-					else:
-						item = File(name, preload=stats, path_is_abs=True)
-						item.load()
-						disk_usage += item.size
-					files.append(item)
-					self.percent = 100 * len(files) // len(filenames)
-					yield
-				self.disk_usage = disk_usage
-
-				self.filenames = filenames
-				self.files = files
-
-				self._clear_marked_items()
-				for item in self.files:
-					if item.path in marked_paths:
-						item._mark(True)
-						self.marked_items.append(item)
-					else:
-						item._mark(False)
-
-				self.sort()
-
-				if files:
-					if self.pointed_obj is not None:
-						self.sync_index()
-					else:
-						self.move(to=0)
-			else:
-				self.filenames = None
-				self.files = None
-
-			self.cycle_list = None
-			self.content_loaded = True
-			self.last_update_time = time()
-			self.correct_pointer()
-
-		finally:
-			self.loading = False
-			self.fm.signal_emit("finished_loading_dir", directory=self)
-
-	def unload(self):
-		self.loading = False
-		self.load_generator = None
-
-	def load_content(self, schedule=None):
-		"""
-		Loads the contents of the directory. Use this sparingly since
-		it takes rather long.
-		"""
-		self.content_outdated = False
-
-		if not self.loading:
-			if not self.loaded:
-				self.load()
-
-			if not self.accessible:
-				self.content_loaded = True
-				return
-
-			if schedule is None:
-				schedule = True   # was: self.size > 30
-
-			if self.load_generator is None:
-				self.load_generator = self.load_bit_by_bit()
-
-				if schedule and self.fm:
-					self.fm.loader.add(self)
-				else:
-					for _ in self.load_generator:
-						pass
-					self.load_generator = None
-
-			elif not schedule or not self.fm:
-				for _ in self.load_generator:
-					pass
-				self.load_generator = None
-
-
-	def sort(self):
-		"""Sort the containing files"""
-		if self.files is None:
-			return
-
-		old_pointed_obj = self.pointed_obj
-		try:
-			sort_func = self.sort_dict[self.settings.sort]
-		except:
-			sort_func = sort_by_basename
-
-		if self.settings.sort_case_insensitive and \
-				sort_func == sort_by_basename:
-			sort_func = sort_by_basename_icase
-
-		if self.settings.sort_case_insensitive and \
-				sort_func == sort_naturally:
-			sort_func = sort_naturally_icase
-
-		self.files.sort(key = sort_func)
-
-		if self.settings.sort_reverse:
-			self.files.reverse()
-
-		if self.settings.sort_directories_first:
-			self.files.sort(key = sort_by_directory)
-
-		if self.pointer is not None:
-			self.move_to_obj(old_pointed_obj)
-		else:
-			self.correct_pointer()
-
-	def _get_cumulative_size(self):
-		if self.size == 0:
-			return 0
-		cum = 0
-		realpath = os.path.realpath
-		for dirpath, dirnames, filenames in os.walk(self.path,
-				onerror=lambda _: None):
-			for file in filenames:
-				try:
-					if dirpath == self.path:
-						stat = os_stat(realpath(dirpath + "/" + file))
-					else:
-						stat = os_stat(dirpath + "/" + file)
-					cum += stat.st_size
-				except:
-					pass
-		return cum
-
-	def look_up_cumulative_size(self):
-		self._cumulative_size_calculated = True
-		self.size = self._get_cumulative_size()
-		self.infostring = ('-> ' if self.is_link else ' ') + \
-				human_readable(self.size)
-
-	@lazy_property
-	def size(self):
-		try:
-			size = len(os.listdir(self.path))  # bite me
-		except OSError:
-			self.infostring = BAD_INFO
-			self.accessible = False
-			self.runnable = False
-			return 0
-		else:
-			self.infostring = ' %d' % size
-			self.accessible = True
-			self.runnable = True
-			return size
-
-	@lazy_property
-	def infostring(self):
-		self.size  # trigger the lazy property initializer
-		if self.is_link:
-			return '->' + self.infostring
-		return self.infostring
-
-	@lazy_property
-	def runnable(self):
-		self.size  # trigger the lazy property initializer
-		return self.runnable
-
-	def sort_if_outdated(self):
-		"""Sort the containing files if they are outdated"""
-		if self.order_outdated:
-			self.order_outdated = False
-			self.sort()
-			return True
-		return False
-
-	def move_to_obj(self, arg):
-		try:
-			arg = arg.path
-		except:
-			pass
-		self.load_content_once(schedule=False)
-		if self.empty():
-			return
-
-		Accumulator.move_to_obj(self, arg, attr='path')
-
-	def search_fnc(self, fnc, offset=1, forward=True):
-		if not hasattr(fnc, '__call__'):
-			return False
-
-		length = len(self)
-
-		if forward:
-			generator = ((self.pointer + (x + offset)) % length \
-					for x in range(length - 1))
-		else:
-			generator = ((self.pointer - (x + offset)) % length \
-					for x in range(length - 1))
-
-		for i in generator:
-			_file = self.files[i]
-			if fnc(_file):
-				self.pointer = i
-				self.pointed_obj = _file
-				self.correct_pointer()
-				return True
-		return False
-
-	def set_cycle_list(self, lst):
-		self.cycle_list = deque(lst)
-
-	def cycle(self, forward=True):
-		if self.cycle_list:
-			if forward is True:
-				self.cycle_list.rotate(-1)
-			elif forward is False:
-				self.cycle_list.rotate(1)
-
-			self.move_to_obj(self.cycle_list[0])
-
-	def correct_pointer(self):
-		"""Make sure the pointer is in the valid range"""
-		Accumulator.correct_pointer(self)
-
-		try:
-			if self == self.fm.thisdir:
-				self.fm.thisfile = self.pointed_obj
-		except:
-			pass
-
-	def load_content_once(self, *a, **k):
-		"""Load the contents of the directory if not done yet"""
-		if not self.content_loaded:
-			self.load_content(*a, **k)
-			return True
-		return False
-
-	def load_content_if_outdated(self, *a, **k):
-		"""
-		Load the contents of the directory if it's
-		outdated or not done yet
-		"""
-
-		if self.load_content_once(*a, **k): return True
-
-		if self.files is None or self.content_outdated:
-			self.load_content(*a, **k)
-			return True
-
-		try:
-			real_mtime = os.stat(self.path).st_mtime
-		except OSError:
-			real_mtime = None
-			return False
-		if self.stat:
-			cached_mtime = self.load_content_mtime
-		else:
-			cached_mtime = 0
-
-		if real_mtime != cached_mtime:
-			self.load_content(*a, **k)
-			return True
-		return False
-
-	def get_description(self):
-		return "Loading " + str(self)
-
-	def use(self):
-		"""mark the filesystem-object as used at the current time"""
-		self.last_used = time()
-
-	def is_older_than(self, seconds):
-		"""returns whether this object wasn't use()d in the last n seconds"""
-		if seconds < 0:
-			return True
-		return self.last_used + seconds < time()
-
-	def go(self, history=True):
-		"""enter the directory if the filemanager is running"""
-		if self.fm:
-			return self.fm.enter_dir(self.path, history=history)
-		return False
-
-	def empty(self):
-		"""Is the directory empty?"""
-		return self.files is None or len(self.files) == 0
-
-	def __nonzero__(self):
-		"""Always True"""
-		return True
-	__bool__ = __nonzero__
-
-	def __len__(self):
-		"""The number of containing files"""
-		assert self.accessible
-		assert self.content_loaded
-		assert self.files is not None
-		return len(self.files)
-
-	def __eq__(self, other):
-		"""Check for equality of the directories paths"""
-		return isinstance(other, Directory) and self.path == other.path
-
-	def __neq__(self, other):
-		"""Check for inequality of the directories paths"""
-		return not self.__eq__(other)
-
-	def __hash__(self):
-		return hash(self.path)
+    is_directory = True
+    enterable = False
+    load_generator = None
+    cycle_list = None
+    loading = False
+    progressbar_supported = True
+
+    filenames = None
+    files = None
+    filter = None
+    marked_items = None
+    scroll_begin = 0
+
+    mount_path = '/'
+    disk_usage = 0
+
+    last_update_time = -1
+    load_content_mtime = -1
+
+    order_outdated = False
+    content_outdated = False
+    content_loaded = False
+
+    _cumulative_size_calculated = False
+
+    sort_dict = {
+        'basename': sort_by_basename,
+        'natural': sort_naturally,
+        'size': lambda path: -path.size,
+        'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
+        'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1),
+        'atime': lambda path: -(path.stat and path.stat.st_atime or 1),
+        'type': lambda path: path.mimetype or '',
+    }
+
+    def __init__(self, path, **kw):
+        assert not os.path.isfile(path), "No directory given!"
+
+        Loadable.__init__(self, None, None)
+        Accumulator.__init__(self)
+        FileSystemObject.__init__(self, path, **kw)
+
+        self.marked_items = list()
+
+        for opt in ('sort_directories_first', 'sort', 'sort_reverse',
+                'sort_case_insensitive'):
+            self.settings.signal_bind('setopt.' + opt,
+                    self.request_resort, weak=True, autosort=False)
+
+        for opt in ('hidden_filter', 'show_hidden'):
+            self.settings.signal_bind('setopt.' + opt,
+                self.request_reload, weak=True, autosort=False)
+
+        self.settings = LocalSettingObject(path, self.settings)
+
+        self.use()
+
+    def request_resort(self):
+        self.order_outdated = True
+
+    def request_reload(self):
+        self.content_outdated = True
+
+    def get_list(self):
+        return self.files
+
+    def mark_item(self, item, val):
+        item._mark(val)
+        if val:
+            if item in self.files and not item in self.marked_items:
+                self.marked_items.append(item)
+        else:
+            while True:
+                try:
+                    self.marked_items.remove(item)
+                except ValueError:
+                    break
+
+    def toggle_mark(self, item):
+        self.mark_item(item, not item.marked)
+
+    def toggle_all_marks(self):
+        for item in self.files:
+            self.toggle_mark(item)
+
+    def mark_all(self, val):
+        for item in self.files:
+            self.mark_item(item, val)
+
+        if not val:
+            del self.marked_items[:]
+            self._clear_marked_items()
+
+    # XXX: Is it really necessary to have the marked items in a list?
+    # Can't we just recalculate them with [f for f in self.files if f.marked]?
+    def _gc_marked_items(self):
+        for item in list(self.marked_items):
+            if item.path not in self.filenames:
+                self.marked_items.remove(item)
+
+    def _clear_marked_items(self):
+        for item in self.marked_items:
+            item._mark(False)
+        del self.marked_items[:]
+
+    def get_selection(self):
+        """READ ONLY"""
+        self._gc_marked_items()
+        if self.marked_items:
+            return [item for item in self.files if item.marked]
+        elif self.pointed_obj:
+            return [self.pointed_obj]
+        else:
+            return []
+
+    # XXX: Check for possible race conditions
+    def load_bit_by_bit(self):
+        """
+        Returns a generator which load a part of the directory
+        in each iteration.
+        """
+
+        self.loading = True
+        self.percent = 0
+        self.load_if_outdated()
+
+        try:
+            if self.runnable:
+                yield
+                mypath = self.path
+
+                self.mount_path = mount_path(mypath)
+
+                if not self.settings.show_hidden and self.settings.hidden_filter:
+                    # COMPAT
+                    # hidden_filter used to be a regex, not a string.  If an
+                    # old config is used, we don't need to re.compile it.
+                    if hasattr(self.settings.hidden_filter, 'search'):
+                        hidden_filter = self.settings.hidden_filter
+                    else:
+                        hidden_filter = re.compile(self.settings.hidden_filter)
+                else:
+                    hidden_filter = None
+
+                filelist = os.listdir(mypath)
+
+                if self._cumulative_size_calculated:
+                    # If self.content_loaded is true, this is not the first
+                    # time loading.  So I can't really be sure if the
+                    # size has changed and I'll add a "?".
+                    if self.content_loaded:
+                        if self.fm.settings.autoupdate_cumulative_size:
+                            self.look_up_cumulative_size()
+                        else:
+                            self.infostring = ' %s' % human_readable(
+                                self.size, separator='? ')
+                    else:
+                        self.infostring = ' %s' % human_readable(self.size)
+                else:
+                    self.size = len(filelist)
+                    self.infostring = ' %d' % self.size
+                if self.is_link:
+                    self.infostring = '->' + self.infostring
+
+                filenames = [mypath + (mypath == '/' and fname or '/' + fname)\
+                        for fname in filelist if accept_file(
+                            fname, mypath, hidden_filter, self.filter)]
+                yield
+
+                self.load_content_mtime = os.stat(mypath).st_mtime
+
+                marked_paths = [obj.path for obj in self.marked_items]
+
+                files = []
+                disk_usage = 0
+                for name in filenames:
+                    try:
+                        file_lstat = os_lstat(name)
+                        if file_lstat.st_mode & 0o170000 == 0o120000:
+                            file_stat = os_stat(name)
+                        else:
+                            file_stat = file_lstat
+                        stats = (file_stat, file_lstat)
+                        is_a_dir = file_stat.st_mode & 0o170000 == 0o040000
+                    except:
+                        stats = None
+                        is_a_dir = False
+                    if is_a_dir:
+                        try:
+                            item = self.fm.get_directory(name)
+                            item.load_if_outdated()
+                        except:
+                            item = Directory(name, preload=stats,
+                                    path_is_abs=True)
+                            item.load()
+                    else:
+                        item = File(name, preload=stats, path_is_abs=True)
+                        item.load()
+                        disk_usage += item.size
+                    files.append(item)
+                    self.percent = 100 * len(files) // len(filenames)
+                    yield
+                self.disk_usage = disk_usage
+
+                self.filenames = filenames
+                self.files = files
+
+                self._clear_marked_items()
+                for item in self.files:
+                    if item.path in marked_paths:
+                        item._mark(True)
+                        self.marked_items.append(item)
+                    else:
+                        item._mark(False)
+
+                self.sort()
+
+                if files:
+                    if self.pointed_obj is not None:
+                        self.sync_index()
+                    else:
+                        self.move(to=0)
+            else:
+                self.filenames = None
+                self.files = None
+
+            self.cycle_list = None
+            self.content_loaded = True
+            self.last_update_time = time()
+            self.correct_pointer()
+
+        finally:
+            self.loading = False
+            self.fm.signal_emit("finished_loading_dir", directory=self)
+
+    def unload(self):
+        self.loading = False
+        self.load_generator = None
+
+    def load_content(self, schedule=None):
+        """
+        Loads the contents of the directory. Use this sparingly since
+        it takes rather long.
+        """
+        self.content_outdated = False
+
+        if not self.loading:
+            if not self.loaded:
+                self.load()
+
+            if not self.accessible:
+                self.content_loaded = True
+                return
+
+            if schedule is None:
+                schedule = True   # was: self.size > 30
+
+            if self.load_generator is None:
+                self.load_generator = self.load_bit_by_bit()
+
+                if schedule and self.fm:
+                    self.fm.loader.add(self)
+                else:
+                    for _ in self.load_generator:
+                        pass
+                    self.load_generator = None
+
+            elif not schedule or not self.fm:
+                for _ in self.load_generator:
+                    pass
+                self.load_generator = None
+
+
+    def sort(self):
+        """Sort the containing files"""
+        if self.files is None:
+            return
+
+        old_pointed_obj = self.pointed_obj
+        try:
+            sort_func = self.sort_dict[self.settings.sort]
+        except:
+            sort_func = sort_by_basename
+
+        if self.settings.sort_case_insensitive and \
+                sort_func == sort_by_basename:
+            sort_func = sort_by_basename_icase
+
+        if self.settings.sort_case_insensitive and \
+                sort_func == sort_naturally:
+            sort_func = sort_naturally_icase
+
+        self.files.sort(key = sort_func)
+
+        if self.settings.sort_reverse:
+            self.files.reverse()
+
+        if self.settings.sort_directories_first:
+            self.files.sort(key = sort_by_directory)
+
+        if self.pointer is not None:
+            self.move_to_obj(old_pointed_obj)
+        else:
+            self.correct_pointer()
+
+    def _get_cumulative_size(self):
+        if self.size == 0:
+            return 0
+        cum = 0
+        realpath = os.path.realpath
+        for dirpath, dirnames, filenames in os.walk(self.path,
+                onerror=lambda _: None):
+            for file in filenames:
+                try:
+                    if dirpath == self.path:
+                        stat = os_stat(realpath(dirpath + "/" + file))
+                    else:
+                        stat = os_stat(dirpath + "/" + file)
+                    cum += stat.st_size
+                except:
+                    pass
+        return cum
+
+    def look_up_cumulative_size(self):
+        self._cumulative_size_calculated = True
+        self.size = self._get_cumulative_size()
+        self.infostring = ('-> ' if self.is_link else ' ') + \
+                human_readable(self.size)
+
+    @lazy_property
+    def size(self):
+        try:
+            size = len(os.listdir(self.path))  # bite me
+        except OSError:
+            self.infostring = BAD_INFO
+            self.accessible = False
+            self.runnable = False
+            return 0
+        else:
+            self.infostring = ' %d' % size
+            self.accessible = True
+            self.runnable = True
+            return size
+
+    @lazy_property
+    def infostring(self):
+        self.size  # trigger the lazy property initializer
+        if self.is_link:
+            return '->' + self.infostring
+        return self.infostring
+
+    @lazy_property
+    def runnable(self):
+        self.size  # trigger the lazy property initializer
+        return self.runnable
+
+    def sort_if_outdated(self):
+        """Sort the containing files if they are outdated"""
+        if self.order_outdated:
+            self.order_outdated = False
+            self.sort()
+            return True
+        return False
+
+    def move_to_obj(self, arg):
+        try:
+            arg = arg.path
+        except:
+            pass
+        self.load_content_once(schedule=False)
+        if self.empty():
+            return
+
+        Accumulator.move_to_obj(self, arg, attr='path')
+
+    def search_fnc(self, fnc, offset=1, forward=True):
+        if not hasattr(fnc, '__call__'):
+            return False
+
+        length = len(self)
+
+        if forward:
+            generator = ((self.pointer + (x + offset)) % length \
+                    for x in range(length - 1))
+        else:
+            generator = ((self.pointer - (x + offset)) % length \
+                    for x in range(length - 1))
+
+        for i in generator:
+            _file = self.files[i]
+            if fnc(_file):
+                self.pointer = i
+                self.pointed_obj = _file
+                self.correct_pointer()
+                return True
+        return False
+
+    def set_cycle_list(self, lst):
+        self.cycle_list = deque(lst)
+
+    def cycle(self, forward=True):
+        if self.cycle_list:
+            if forward is True:
+                self.cycle_list.rotate(-1)
+            elif forward is False:
+                self.cycle_list.rotate(1)
+
+            self.move_to_obj(self.cycle_list[0])
+
+    def correct_pointer(self):
+        """Make sure the pointer is in the valid range"""
+        Accumulator.correct_pointer(self)
+
+        try:
+            if self == self.fm.thisdir:
+                self.fm.thisfile = self.pointed_obj
+        except:
+            pass
+
+    def load_content_once(self, *a, **k):
+        """Load the contents of the directory if not done yet"""
+        if not self.content_loaded:
+            self.load_content(*a, **k)
+            return True
+        return False
+
+    def load_content_if_outdated(self, *a, **k):
+        """
+        Load the contents of the directory if it's
+        outdated or not done yet
+        """
+
+        if self.load_content_once(*a, **k): return True
+
+        if self.files is None or self.content_outdated:
+            self.load_content(*a, **k)
+            return True
+
+        try:
+            real_mtime = os.stat(self.path).st_mtime
+        except OSError:
+            real_mtime = None
+            return False
+        if self.stat:
+            cached_mtime = self.load_content_mtime
+        else:
+            cached_mtime = 0
+
+        if real_mtime != cached_mtime:
+            self.load_content(*a, **k)
+            return True
+        return False
+
+    def get_description(self):
+        return "Loading " + str(self)
+
+    def use(self):
+        """mark the filesystem-object as used at the current time"""
+        self.last_used = time()
+
+    def is_older_than(self, seconds):
+        """returns whether this object wasn't use()d in the last n seconds"""
+        if seconds < 0:
+            return True
+        return self.last_used + seconds < time()
+
+    def go(self, history=True):
+        """enter the directory if the filemanager is running"""
+        if self.fm:
+            return self.fm.enter_dir(self.path, history=history)
+        return False
+
+    def empty(self):
+        """Is the directory empty?"""
+        return self.files is None or len(self.files) == 0
+
+    def __nonzero__(self):
+        """Always True"""
+        return True
+    __bool__ = __nonzero__
+
+    def __len__(self):
+        """The number of containing files"""
+        assert self.accessible
+        assert self.content_loaded
+        assert self.files is not None
+        return len(self.files)
+
+    def __eq__(self, other):
+        """Check for equality of the directories paths"""
+        return isinstance(other, Directory) and self.path == other.path
+
+    def __neq__(self, other):
+        """Check for inequality of the directories paths"""
+        return not self.__eq__(other)
+
+    def __hash__(self):
+        return hash(self.path)
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 8598a1d6..5831ed3f 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -6,82 +6,82 @@ from ranger.fsobject import FileSystemObject
 
 N_FIRST_BYTES = 256
 control_characters = set(chr(n) for n in
-		set(range(0, 9)) | set(range(14, 32)))
+        set(range(0, 9)) | set(range(14, 32)))
 
 # Don't even try to preview files which mach this regular expression:
 PREVIEW_BLACKLIST = re.compile(r"""
-		# look at the extension:
-		\.(
-			# one character extensions:
-				[oa]
-			# media formats:
-				| avi | mpe?g | mp\d | og[gmv] | wm[av] | mkv | flv
-				| vob | wav | mpc | flac | divx? | xcf | pdf
-			# binary files:
-				| torrent | class | so | img | py[co] | dmg
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		# ignore fully numerical file extensions:
-			(\.\d+)*?
-		$
+        # look at the extension:
+        \.(
+            # one character extensions:
+                [oa]
+            # media formats:
+                | avi | mpe?g | mp\d | og[gmv] | wm[av] | mkv | flv
+                | vob | wav | mpc | flac | divx? | xcf | pdf
+            # binary files:
+                | torrent | class | so | img | py[co] | dmg
+        )
+        # ignore filetype-independent suffixes:
+            (\.part|\.bak|~)?
+        # ignore fully numerical file extensions:
+            (\.\d+)*?
+        $
 """, re.VERBOSE | re.IGNORECASE)
 
 # Preview these files (almost) always:
 PREVIEW_WHITELIST = re.compile(r"""
-		\.(
-			txt | py | c
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		$
+        \.(
+            txt | py | c
+        )
+        # ignore filetype-independent suffixes:
+            (\.part|\.bak|~)?
+        $
 """, re.VERBOSE | re.IGNORECASE)
 
 class File(FileSystemObject):
-	is_file = True
-	preview_data = None
-	preview_known = False
-	preview_loading = False
+    is_file = True
+    preview_data = None
+    preview_known = False
+    preview_loading = False
 
-	@property
-	def firstbytes(self):
-		try:
-			return self._firstbytes
-		except:
-			try:
-				f = open(self.path, 'r')
-				self._firstbytes = f.read(N_FIRST_BYTES)
-				f.close()
-				return self._firstbytes
-			except:
-				pass
+    @property
+    def firstbytes(self):
+        try:
+            return self._firstbytes
+        except:
+            try:
+                f = open(self.path, 'r')
+                self._firstbytes = f.read(N_FIRST_BYTES)
+                f.close()
+                return self._firstbytes
+            except:
+                pass
 
-	def is_binary(self):
-		if self.firstbytes and control_characters & set(self.firstbytes):
-			return True
-		return False
+    def is_binary(self):
+        if self.firstbytes and control_characters & set(self.firstbytes):
+            return True
+        return False
 
-	def has_preview(self):
-		if not self.fm.settings.preview_files:
-			return False
-		if self.is_socket or self.is_fifo or self.is_device:
-			return False
-		if not self.accessible:
-			return False
-		if self.fm.settings.preview_script and \
-				self.fm.settings.use_preview_script:
-			return True
-		if self.image or self.container:
-			return False
-		if PREVIEW_WHITELIST.search(self.basename):
-			return True
-		if PREVIEW_BLACKLIST.search(self.basename):
-			return False
-		if self.path == '/dev/core' or self.path == '/proc/kcore':
-			return False
-		if self.is_binary():
-			return False
-		return True
+    def has_preview(self):
+        if not self.fm.settings.preview_files:
+            return False
+        if self.is_socket or self.is_fifo or self.is_device:
+            return False
+        if not self.accessible:
+            return False
+        if self.fm.settings.preview_script and \
+                self.fm.settings.use_preview_script:
+            return True
+        if self.image or self.container:
+            return False
+        if PREVIEW_WHITELIST.search(self.basename):
+            return True
+        if PREVIEW_BLACKLIST.search(self.basename):
+            return False
+        if self.path == '/dev/core' or self.path == '/proc/kcore':
+            return False
+        if self.is_binary():
+            return False
+        return True
 
-	def get_preview_source(self, width, height):
-		return self.fm.get_preview(self, width, height)
+    def get_preview_source(self, width, height):
+        return self.fm.get_preview(self, width, height)
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index d2207ab6..02b32958 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -2,13 +2,13 @@
 # This software is distributed under the terms of the GNU GPL version 3.
 
 CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio',
-	'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar',
-	'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip')
+    'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar',
+    'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip')
 DOCUMENT_EXTENSIONS = ('cfg', 'css', 'cvs', 'djvu', 'doc', 'docx', 'gnm',
-	'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf',
-	'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx')
+    'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf',
+    'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx')
 DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits',
-	'hacking', 'help', 'install', 'license', 'readme', 'todo')
+    'hacking', 'help', 'install', 'license', 'readme', 'todo')
 
 import re
 from os import lstat, stat
@@ -20,270 +20,270 @@ from ranger.ext.lazy_property import lazy_property
 from ranger.ext.human_readable import human_readable
 
 if hasattr(str, 'maketrans'):
-	maketrans = str.maketrans
+    maketrans = str.maketrans
 else:
-	from string import maketrans
+    from string import maketrans
 _unsafe_chars = '\n' + ''.join(map(chr, range(32))) + ''.join(map(chr, range(128, 256)))
 _safe_string_table = maketrans(_unsafe_chars, '?' * len(_unsafe_chars))
 _extract_number_re = re.compile(r'([^0-9]?)(\d*)')
 
 def safe_path(path):
-	return path.translate(_safe_string_table)
+    return path.translate(_safe_string_table)
 
 class FileSystemObject(FileManagerAware):
-	(basename,
-	basename_lower,
-	dirname,
-	extension,
-	infostring,
-	path,
-	permissions,
-	stat) = (None,) * 8
-
-	(content_loaded,
-	force_load,
-
-	is_device,
-	is_directory,
-	is_file,
-	is_fifo,
-	is_link,
-	is_socket,
-
-	accessible,
-	exists,       # "exists" currently means "link_target_exists"
-	loaded,
-	marked,
-	runnable,
-	stopped,
-	tagged,
-
-	audio,
-	container,
-	document,
-	image,
-	media,
-	video) = (False,) * 21
-
-	size = 0
-
-
-	def __init__(self, path, preload=None, path_is_abs=False):
-		if not path_is_abs:
-			path = abspath(path)
-		self.path = path
-		self.basename = basename(path)
-		self.basename_lower = self.basename.lower()
-		self.extension = splitext(self.basename)[1].lstrip(extsep) or None
-		self.dirname = dirname(path)
-		self.preload = preload
-		self.display_data = {}
-
-		try:
-			lastdot = self.basename.rindex('.') + 1
-			self.extension = self.basename[lastdot:].lower()
-		except ValueError:
-			self.extension = None
-
-	def __repr__(self):
-		return "<{0} {1}>".format(self.__class__.__name__, self.path)
-
-	@lazy_property
-	def shell_escaped_basename(self):
-		return shell_escape(self.basename)
-
-	@lazy_property
-	def filetype(self):
-		try:
-			return spawn(["file", '-Lb', '--mime-type', self.path])
-		except OSError:
-			return ""
-
-	@lazy_property
-	def basename_natural(self):
-		return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
-			enumerate(_extract_number_re.split(self.basename))]
-
-	@lazy_property
-	def basename_natural_lower(self):
-		return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
-			enumerate(_extract_number_re.split(self.basename_lower))]
-
-	@lazy_property
-	def safe_basename(self):
-		return self.basename.translate(_safe_string_table)
-
-
-	for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
-		exec("%s = lazy_property("
-			"lambda self: self.set_mimetype() or self.%s)" % (attr, attr))
-
-	def __str__(self):
-		"""returns a string containing the absolute path"""
-		return str(self.path)
-
-	def use(self):
-		"""Used in garbage-collecting.  Override in Directory"""
-
-	def look_up_cumulative_size(self):
-		pass # normal files have no cumulative size
-
-	def set_mimetype(self):
-		"""assign attributes such as self.video according to the mimetype"""
-		basename = self.basename
-		if self.extension == 'part':
-			basename = basename[0:-5]
-		self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0]
-		if self._mimetype is None:
-			self._mimetype = ''
-
-		self.video = self._mimetype.startswith('video')
-		self.image = self._mimetype.startswith('image')
-		self.audio = self._mimetype.startswith('audio')
-		self.media = self.video or self.image or self.audio
-		self.document = self._mimetype.startswith('text') \
-				or self.extension in DOCUMENT_EXTENSIONS \
-				or self.basename.lower() in DOCUMENT_BASENAMES
-		self.container = self.extension in CONTAINER_EXTENSIONS
-
-		keys = ('video', 'audio', 'image', 'media', 'document', 'container')
-		self._mimetype_tuple = tuple(key for key in keys if getattr(self, key))
-
-		if self._mimetype == '':
-			self._mimetype = None
-
-	@property
-	def mimetype(self):
-		try:
-			return self._mimetype
-		except:
-			self.set_mimetype()
-			return self._mimetype
-
-	@property
-	def mimetype_tuple(self):
-		try:
-			return self._mimetype_tuple
-		except:
-			self.set_mimetype()
-			return self._mimetype_tuple
-
-	def mark(self, boolean):
-		directory = self.fm.get_directory(self.dirname)
-		directory.mark_item(self)
-
-	def _mark(self, boolean):
-		"""Called by directory.mark_item() and similar functions"""
-		self.marked = bool(boolean)
-
-	@lazy_property
-	def realpath(self):
-		if self.is_link:
-			try:
-				return realpath(self.path)
-			except:
-				return None  # it is impossible to get the link destination
-		return self.path
-
-	def load(self):
-		"""
-		reads useful information about the filesystem-object from the
-		filesystem and caches it for later use
-		"""
-
-		self.display_data = {}
-		self.fm.update_preview(self.path)
-		self.loaded = True
-
-		# Get the stat object, either from preload or from [l]stat
-		self.permissions = None
-		new_stat = None
-		path = self.path
-		is_link = False
-		if self.preload:
-			new_stat = self.preload[1]
-			self.is_link = new_stat.st_mode & 0o170000 == 0o120000
-			if self.is_link:
-				new_stat = self.preload[0]
-			self.preload = None
-			self.exists = True if new_stat else False
-		else:
-			try:
-				new_stat = lstat(path)
-				self.is_link = new_stat.st_mode & 0o170000 == 0o120000
-				if self.is_link:
-					new_stat = stat(path)
-				self.exists = True
-			except:
-				self.exists = False
-
-		# Set some attributes
-
-		self.accessible = True if new_stat else False
-		mode = new_stat.st_mode if new_stat else 0
-
-		format = mode & 0o170000
-		if format == 0o020000 or format == 0o060000:  # stat.S_IFCHR/BLK
-			self.is_device = True
-			self.size = 0
-			self.infostring = 'dev'
-		elif format == 0o010000:  # stat.S_IFIFO
-			self.is_fifo = True
-			self.size = 0
-			self.infostring = 'fifo'
-		elif format == 0o140000:  # stat.S_IFSOCK
-			self.is_socket = True
-			self.size = 0
-			self.infostring = 'sock'
-		elif self.is_file:
-			if new_stat:
-				self.size = new_stat.st_size
-				self.infostring = ' ' + human_readable(self.size)
-			else:
-				self.size = 0
-				self.infostring = '?'
-		if self.is_link and not self.is_directory:
-			self.infostring = '->' + self.infostring
-
-		self.stat = new_stat
-
-	def get_permission_string(self):
-		if self.permissions is not None:
-			return self.permissions
-
-		if self.is_directory:
-			perms = ['d']
-		elif self.is_link:
-			perms = ['l']
-		else:
-			perms = ['-']
-
-		mode = self.stat.st_mode
-		test = 0o0400
-		while test:  # will run 3 times because 0o400 >> 9 = 0
-			for what in "rwx":
-				if mode & test:
-					perms.append(what)
-				else:
-					perms.append('-')
-				test >>= 1
-
-		self.permissions = ''.join(perms)
-		return self.permissions
-
-	def load_if_outdated(self):
-		"""
-		Calls load() if the currently cached information is outdated
-		or nonexistant.
-		"""
-		if not self.loaded:
-			self.load()
-			return True
-		try:
-			real_ctime = lstat(self.path).st_ctime
-		except OSError:
-			real_ctime = None
-		if not self.stat or self.stat.st_ctime != real_ctime:
-			self.load()
-			return True
-		return False
+    (basename,
+    basename_lower,
+    dirname,
+    extension,
+    infostring,
+    path,
+    permissions,
+    stat) = (None,) * 8
+
+    (content_loaded,
+    force_load,
+
+    is_device,
+    is_directory,
+    is_file,
+    is_fifo,
+    is_link,
+    is_socket,
+
+    accessible,
+    exists,       # "exists" currently means "link_target_exists"
+    loaded,
+    marked,
+    runnable,
+    stopped,
+    tagged,
+
+    audio,
+    container,
+    document,
+    image,
+    media,
+    video) = (False,) * 21
+
+    size = 0
+
+
+    def __init__(self, path, preload=None, path_is_abs=False):
+        if not path_is_abs:
+            path = abspath(path)
+        self.path = path
+        self.basename = basename(path)
+        self.basename_lower = self.basename.lower()
+        self.extension = splitext(self.basename)[1].lstrip(extsep) or None
+        self.dirname = dirname(path)
+        self.preload = preload
+        self.display_data = {}
+
+        try:
+            lastdot = self.basename.rindex('.') + 1
+            self.extension = self.basename[lastdot:].lower()
+        except ValueError:
+            self.extension = None
+
+    def __repr__(self):
+        return "<{0} {1}>".format(self.__class__.__name__, self.path)
+
+    @lazy_property
+    def shell_escaped_basename(self):
+        return shell_escape(self.basename)
+
+    @lazy_property
+    def filetype(self):
+        try:
+            return spawn(["file", '-Lb', '--mime-type', self.path])
+        except OSError:
+            return ""
+
+    @lazy_property
+    def basename_natural(self):
+        return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
+            enumerate(_extract_number_re.split(self.basename))]
+
+    @lazy_property
+    def basename_natural_lower(self):
+        return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
+            enumerate(_extract_number_re.split(self.basename_lower))]
+
+    @lazy_property
+    def safe_basename(self):
+        return self.basename.translate(_safe_string_table)
+
+
+    for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
+        exec("%s = lazy_property("
+            "lambda self: self.set_mimetype() or self.%s)" % (attr, attr))
+
+    def __str__(self):
+        """returns a string containing the absolute path"""
+        return str(self.path)
+
+    def use(self):
+        """Used in garbage-collecting.  Override in Directory"""
+
+    def look_up_cumulative_size(self):
+        pass # normal files have no cumulative size
+
+    def set_mimetype(self):
+        """assign attributes such as self.video according to the mimetype"""
+        basename = self.basename
+        if self.extension == 'part':
+            basename = basename[0:-5]
+        self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0]
+        if self._mimetype is None:
+            self._mimetype = ''
+
+        self.video = self._mimetype.startswith('video')
+        self.image = self._mimetype.startswith('image')
+        self.audio = self._mimetype.startswith('audio')
+        self.media = self.video or self.image or self.audio
+        self.document = self._mimetype.startswith('text') \
+                or self.extension in DOCUMENT_EXTENSIONS \
+                or self.basename.lower() in DOCUMENT_BASENAMES
+        self.container = self.extension in CONTAINER_EXTENSIONS
+
+        keys = ('video', 'audio', 'image', 'media', 'document', 'container')
+        self._mimetype_tuple = tuple(key for key in keys if getattr(self, key))
+
+        if self._mimetype == '':
+            self._mimetype = None
+
+    @property
+    def mimetype(self):
+        try:
+            return self._mimetype
+        except:
+            self.set_mimetype()
+            return self._mimetype
+
+    @property
+    def mimetype_tuple(self):
+        try:
+            return self._mimetype_tuple
+        except:
+            self.set_mimetype()
+            return self._mimetype_tuple
+
+    def mark(self, boolean):
+        directory = self.fm.get_directory(self.dirname)
+        directory.mark_item(self)
+
+    def _mark(self, boolean):
+        """Called by directory.mark_item() and similar functions"""
+        self.marked = bool(boolean)
+
+    @lazy_property
+    def realpath(self):
+        if self.is_link:
+            try:
+                return realpath(self.path)
+            except:
+                return None  # it is impossible to get the link destination
+        return self.path
+
+    def load(self):
+        """
+        reads useful information about the filesystem-object from the
+        filesystem and caches it for later use
+        """
+
+        self.display_data = {}
+        self.fm.update_preview(self.path)
+        self.loaded = True
+
+        # Get the stat object, either from preload or from [l]stat
+        self.permissions = None
+        new_stat = None
+        path = self.path
+        is_link = False
+        if self.preload:
+            new_stat = self.preload[1]
+            self.is_link = new_stat.st_mode & 0o170000 == 0o120000
+            if self.is_link:
+                new_stat = self.preload[0]
+            self.preload = None
+            self.exists = True if new_stat else False
+        else:
+            try:
+                new_stat = lstat(path)
+                self.is_link = new_stat.st_mode & 0o170000 == 0o120000
+                if self.is_link:
+                    new_stat = stat(path)
+                self.exists = True
+            except:
+                self.exists = False
+
+        # Set some attributes
+
+        self.accessible = True if new_stat else False
+        mode = new_stat.st_mode if new_stat else 0
+
+        format = mode & 0o170000
+        if format == 0o020000 or format == 0o060000:  # stat.S_IFCHR/BLK
+            self.is_device = True
+            self.size = 0
+            self.infostring = 'dev'
+        elif format == 0o010000:  # stat.S_IFIFO
+            self.is_fifo = True
+            self.size = 0
+            self.infostring = 'fifo'
+        elif format == 0o140000:  # stat.S_IFSOCK
+            self.is_socket = True
+            self.size = 0
+            self.infostring = 'sock'
+        elif self.is_file:
+            if new_stat:
+                self.size = new_stat.st_size
+                self.infostring = ' ' + human_readable(self.size)
+            else:
+                self.size = 0
+                self.infostring = '?'
+        if self.is_link and not self.is_directory:
+            self.infostring = '->' + self.infostring
+
+        self.stat = new_stat
+
+    def get_permission_string(self):
+        if self.permissions is not None:
+            return self.permissions
+
+        if self.is_directory:
+            perms = ['d']
+        elif self.is_link:
+            perms = ['l']
+        else:
+            perms = ['-']
+
+        mode = self.stat.st_mode
+        test = 0o0400
+        while test:  # will run 3 times because 0o400 >> 9 = 0
+            for what in "rwx":
+                if mode & test:
+                    perms.append(what)
+                else:
+                    perms.append('-')
+                test >>= 1
+
+        self.permissions = ''.join(perms)
+        return self.permissions
+
+    def load_if_outdated(self):
+        """
+        Calls load() if the currently cached information is outdated
+        or nonexistant.
+        """
+        if not self.loaded:
+            self.load()
+            return True
+        try:
+            real_ctime = lstat(self.path).st_ctime
+        except OSError:
+            real_ctime = None
+        if not self.stat or self.stat.st_ctime != real_ctime:
+            self.load()
+            return True
+        return False
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py
index 1b693c7c..7019c6fb 100644
--- a/ranger/gui/ansi.py
+++ b/ranger/gui/ansi.py
@@ -14,154 +14,154 @@ codesplit_re = re.compile('38;5;(\d+);|48;5;(\d+);|(\d*);')
 reset = '\x1b[0m'
 
 def split_ansi_from_text(ansi_text):
-	return ansi_re.split(ansi_text)
+    return ansi_re.split(ansi_text)
 
 # For information on the ANSI codes see
 # githttp://en.wikipedia.org/wiki/ANSI_escape_code
 def text_with_fg_bg_attr(ansi_text):
-	fg, bg, attr = -1, -1, 0
-	for chunk in split_ansi_from_text(ansi_text):
-		if chunk and chunk[0] == '\x1b':
-			if chunk[-1] != 'm':
-				continue
-			match = re.match(r'^.\[(.*).$', chunk)
-			if not match:
-				# XXX I have no test case to determine what should happen here
-				continue
-			attr_args = match.group(1)
-
-			# Convert arguments to attributes/colors
-			for x256fg, x256bg, arg in codesplit_re.findall(attr_args + ';'):
-				# first handle xterm256 codes
-				try:
-					if len(x256fg) > 0:           # xterm256 foreground
-						fg = int(x256fg)
-						continue
-					elif len(x256bg) > 0:         # xterm256 background
-						bg = int(x256bg)
-						continue
-					elif len(arg) > 0:            # usual ansi code
-						n = int(arg)
-					else:                         # empty code means reset
-						n = 0
-				except:
-					continue
-
-				if n == 0:                        # reset colors and attributes
-					fg, bg, attr = -1, -1, 0
-
-				elif n == 1:                      # enable attribute
-					attr |= color.bold
-				elif n == 4:
-					attr |= color.underline
-				elif n == 5:
-					attr |= color.blink
-				elif n == 7:
-					attr |= color.reverse
-				elif n == 8:
-					attr |= color.invisible
-
-				elif n == 22:                     # disable attribute
-					attr &= not color.bold
-				elif n == 24:
-					attr &= not color.underline
-				elif n == 25:
-					attr &= not color.blink
-				elif n == 27:
-					attr &= not color.reverse
-				elif n == 28:
-					attr &= not color.invisible
-
-				elif n >= 30 and n <= 37:         # 8 ansi foreground and background colors
-					fg = n - 30
-				elif n == 39:
-					fg = -1
-				elif n >= 40 and n <= 47:
-					bg = n - 40
-				elif n == 49:
-					bg = -1
-
-				elif n >= 90 and n <= 97:         # 8 aixterm high intensity colors (light but not bold)
-					fg = n - 90 + 8
-				elif n == 99:
-					fg = -1
-				elif n >= 100 and n <= 107:
-					bg = n - 100 + 8
-				elif n == 109:
-					bg = -1
-
-			yield (fg, bg, attr)
-
-		else:
-			yield chunk
+    fg, bg, attr = -1, -1, 0
+    for chunk in split_ansi_from_text(ansi_text):
+        if chunk and chunk[0] == '\x1b':
+            if chunk[-1] != 'm':
+                continue
+            match = re.match(r'^.\[(.*).$', chunk)
+            if not match:
+                # XXX I have no test case to determine what should happen here
+                continue
+            attr_args = match.group(1)
+
+            # Convert arguments to attributes/colors
+            for x256fg, x256bg, arg in codesplit_re.findall(attr_args + ';'):
+                # first handle xterm256 codes
+                try:
+                    if len(x256fg) > 0:           # xterm256 foreground
+                        fg = int(x256fg)
+                        continue
+                    elif len(x256bg) > 0:         # xterm256 background
+                        bg = int(x256bg)
+                        continue
+                    elif len(arg) > 0:            # usual ansi code
+                        n = int(arg)
+                    else:                         # empty code means reset
+                        n = 0
+                except:
+                    continue
+
+                if n == 0:                        # reset colors and attributes
+                    fg, bg, attr = -1, -1, 0
+
+                elif n == 1:                      # enable attribute
+                    attr |= color.bold
+                elif n == 4:
+                    attr |= color.underline
+                elif n == 5:
+                    attr |= color.blink
+                elif n == 7:
+                    attr |= color.reverse
+                elif n == 8:
+                    attr |= color.invisible
+
+                elif n == 22:                     # disable attribute
+                    attr &= not color.bold
+                elif n == 24:
+                    attr &= not color.underline
+                elif n == 25:
+                    attr &= not color.blink
+                elif n == 27:
+                    attr &= not color.reverse
+                elif n == 28:
+                    attr &= not color.invisible
+
+                elif n >= 30 and n <= 37:         # 8 ansi foreground and background colors
+                    fg = n - 30
+                elif n == 39:
+                    fg = -1
+                elif n >= 40 and n <= 47:
+                    bg = n - 40
+                elif n == 49:
+                    bg = -1
+
+                elif n >= 90 and n <= 97:         # 8 aixterm high intensity colors (light but not bold)
+                    fg = n - 90 + 8
+                elif n == 99:
+                    fg = -1
+                elif n >= 100 and n <= 107:
+                    bg = n - 100 + 8
+                elif n == 109:
+                    bg = -1
+
+            yield (fg, bg, attr)
+
+        else:
+            yield chunk
 
 def char_len(ansi_text):
-	"""
-	Count the number of visible characters.
-
-	>>> char_len("\x1b[0;30;40mX\x1b[0m")
-	1
-	>>> char_len("\x1b[0;30;40mXY\x1b[0m")
-	2
-	>>> char_len("\x1b[0;30;40mX\x1b[0mY")
-	2
-	>>> char_len("hello")
-	5
-	>>> char_len("")
-	0
-	"""
-	return len(ansi_re.sub('', ansi_text))
+    """
+    Count the number of visible characters.
+
+    >>> char_len("\x1b[0;30;40mX\x1b[0m")
+    1
+    >>> char_len("\x1b[0;30;40mXY\x1b[0m")
+    2
+    >>> char_len("\x1b[0;30;40mX\x1b[0mY")
+    2
+    >>> char_len("hello")
+    5
+    >>> char_len("")
+    0
+    """
+    return len(ansi_re.sub('', ansi_text))
 
 def char_slice(ansi_text, start, length):
-	"""
-	Slices a string with respect to ansi code sequences
-
-	Acts as if the ansi codes aren't there, slices the text from the
-	given start point to the given length and adds the codes back in.
-
-	>>> test_string = "abcde\x1b[30mfoo\x1b[31mbar\x1b[0mnormal"
-	>>> split_ansi_from_text(test_string)
-	['abcde', '\\x1b[30m', 'foo', '\\x1b[31m', 'bar', '\\x1b[0m', 'normal']
-	>>> char_slice(test_string, 1, 3)
-	'bcd'
-	>>> char_slice(test_string, 5, 6)
-	'\\x1b[30mfoo\\x1b[31mbar'
-	>>> char_slice(test_string, 0, 8)
-	'abcde\\x1b[30mfoo'
-	>>> char_slice(test_string, 4, 4)
-	'e\\x1b[30mfoo'
-	>>> char_slice(test_string, 11, 100)
-	'\\x1b[0mnormal'
-	>>> char_slice(test_string, 9, 100)
-	'\\x1b[31mar\\x1b[0mnormal'
-	>>> char_slice(test_string, 9, 4)
-	'\\x1b[31mar\\x1b[0mno'
-	"""
-	chunks = []
-	last_color = ""
-	pos = old_pos = 0
-	for i, chunk in enumerate(split_ansi_from_text(ansi_text)):
-		if i % 2 == 1:
-			last_color = chunk
-			continue
-
-		old_pos = pos
-		pos += len(chunk)
-		if pos <= start:
-			pass # seek
-		elif old_pos < start and pos >= start:
-			chunks.append(last_color)
-			chunks.append(chunk[start-old_pos:start-old_pos+length])
-		elif pos > length + start:
-			chunks.append(last_color)
-			chunks.append(chunk[:start-old_pos+length])
-		else:
-			chunks.append(last_color)
-			chunks.append(chunk)
-		if pos - start >= length:
-			break
-	return ''.join(chunks)
+    """
+    Slices a string with respect to ansi code sequences
+
+    Acts as if the ansi codes aren't there, slices the text from the
+    given start point to the given length and adds the codes back in.
+
+    >>> test_string = "abcde\x1b[30mfoo\x1b[31mbar\x1b[0mnormal"
+    >>> split_ansi_from_text(test_string)
+    ['abcde', '\\x1b[30m', 'foo', '\\x1b[31m', 'bar', '\\x1b[0m', 'normal']
+    >>> char_slice(test_string, 1, 3)
+    'bcd'
+    >>> char_slice(test_string, 5, 6)
+    '\\x1b[30mfoo\\x1b[31mbar'
+    >>> char_slice(test_string, 0, 8)
+    'abcde\\x1b[30mfoo'
+    >>> char_slice(test_string, 4, 4)
+    'e\\x1b[30mfoo'
+    >>> char_slice(test_string, 11, 100)
+    '\\x1b[0mnormal'
+    >>> char_slice(test_string, 9, 100)
+    '\\x1b[31mar\\x1b[0mnormal'
+    >>> char_slice(test_string, 9, 4)
+    '\\x1b[31mar\\x1b[0mno'
+    """
+    chunks = []
+    last_color = ""
+    pos = old_pos = 0
+    for i, chunk in enumerate(split_ansi_from_text(ansi_text)):
+        if i % 2 == 1:
+            last_color = chunk
+            continue
+
+        old_pos = pos
+        pos += len(chunk)
+        if pos <= start:
+            pass # seek
+        elif old_pos < start and pos >= start:
+            chunks.append(last_color)
+            chunks.append(chunk[start-old_pos:start-old_pos+length])
+        elif pos > length + start:
+            chunks.append(last_color)
+            chunks.append(chunk[:start-old_pos+length])
+        else:
+            chunks.append(last_color)
+            chunks.append(chunk)
+        if pos - start >= length:
+            break
+    return ''.join(chunks)
 
 if __name__ == '__main__':
-	import doctest
-	doctest.testmod()
+    import doctest
+    doctest.testmod()
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index ae07dd35..a6596bf5 100644
--- a/ranger/gui/bar.py
+++ b/ranger/gui/bar.py
@@ -6,134 +6,134 @@ import sys
 PY3 = sys.version > '3'
 
 class Bar(object):
-	left = None
-	right = None
-	gap = None
-
-	def __init__(self, base_color_tag):
-		self.left = BarSide(base_color_tag)
-		self.right = BarSide(base_color_tag)
-		self.gap = BarSide(base_color_tag)
-
-	def add(self, *a, **kw):
-		self.left.add(*a, **kw)
-
-	def addright(self, *a, **kw):
-		self.right.add(*a, **kw)
-
-	def sumsize(self):
-		return self.left.sumsize() + self.right.sumsize()
-
-	def fixedsize(self):
-		return self.left.fixedsize() + self.right.fixedsize()
-
-	def shrink_by_removing(self, wid):
-		leftsize = self.left.sumsize()
-		rightsize = self.right.sumsize()
-		sumsize = leftsize + rightsize
-
-		# remove elemets from the left until it fits
-		if sumsize > wid:
-			while len(self.left) > 0:
-				leftsize -= len(self.left.pop(-1))
-				if leftsize + rightsize <= wid:
-					break
-			sumsize = leftsize + rightsize
-
-			# remove elemets from the right until it fits
-			if sumsize > wid:
-				while len(self.right) > 0:
-					rightsize -= len(self.right.pop(0))
-					if leftsize + rightsize <= wid:
-						break
-				sumsize = leftsize + rightsize
-
-		if sumsize < wid:
-			self.fill_gap(' ', (wid - sumsize), gapwidth=True)
-
-	def shrink_from_the_left(self, wid):
-		fixedsize = self.fixedsize()
-		if wid < fixedsize:
-			raise ValueError("Cannot shrink down to that size by cutting")
-		leftsize = self.left.sumsize()
-		rightsize = self.right.sumsize()
-		oversize = leftsize + rightsize - wid
-		if oversize <= 0:
-			return self.fill_gap(' ', wid, gapwidth=False)
-
-		# Shrink items to a minimum size until there is enough room.
-		for item in self.left:
-			if not item.fixed:
-				itemlen = len(item)
-				if oversize > itemlen - item.min_size:
-					item.cut_off_to(item.min_size)
-					oversize -= (itemlen - item.min_size)
-				else:
-					item.cut_off(oversize)
-					break
-
-	def fill_gap(self, char, wid, gapwidth=False):
-		del self.gap[:]
-
-		if not gapwidth:
-			wid = wid - self.sumsize()
-
-		if wid > 0:
-			self.gap.add(char * wid, 'space')
-
-	def combine(self):
-		return self.left + self.gap + self.right
+    left = None
+    right = None
+    gap = None
+
+    def __init__(self, base_color_tag):
+        self.left = BarSide(base_color_tag)
+        self.right = BarSide(base_color_tag)
+        self.gap = BarSide(base_color_tag)
+
+    def add(self, *a, **kw):
+        self.left.add(*a, **kw)
+
+    def addright(self, *a, **kw):
+        self.right.add(*a, **kw)
+
+    def sumsize(self):
+        return self.left.sumsize() + self.right.sumsize()
+
+    def fixedsize(self):
+        return self.left.fixedsize() + self.right.fixedsize()
+
+    def shrink_by_removing(self, wid):
+        leftsize = self.left.sumsize()
+        rightsize = self.right.sumsize()
+        sumsize = leftsize + rightsize
+
+        # remove elemets from the left until it fits
+        if sumsize > wid:
+            while len(self.left) > 0:
+                leftsize -= len(self.left.pop(-1))
+                if leftsize + rightsize <= wid:
+                    break
+            sumsize = leftsize + rightsize
+
+            # remove elemets from the right until it fits
+            if sumsize > wid:
+                while len(self.right) > 0:
+                    rightsize -= len(self.right.pop(0))
+                    if leftsize + rightsize <= wid:
+                        break
+                sumsize = leftsize + rightsize
+
+        if sumsize < wid:
+            self.fill_gap(' ', (wid - sumsize), gapwidth=True)
+
+    def shrink_from_the_left(self, wid):
+        fixedsize = self.fixedsize()
+        if wid < fixedsize:
+            raise ValueError("Cannot shrink down to that size by cutting")
+        leftsize = self.left.sumsize()
+        rightsize = self.right.sumsize()
+        oversize = leftsize + rightsize - wid
+        if oversize <= 0:
+            return self.fill_gap(' ', wid, gapwidth=False)
+
+        # Shrink items to a minimum size until there is enough room.
+        for item in self.left:
+            if not item.fixed:
+                itemlen = len(item)
+                if oversize > itemlen - item.min_size:
+                    item.cut_off_to(item.min_size)
+                    oversize -= (itemlen - item.min_size)
+                else:
+                    item.cut_off(oversize)
+                    break
+
+    def fill_gap(self, char, wid, gapwidth=False):
+        del self.gap[:]
+
+        if not gapwidth:
+            wid = wid - self.sumsize()
+
+        if wid > 0:
+            self.gap.add(char * wid, 'space')
+
+    def combine(self):
+        return self.left + self.gap + self.right
 
 
 class BarSide(list):
-	def __init__(self, base_color_tag):
-		self.base_color_tag = base_color_tag
+    def __init__(self, base_color_tag):
+        self.base_color_tag = base_color_tag
 
-	def add(self, string, *lst, **kw):
-		cs = ColoredString(string, self.base_color_tag, *lst)
-		cs.__dict__.update(kw)
-		self.append(cs)
+    def add(self, string, *lst, **kw):
+        cs = ColoredString(string, self.base_color_tag, *lst)
+        cs.__dict__.update(kw)
+        self.append(cs)
 
-	def add_space(self, n=1):
-		self.add(' ' * n, 'space')
+    def add_space(self, n=1):
+        self.add(' ' * n, 'space')
 
-	def sumsize(self):
-		return sum(len(item) for item in self)
+    def sumsize(self):
+        return sum(len(item) for item in self)
 
-	def fixedsize(self):
-		n = 0
-		for item in self:
-			if item.fixed:
-				n += len(item)
-			else:
-				n += item.min_size
-		return n
+    def fixedsize(self):
+        n = 0
+        for item in self:
+            if item.fixed:
+                n += len(item)
+            else:
+                n += item.min_size
+        return n
 
 
 class ColoredString(object):
-	def __init__(self, string, *lst):
-		self.string = WideString(string)
-		self.lst = lst
-		self.fixed = False
-		if not len(string):
-			self.min_size = 0
-		elif PY3:
-			self.min_size = utf_char_width(string[0])
-		else:
-			self.min_size = utf_char_width(self.string.chars[0].decode('utf-8'))
-
-	def cut_off(self, n):
-		if n >= 1:
-			self.string = self.string[:-n]
-
-	def cut_off_to(self, n):
-		if n < self.min_size:
-			self.string = self.string[:self.min_size]
-		elif n < len(self.string):
-			self.string = self.string[:n]
-
-	def __len__(self):
-		return len(self.string)
-
-	def __str__(self):
-		return str(self.string)
+    def __init__(self, string, *lst):
+        self.string = WideString(string)
+        self.lst = lst
+        self.fixed = False
+        if not len(string):
+            self.min_size = 0
+        elif PY3:
+            self.min_size = utf_char_width(string[0])
+        else:
+            self.min_size = utf_char_width(self.string.chars[0].decode('utf-8'))
+
+    def cut_off(self, n):
+        if n >= 1:
+            self.string = self.string[:-n]
+
+    def cut_off_to(self, n):
+        if n < self.min_size:
+            self.string = self.string[:self.min_size]
+        elif n < len(self.string):
+            self.string = self.string[:n]
+
+    def __len__(self):
+        return len(self.string)
+
+    def __str__(self):
+        return str(self.string)
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 037ff5c1..ab986b55 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -19,18 +19,18 @@ import curses
 COLOR_PAIRS = {10: 0}
 
 def get_color(fg, bg):
-	"""
-	Returns the curses color pair for the given fg/bg combination.
-	"""
+    """
+    Returns the curses color pair for the given fg/bg combination.
+    """
 
-	c = bg+2 + 9*(fg + 2)
+    c = bg+2 + 9*(fg + 2)
 
-	if c not in COLOR_PAIRS:
-		size = len(COLOR_PAIRS)
-		curses.init_pair(size, fg, bg)
-		COLOR_PAIRS[c] = size
+    if c not in COLOR_PAIRS:
+        size = len(COLOR_PAIRS)
+        curses.init_pair(size, fg, bg)
+        COLOR_PAIRS[c] = size
 
-	return COLOR_PAIRS[c]
+    return COLOR_PAIRS[c]
 
 black   = curses.COLOR_BLACK
 blue    = curses.COLOR_BLUE
@@ -52,7 +52,7 @@ invisible  = curses.A_INVIS
 default_colors = (default, default, normal)
 
 def remove_attr(integer, attribute):
-	"""Remove an attribute from an integer"""
-	if integer & attribute:
-		return integer ^ attribute
-	return integer
+    """Remove an attribute from an integer"""
+    if integer & attribute:
+        return integer ^ attribute
+    return integer
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index 34e59f55..0a142d52 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -37,103 +37,103 @@ from ranger.ext.cached_function import cached_function
 from ranger.ext.iter_tools import flatten
 
 class ColorScheme(object):
-	"""
-	This is the class that colorschemes must inherit from.
-
-	it defines the get() method, which returns the color tuple
-	which fits to the given keys.
-	"""
-
-	@cached_function
-	def get(self, *keys):
-		"""
-		Returns the (fg, bg, attr) for the given keys.
-
-		Using this function rather than use() will cache all
-		colors for faster access.
-		"""
-		context = Context(keys)
-		color = self.use(context)
-		if len(color) != 3 or not all(isinstance(value, int) \
-				for value in color):
-			raise ValueError("Bad Value from colorscheme.  Need "
-				"a tuple of (foreground_color, background_color, attribute).")
-		return color
-
-	@cached_function
-	def get_attr(self, *keys):
-		"""
-		Returns the curses attribute for the specified keys
-
-		Ready to use for curses.setattr()
-		"""
-		fg, bg, attr = self.get(*flatten(keys))
-		return attr | color_pair(get_color(fg, bg))
-
-	def use(self, context):
-		"""
-		Use the colorscheme to determine the (fg, bg, attr) tuple.
-
-		Override this method in your own colorscheme.
-		"""
-		return (-1, -1, 0)
+    """
+    This is the class that colorschemes must inherit from.
+
+    it defines the get() method, which returns the color tuple
+    which fits to the given keys.
+    """
+
+    @cached_function
+    def get(self, *keys):
+        """
+        Returns the (fg, bg, attr) for the given keys.
+
+        Using this function rather than use() will cache all
+        colors for faster access.
+        """
+        context = Context(keys)
+        color = self.use(context)
+        if len(color) != 3 or not all(isinstance(value, int) \
+                for value in color):
+            raise ValueError("Bad Value from colorscheme.  Need "
+                "a tuple of (foreground_color, background_color, attribute).")
+        return color
+
+    @cached_function
+    def get_attr(self, *keys):
+        """
+        Returns the curses attribute for the specified keys
+
+        Ready to use for curses.setattr()
+        """
+        fg, bg, attr = self.get(*flatten(keys))
+        return attr | color_pair(get_color(fg, bg))
+
+    def use(self, context):
+        """
+        Use the colorscheme to determine the (fg, bg, attr) tuple.
+
+        Override this method in your own colorscheme.
+        """
+        return (-1, -1, 0)
 
 def _colorscheme_name_to_class(signal):
-	# Find the colorscheme.  First look in ~/.config/ranger/colorschemes,
-	# then at RANGERDIR/colorschemes.  If the file contains a class
-	# named Scheme, it is used.  Otherwise, an arbitrary other class
-	# is picked.
-	if isinstance(signal.value, ColorScheme): return
-
-	if not signal.value:
-		signal.value = 'default'
-
-	scheme_name = signal.value
-	usecustom = not ranger.arg.clean
-
-	def exists(colorscheme):
-		return os.path.exists(colorscheme + '.py')
-
-	def is_scheme(x):
-		try:
-			return issubclass(x, ColorScheme)
-		except:
-			return False
-
-	# create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist
-	if usecustom:
-		if os.path.exists(signal.fm.confpath('colorschemes')):
-			initpy = signal.fm.confpath('colorschemes', '__init__.py')
-			if not os.path.exists(initpy):
-				open(initpy, 'a').close()
-
-	if usecustom and \
-			exists(signal.fm.confpath('colorschemes', scheme_name)):
-		scheme_supermodule = 'colorschemes'
-	elif exists(signal.fm.relpath('colorschemes', scheme_name)):
-		scheme_supermodule = 'ranger.colorschemes'
-		usecustom = False
-	else:
-		scheme_supermodule = None  # found no matching file.
-
-	if scheme_supermodule is None:
-		if signal.previous and isinstance(signal.previous, ColorScheme):
-			signal.value = signal.previous
-		else:
-			signal.value = ColorScheme()
-		raise Exception("Cannot locate colorscheme `%s'" % scheme_name)
-	else:
-		if usecustom: allow_access_to_confdir(ranger.arg.confdir, True)
-		scheme_module = getattr(__import__(scheme_supermodule,
-				globals(), locals(), [scheme_name], 0), scheme_name)
-		if usecustom: allow_access_to_confdir(ranger.arg.confdir, False)
-		if hasattr(scheme_module, 'Scheme') \
-				and is_scheme(scheme_module.Scheme):
-			signal.value = scheme_module.Scheme()
-		else:
-			for var in scheme_module.__dict__.values():
-				if var != ColorScheme and is_scheme(var):
-					signal.value = var()
-					break
-			else:
-				raise Exception("The module contains no valid colorscheme!")
+    # Find the colorscheme.  First look in ~/.config/ranger/colorschemes,
+    # then at RANGERDIR/colorschemes.  If the file contains a class
+    # named Scheme, it is used.  Otherwise, an arbitrary other class
+    # is picked.
+    if isinstance(signal.value, ColorScheme): return
+
+    if not signal.value:
+        signal.value = 'default'
+
+    scheme_name = signal.value
+    usecustom = not ranger.arg.clean
+
+    def exists(colorscheme):
+        return os.path.exists(colorscheme + '.py')
+
+    def is_scheme(x):
+        try:
+            return issubclass(x, ColorScheme)
+        except:
+            return False
+
+    # create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist
+    if usecustom:
+        if os.path.exists(signal.fm.confpath('colorschemes')):
+            initpy = signal.fm.confpath('colorschemes', '__init__.py')
+            if not os.path.exists(initpy):
+                open(initpy, 'a').close()
+
+    if usecustom and \
+            exists(signal.fm.confpath('colorschemes', scheme_name)):
+        scheme_supermodule = 'colorschemes'
+    elif exists(signal.fm.relpath('colorschemes', scheme_name)):
+        scheme_supermodule = 'ranger.colorschemes'
+        usecustom = False
+    else:
+        scheme_supermodule = None  # found no matching file.
+
+    if scheme_supermodule is None:
+        if signal.previous and isinstance(signal.previous, ColorScheme):
+            signal.value = signal.previous
+        else:
+            signal.value = ColorScheme()
+        raise Exception("Cannot locate colorscheme `%s'" % scheme_name)
+    else:
+        if usecustom: allow_access_to_confdir(ranger.arg.confdir, True)
+        scheme_module = getattr(__import__(scheme_supermodule,
+                globals(), locals(), [scheme_name], 0), scheme_name)
+        if usecustom: allow_access_to_confdir(ranger.arg.confdir, False)
+        if hasattr(scheme_module, 'Scheme') \
+                and is_scheme(scheme_module.Scheme):
+            signal.value = scheme_module.Scheme()
+        else:
+            for var in scheme_module.__dict__.values():
+                if var != ColorScheme and is_scheme(var):
+                    signal.value = var()
+                    break
+            else:
+                raise Exception("The module contains no valid colorscheme!")
diff --git a/ranger/gui/context.py b/ranger/gui/context.py
index cdccecde..c9e8104e 100644
--- a/ranger/gui/context.py
+++ b/ranger/gui/context.py
@@ -2,28 +2,28 @@
 # This software is distributed under the terms of the GNU GPL version 3.
 
 CONTEXT_KEYS = ['reset', 'error', 'badinfo',
-		'in_browser', 'in_statusbar', 'in_titlebar', 'in_console',
-		'in_pager', 'in_taskview',
-		'directory', 'file', 'hostname',
-		'executable', 'media', 'link', 'fifo', 'socket', 'device',
-		'video', 'audio', 'image', 'media', 'document', 'container',
-		'selected', 'empty', 'main_column', 'message', 'background',
-		'good', 'bad',
-		'space', 'permissions', 'owner', 'group', 'mtime', 'nlink',
-		'scroll', 'all', 'bot', 'top', 'percentage', 'filter',
-		'marked', 'tagged', 'tag_marker', 'cut', 'copied',
-		'help_markup', # COMPAT
-		'seperator', 'key', 'special', 'border', # COMPAT
-		'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded',
-		'keybuffer']
+        'in_browser', 'in_statusbar', 'in_titlebar', 'in_console',
+        'in_pager', 'in_taskview',
+        'directory', 'file', 'hostname',
+        'executable', 'media', 'link', 'fifo', 'socket', 'device',
+        'video', 'audio', 'image', 'media', 'document', 'container',
+        'selected', 'empty', 'main_column', 'message', 'background',
+        'good', 'bad',
+        'space', 'permissions', 'owner', 'group', 'mtime', 'nlink',
+        'scroll', 'all', 'bot', 'top', 'percentage', 'filter',
+        'marked', 'tagged', 'tag_marker', 'cut', 'copied',
+        'help_markup', # COMPAT
+        'seperator', 'key', 'special', 'border', # COMPAT
+        'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded',
+        'keybuffer']
 
 class Context(object):
-	def __init__(self, keys):
-		# set all given keys to True
-		d = self.__dict__
-		for key in keys:
-			d[key] = True
+    def __init__(self, keys):
+        # set all given keys to True
+        d = self.__dict__
+        for key in keys:
+            d[key] = True
 
 # set all keys to False
 for key in CONTEXT_KEYS:
-	setattr(Context, key, False)
+    setattr(Context, key, False)
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index 43b583a6..82640322 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -9,67 +9,67 @@ from ranger.gui.color import get_color
 from ranger.core.shared import SettingsAware
 
 def _fix_surrogates(args):
-	return [isinstance(arg, str) and arg.encode('utf-8', 'surrogateescape')
-			.decode('utf-8', 'replace') or arg for arg in args]
+    return [isinstance(arg, str) and arg.encode('utf-8', 'surrogateescape')
+            .decode('utf-8', 'replace') or arg for arg in args]
 
 class CursesShortcuts(SettingsAware):
-	"""
-	This class defines shortcuts to faciliate operations with curses.
-	color(*keys) -- sets the color associated with the keys from
-		the current colorscheme.
-	color_at(y, x, wid, *keys) -- sets the color at the given position
-	color_reset() -- resets the color to the default
-	addstr(*args) -- failsafe version of self.win.addstr(*args)
-	"""
+    """
+    This class defines shortcuts to faciliate operations with curses.
+    color(*keys) -- sets the color associated with the keys from
+        the current colorscheme.
+    color_at(y, x, wid, *keys) -- sets the color at the given position
+    color_reset() -- resets the color to the default
+    addstr(*args) -- failsafe version of self.win.addstr(*args)
+    """
 
-	def addstr(self, *args):
-		try:
-			self.win.addstr(*args)
-		except:
-			if len(args) > 1:
-				try:
-					self.win.addstr(*_fix_surrogates(args))
-				except:
-					pass
+    def addstr(self, *args):
+        try:
+            self.win.addstr(*args)
+        except:
+            if len(args) > 1:
+                try:
+                    self.win.addstr(*_fix_surrogates(args))
+                except:
+                    pass
 
-	def addnstr(self, *args):
-		try:
-			self.win.addnstr(*args)
-		except:
-			if len(args) > 2:
-				try:
-					self.win.addnstr(*_fix_surrogates(args))
-				except:
-					pass
+    def addnstr(self, *args):
+        try:
+            self.win.addnstr(*args)
+        except:
+            if len(args) > 2:
+                try:
+                    self.win.addnstr(*_fix_surrogates(args))
+                except:
+                    pass
 
-	def addch(self, *args):
-		try:
-			self.win.addch(*args)
-		except:
-			pass
+    def addch(self, *args):
+        try:
+            self.win.addch(*args)
+        except:
+            pass
 
-	def color(self, *keys):
-		"""Change the colors from now on."""
-		attr = self.settings.colorscheme.get_attr(*keys)
-		try:
-			self.win.attrset(attr)
-		except _curses.error:
-			pass
+    def color(self, *keys):
+        """Change the colors from now on."""
+        attr = self.settings.colorscheme.get_attr(*keys)
+        try:
+            self.win.attrset(attr)
+        except _curses.error:
+            pass
 
-	def color_at(self, y, x, wid, *keys):
-		"""Change the colors at the specified position"""
-		attr = self.settings.colorscheme.get_attr(*keys)
-		try:
-			self.win.chgat(y, x, wid, attr)
-		except _curses.error:
-			pass
+    def color_at(self, y, x, wid, *keys):
+        """Change the colors at the specified position"""
+        attr = self.settings.colorscheme.get_attr(*keys)
+        try:
+            self.win.chgat(y, x, wid, attr)
+        except _curses.error:
+            pass
 
-	def set_fg_bg_attr(self, fg, bg, attr):
-		try:
-			self.win.attrset(curses.color_pair(get_color(fg, bg)) | attr)
-		except _curses.error:
-			pass
+    def set_fg_bg_attr(self, fg, bg, attr):
+        try:
+            self.win.attrset(curses.color_pair(get_color(fg, bg)) | attr)
+        except _curses.error:
+            pass
 
-	def color_reset(self):
-		"""Change the colors to the default colors"""
-		CursesShortcuts.color(self, 'reset')
+    def color_reset(self):
+        """Change the colors to the default colors"""
+        CursesShortcuts.color(self, 'reset')
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
index 28526d7a..e5431039 100644
--- a/ranger/gui/displayable.py
+++ b/ranger/gui/displayable.py
@@ -5,311 +5,311 @@ from ranger.core.shared import FileManagerAware, EnvironmentAware
 from ranger.gui.curses_shortcuts import CursesShortcuts
 
 class Displayable(EnvironmentAware, FileManagerAware, CursesShortcuts):
-	"""
-	Displayables are objects which are displayed on the screen.
-
-	This is just the abstract class, defining basic operations
-	such as resizing, printing, changing colors.
-	Subclasses of displayable can extend these methods:
-
-	draw() -- draw the object. Is only called if visible.
-	poke() -- is called just before draw(), even if not visible.
-	finalize() -- called after all objects finished drawing.
-	click(event) -- called with a MouseEvent. This is called on all
-		visible objects under the mouse, until one returns True.
-	press(key) -- called after a key press on focused objects.
-	destroy() -- called before destroying the displayable object
-
-	Additionally, there are these methods:
-
-	__contains__(item) -- is the item (y, x) inside the widget?
-
-	These attributes are set:
-
-	Modifiable:
-		focused -- Focused objects receive press() calls.
-		visible -- Visible objects receive draw() and finalize() calls
-		need_redraw -- Should the widget be redrawn? This variable may
-			be set at various places in the script and should eventually be
-			handled (and unset) in the draw() method.
-
-	Read-Only: (i.e. reccomended not to change manually)
-		win -- the own curses window object
-		parent -- the parent (DisplayableContainer) object or None
-		x, y, wid, hei -- absolute coordinates and boundaries
-		settings, fm -- inherited shared variables
-	"""
-
-	def __init__(self, win, env=None, fm=None, settings=None):
-		from ranger.gui.ui import UI
-		if env is not None:
-			self.env = env
-		if fm is not None:
-			self.fm = fm
-		if settings is not None:
-			self.settings = settings
-
-		self.need_redraw = True
-		self.focused = False
-		self.visible = True
-		self.x = 0
-		self.y = 0
-		self.wid = 0
-		self.hei = 0
-		self.paryx = (0, 0)
-		self.parent = None
-
-		self._old_visible = self.visible
-
-		if win is not None:
-			if isinstance(self, UI):
-				self.win = win
-			else:
-				self.win = win.derwin(1, 1, 0, 0)
-
-	def __nonzero__(self):
-		"""Always True"""
-		return True
-	__bool__ = __nonzero__
-
-	def __contains__(self, item):
-		"""
-		Is item inside the boundaries?
-		item can be an iterable like [y, x] or an object with x and y methods.
-		"""
-		try:
-			y, x = item.y, item.x
-		except AttributeError:
-			try:
-				y, x = item
-			except (ValueError, TypeError):
-				return False
-
-		return self.contains_point(y, x)
-
-	def draw(self):
-		"""
-		Draw the object. Called on every main iteration if visible.
-		Containers should call draw() on their contained objects here.
-		Override this!
-		"""
-
-	def destroy(self):
-		"""
-		Called when the object is destroyed.
-		Override this!
-		"""
-
-	def contains_point(self, y, x):
-		"""
-		Test whether the point (with absolute coordinates) lies
-		within the boundaries of this object.
-		"""
-		return (x >= self.x and x < self.x + self.wid) and \
-				(y >= self.y and y < self.y + self.hei)
-
-	def click(self, event):
-		"""
-		Called when a mouse key is pressed and self.focused is True.
-		Override this!
-		"""
-		pass
-
-	def press(self, key):
-		"""
-		Called when a key is pressed and self.focused is True.
-		Override this!
-		"""
-		pass
-
-	def poke(self):
-		"""Called before drawing, even if invisible"""
-		if self._old_visible != self.visible:
-			self._old_visible = self.visible
-			self.need_redraw = True
-
-			if not self.visible:
-				self.win.erase()
-
-	def finalize(self):
-		"""
-		Called after every displayable is done drawing.
-		Override this!
-		"""
-		pass
-
-	def resize(self, y, x, hei=None, wid=None):
-		"""Resize the widget"""
-		do_move = True
-		try:
-			maxy, maxx = self.fm.ui.termsize
-		except TypeError:
-			pass
-		else:
-			if hei is None:
-				hei = maxy - y
-
-			if wid is None:
-				wid = maxx - x
-
-			if x < 0 or y < 0:
-				self.fm.notify("Warning: Subwindow origin below zero for <%s> "
-					"(x = %d, y = %d)" % (self, x, y), bad=True)
-
-			if x + wid > maxx or y + hei > maxy:
-				self.fm.notify("Warning: Subwindow size out of bounds for <%s> "
-					"(x = %d, y = %d, hei = %d, wid = %d)" % (self,
-					x, y, hei, wid), bad=True)
-
-		window_is_cleared = False
-
-		if hei != self.hei or wid != self.wid:
-			#log("resizing " + str(self))
-			self.win.erase()
-			self.need_redraw = True
-			window_is_cleared = True
-			try:
-				self.win.resize(hei, wid)
-			except:
-				# Not enough space for resizing...
-				try:
-					self.win.mvderwin(0, 0)
-					do_move = True
-					self.win.resize(hei, wid)
-				except:
-					pass
-					#raise ValueError("Resizing Failed!")
-
-			self.hei, self.wid = self.win.getmaxyx()
-
-		if do_move or y != self.paryx[0] or x != self.paryx[1]:
-			if not window_is_cleared:
-				self.win.erase()
-				self.need_redraw = True
-			#log("moving " + str(self))
-			try:
-				self.win.mvderwin(y, x)
-			except:
-				pass
-
-			self.paryx = self.win.getparyx()
-			self.y, self.x = self.paryx
-			if self.parent:
-				self.y += self.parent.y
-				self.x += self.parent.x
-
-	def __str__(self):
-		return self.__class__.__name__
+    """
+    Displayables are objects which are displayed on the screen.
+
+    This is just the abstract class, defining basic operations
+    such as resizing, printing, changing colors.
+    Subclasses of displayable can extend these methods:
+
+    draw() -- draw the object. Is only called if visible.
+    poke() -- is called just before draw(), even if not visible.
+    finalize() -- called after all objects finished drawing.
+    click(event) -- called with a MouseEvent. This is called on all
+        visible objects under the mouse, until one returns True.
+    press(key) -- called after a key press on focused objects.
+    destroy() -- called before destroying the displayable object
+
+    Additionally, there are these methods:
+
+    __contains__(item) -- is the item (y, x) inside the widget?
+
+    These attributes are set:
+
+    Modifiable:
+        focused -- Focused objects receive press() calls.
+        visible -- Visible objects receive draw() and finalize() calls
+        need_redraw -- Should the widget be redrawn? This variable may
+            be set at various places in the script and should eventually be
+            handled (and unset) in the draw() method.
+
+    Read-Only: (i.e. reccomended not to change manually)
+        win -- the own curses window object
+        parent -- the parent (DisplayableContainer) object or None
+        x, y, wid, hei -- absolute coordinates and boundaries
+        settings, fm -- inherited shared variables
+    """
+
+    def __init__(self, win, env=None, fm=None, settings=None):
+        from ranger.gui.ui import UI
+        if env is not None:
+            self.env = env
+        if fm is not None:
+            self.fm = fm
+        if settings is not None:
+            self.settings = settings
+
+        self.need_redraw = True
+        self.focused = False
+        self.visible = True
+        self.x = 0
+        self.y = 0
+        self.wid = 0
+        self.hei = 0
+        self.paryx = (0, 0)
+        self.parent = None
+
+        self._old_visible = self.visible
+
+        if win is not None:
+            if isinstance(self, UI):
+                self.win = win
+            else:
+                self.win = win.derwin(1, 1, 0, 0)
+
+    def __nonzero__(self):
+        """Always True"""
+        return True
+    __bool__ = __nonzero__
+
+    def __contains__(self, item):
+        """
+        Is item inside the boundaries?
+        item can be an iterable like [y, x] or an object with x and y methods.
+        """
+        try:
+            y, x = item.y, item.x
+        except AttributeError:
+            try:
+                y, x = item
+            except (ValueError, TypeError):
+                return False
+
+        return self.contains_point(y, x)
+
+    def draw(self):
+        """
+        Draw the object. Called on every main iteration if visible.
+        Containers should call draw() on their contained objects here.
+        Override this!
+        """
+
+    def destroy(self):
+        """
+        Called when the object is destroyed.
+        Override this!
+        """
+
+    def contains_point(self, y, x):
+        """
+        Test whether the point (with absolute coordinates) lies
+        within the boundaries of this object.
+        """
+        return (x >= self.x and x < self.x + self.wid) and \
+                (y >= self.y and y < self.y + self.hei)
+
+    def click(self, event):
+        """
+        Called when a mouse key is pressed and self.focused is True.
+        Override this!
+        """
+        pass
+
+    def press(self, key):
+        """
+        Called when a key is pressed and self.focused is True.
+        Override this!
+        """
+        pass
+
+    def poke(self):
+        """Called before drawing, even if invisible"""
+        if self._old_visible != self.visible:
+            self._old_visible = self.visible
+            self.need_redraw = True
+
+            if not self.visible:
+                self.win.erase()
+
+    def finalize(self):
+        """
+        Called after every displayable is done drawing.
+        Override this!
+        """
+        pass
+
+    def resize(self, y, x, hei=None, wid=None):
+        """Resize the widget"""
+        do_move = True
+        try:
+            maxy, maxx = self.fm.ui.termsize
+        except TypeError:
+            pass
+        else:
+            if hei is None:
+                hei = maxy - y
+
+            if wid is None:
+                wid = maxx - x
+
+            if x < 0 or y < 0:
+                self.fm.notify("Warning: Subwindow origin below zero for <%s> "
+                    "(x = %d, y = %d)" % (self, x, y), bad=True)
+
+            if x + wid > maxx or y + hei > maxy:
+                self.fm.notify("Warning: Subwindow size out of bounds for <%s> "
+                    "(x = %d, y = %d, hei = %d, wid = %d)" % (self,
+                    x, y, hei, wid), bad=True)
+
+        window_is_cleared = False
+
+        if hei != self.hei or wid != self.wid:
+            #log("resizing " + str(self))
+            self.win.erase()
+            self.need_redraw = True
+            window_is_cleared = True
+            try:
+                self.win.resize(hei, wid)
+            except:
+                # Not enough space for resizing...
+                try:
+                    self.win.mvderwin(0, 0)
+                    do_move = True
+                    self.win.resize(hei, wid)
+                except:
+                    pass
+                    #raise ValueError("Resizing Failed!")
+
+            self.hei, self.wid = self.win.getmaxyx()
+
+        if do_move or y != self.paryx[0] or x != self.paryx[1]:
+            if not window_is_cleared:
+                self.win.erase()
+                self.need_redraw = True
+            #log("moving " + str(self))
+            try:
+                self.win.mvderwin(y, x)
+            except:
+                pass
+
+            self.paryx = self.win.getparyx()
+            self.y, self.x = self.paryx
+            if self.parent:
+                self.y += self.parent.y
+                self.x += self.parent.x
+
+    def __str__(self):
+        return self.__class__.__name__
 
 class DisplayableContainer(Displayable):
-	"""
-	DisplayableContainers are Displayables which contain other Displayables.
-
-	This is also an abstract class. The methods draw, poke, finalize,
-	click, press and destroy are extended here and will recursively
-	call the function on all contained objects.
-
-	New methods:
-
-	add_child(object) -- add the object to the container.
-	remove_child(object) -- remove the object from the container.
-
-	New attributes:
-
-	container -- a list with all contained objects (rw)
-	"""
-
-	def __init__(self, win, env=None, fm=None, settings=None):
-		if env is not None:
-			self.env = env
-		if fm is not None:
-			self.fm = fm
-		if settings is not None:
-			self.settings = settings
-
-		self.container = []
-
-		Displayable.__init__(self, win)
-
-	# ------------------------------------ extended or overidden methods
-
-	def poke(self):
-		"""Recursively called on objects in container"""
-		Displayable.poke(self)
-		for displayable in self.container:
-			displayable.poke()
-
-	def draw(self):
-		"""Recursively called on visible objects in container"""
-		for displayable in self.container:
-			if self.need_redraw:
-				displayable.need_redraw = True
-			if displayable.visible:
-				displayable.draw()
-
-		self.need_redraw = False
-
-	def finalize(self):
-		"""Recursively called on visible objects in container"""
-		for displayable in self.container:
-			if displayable.visible:
-				displayable.finalize()
-
-	def press(self, key):
-		"""Recursively called on objects in container"""
-		focused_obj = self._get_focused_obj()
-
-		if focused_obj:
-			focused_obj.press(key)
-			return True
-		return False
-
-	def click(self, event):
-		"""Recursively called on objects in container"""
-		focused_obj = self._get_focused_obj()
-		if focused_obj and focused_obj.click(event):
-			return True
-
-		for displayable in self.container:
-			if displayable.visible and event in displayable:
-				if displayable.click(event):
-					return True
-
-		return False
-
-	def destroy(self):
-		"""Recursively called on objects in container"""
-		for displayable in self.container:
-			displayable.destroy()
-
-	# ----------------------------------------------- new methods
-
-	def add_child(self, obj):
-		"""Add the objects to the container."""
-		if obj.parent:
-			obj.parent.remove_child(obj)
-		self.container.append(obj)
-		obj.parent = self
-
-	def remove_child(self, obj):
-		"""Remove the object from the container."""
-		try:
-			self.container.remove(obj)
-		except ValueError:
-			pass
-		else:
-			obj.parent = None
-
-	def _get_focused_obj(self):
-		# Finds a focused displayable object in the container.
-		for displayable in self.container:
-			if displayable.focused:
-				return displayable
-			try:
-				obj = displayable._get_focused_obj()
-			except AttributeError:
-				pass
-			else:
-				if obj is not None:
-					return obj
-		return None
+    """
+    DisplayableContainers are Displayables which contain other Displayables.
+
+    This is also an abstract class. The methods draw, poke, finalize,
+    click, press and destroy are extended here and will recursively
+    call the function on all contained objects.
+
+    New methods:
+
+    add_child(object) -- add the object to the container.
+    remove_child(object) -- remove the object from the container.
+
+    New attributes:
+
+    container -- a list with all contained objects (rw)
+    """
+
+    def __init__(self, win, env=None, fm=None, settings=None):
+        if env is not None:
+            self.env = env
+        if fm is not None:
+            self.fm = fm
+        if settings is not None:
+            self.settings = settings
+
+        self.container = []
+
+        Displayable.__init__(self, win)
+
+    # ------------------------------------ extended or overidden methods
+
+    def poke(self):
+        """Recursively called on objects in container"""
+        Displayable.poke(self)
+        for displayable in self.container:
+            displayable.poke()
+
+    def draw(self):
+        """Recursively called on visible objects in container"""
+        for displayable in self.container:
+            if self.need_redraw:
+                displayable.need_redraw = True
+            if displayable.visible:
+                displayable.draw()
+
+        self.need_redraw = False
+
+    def finalize(self):
+        """Recursively called on visible objects in container"""
+        for displayable in self.container:
+            if displayable.visible:
+                displayable.finalize()
+
+    def press(self, key):
+        """Recursively called on objects in container"""
+        focused_obj = self._get_focused_obj()
+
+        if focused_obj:
+            focused_obj.press(key)
+            return True
+        return False
+
+    def click(self, event):
+        """Recursively called on objects in container"""
+        focused_obj = self._get_focused_obj()
+        if focused_obj and focused_obj.click(event):
+            return True
+
+        for displayable in self.container:
+            if displayable.visible and event in displayable:
+                if displayable.click(event):
+                    return True
+
+        return False
+
+    def destroy(self):
+        """Recursively called on objects in container"""
+        for displayable in self.container:
+            displayable.destroy()
+
+    # ----------------------------------------------- new methods
+
+    def add_child(self, obj):
+        """Add the objects to the container."""
+        if obj.parent:
+            obj.parent.remove_child(obj)
+        self.container.append(obj)
+        obj.parent = self
+
+    def remove_child(self, obj):
+        """Remove the object from the container."""
+        try:
+            self.container.remove(obj)
+        except ValueError:
+            pass
+        else:
+            obj.parent = None
+
+    def _get_focused_obj(self):
+        # Finds a focused displayable object in the container.
+        for displayable in self.container:
+            if displayable.focused:
+                return displayable
+            try:
+                obj = displayable._get_focused_obj()
+            except AttributeError:
+                pass
+            else:
+                if obj is not None:
+                    return obj
+        return None
diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py
index ed370e54..1f157570 100644
--- a/ranger/gui/mouse_event.py
+++ b/ranger/gui/mouse_event.py
@@ -4,53 +4,53 @@
 import curses
 
 class MouseEvent(object):
-	PRESSED = [ 0,
-			curses.BUTTON1_PRESSED,
-			curses.BUTTON2_PRESSED,
-			curses.BUTTON3_PRESSED,
-			curses.BUTTON4_PRESSED ]
-	CTRL_SCROLLWHEEL_MULTIPLIER = 5
-
-	def __init__(self, getmouse):
-		"""Creates a MouseEvent object from the result of win.getmouse()"""
-		_, self.x, self.y, _, self.bstate = getmouse
-
-		# x-values above ~220 suddenly became negative, apparently
-		# it's sufficient to add 0xFF to fix that error.
-		if self.x < 0:
-			self.x += 0xFF
-
-		if self.y < 0:
-			self.y += 0xFF
-
-	def pressed(self, n):
-		"""Returns whether the mouse key n is pressed"""
-		try:
-			return (self.bstate & MouseEvent.PRESSED[n]) != 0
-		except:
-			return False
-
-	def mouse_wheel_direction(self):
-		"""Returns the direction of the scroll action, 0 if there was none"""
-		# If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button.
-		# I interpret invalid buttons as "scroll down" because all tested
-		# systems have a broken curses implementation and this is a workaround.
-		if self.bstate & curses.BUTTON4_PRESSED:
-			return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1
-		elif self.bstate & curses.BUTTON2_PRESSED \
-				or self.bstate > curses.ALL_MOUSE_EVENTS:
-			return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1
-		else:
-			return 0
-
-	def ctrl(self):
-		return self.bstate & curses.BUTTON_CTRL
-
-	def alt(self):
-		return self.bstate & curses.BUTTON_ALT
-
-	def shift(self):
-		return self.bstate & curses.BUTTON_SHIFT
-
-	def key_invalid(self):
-		return self.bstate > curses.ALL_MOUSE_EVENTS
+    PRESSED = [ 0,
+            curses.BUTTON1_PRESSED,
+            curses.BUTTON2_PRESSED,
+            curses.BUTTON3_PRESSED,
+            curses.BUTTON4_PRESSED ]
+    CTRL_SCROLLWHEEL_MULTIPLIER = 5
+
+    def __init__(self, getmouse):
+        """Creates a MouseEvent object from the result of win.getmouse()"""
+        _, self.x, self.y, _, self.bstate = getmouse
+
+        # x-values above ~220 suddenly became negative, apparently
+        # it's sufficient to add 0xFF to fix that error.
+        if self.x < 0:
+            self.x += 0xFF
+
+        if self.y < 0:
+            self.y += 0xFF
+
+    def pressed(self, n):
+        """Returns whether the mouse key n is pressed"""
+        try:
+            return (self.bstate & MouseEvent.PRESSED[n]) != 0
+        except:
+            return False
+
+    def mouse_wheel_direction(self):
+        """Returns the direction of the scroll action, 0 if there was none"""
+        # If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button.
+        # I interpret invalid buttons as "scroll down" because all tested
+        # systems have a broken curses implementation and this is a workaround.
+        if self.bstate & curses.BUTTON4_PRESSED:
+            return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1
+        elif self.bstate & curses.BUTTON2_PRESSED \
+                or self.bstate > curses.ALL_MOUSE_EVENTS:
+            return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1
+        else:
+            return 0
+
+    def ctrl(self):
+        return self.bstate & curses.BUTTON_CTRL
+
+    def alt(self):
+        return self.bstate & curses.BUTTON_ALT
+
+    def shift(self):
+        return self.bstate & curses.BUTTON_SHIFT
+
+    def key_invalid(self):
+        return self.bstate > curses.ALL_MOUSE_EVENTS
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index f01b7b17..f35b11bf 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -14,364 +14,364 @@ MOUSEMASK = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION
 
 _ASCII = ''.join(chr(c) for c in range(32, 127))
 def ascii_only(string):
-	return ''.join(c if c in _ASCII else '?' for c in string)
+    return ''.join(c if c in _ASCII else '?' for c in string)
 
 def _setup_mouse(signal):
-	if signal['value']:
-		curses.mousemask(MOUSEMASK)
-		curses.mouseinterval(0)
-
-		## this line solves this problem:
-		## If an action, following a mouse click, includes the
-		## suspension and re-initializion of the ui (e.g. running a
-		## file by clicking on its preview) and the next key is another
-		## mouse click, the bstate of this mouse event will be invalid.
-		## (atm, invalid bstates are recognized as scroll-down)
-		curses.ungetmouse(0,0,0,0,0)
-	else:
-		curses.mousemask(0)
+    if signal['value']:
+        curses.mousemask(MOUSEMASK)
+        curses.mouseinterval(0)
+
+        ## this line solves this problem:
+        ## If an action, following a mouse click, includes the
+        ## suspension and re-initializion of the ui (e.g. running a
+        ## file by clicking on its preview) and the next key is another
+        ## mouse click, the bstate of this mouse event will be invalid.
+        ## (atm, invalid bstates are recognized as scroll-down)
+        curses.ungetmouse(0,0,0,0,0)
+    else:
+        curses.mousemask(0)
 
 # TODO: progress bar
 # TODO: branch view
 class UI(DisplayableContainer):
-	is_set_up = False
-	load_mode = False
-	is_on = False
-	termsize = None
-
-	def __init__(self, env=None, fm=None):
-		self.keybuffer = KeyBuffer()
-		self.keymaps = KeyMaps(self.keybuffer)
-
-		if fm is not None:
-			self.fm = fm
-
-	def setup_curses(self):
-		os.environ['ESCDELAY'] = '25'   # don't know a cleaner way
-		try:
-			self.win = curses.initscr()
-		except _curses.error as e:
-			if e.args[0] == "setupterm: could not find terminal":
-				os.environ['TERM'] = 'linux'
-				self.win = curses.initscr()
-		self.keymaps.use_keymap('browser')
-		DisplayableContainer.__init__(self, None)
-
-	def initialize(self):
-		"""initialize curses, then call setup (at the first time) and resize."""
-		self.win.leaveok(0)
-		self.win.keypad(1)
-		self.load_mode = False
-
-		curses.cbreak()
-		curses.noecho()
-		curses.halfdelay(20)
-		try:
-			curses.curs_set(int(bool(self.settings.show_cursor)))
-		except:
-			pass
-		curses.start_color()
-		curses.use_default_colors()
-
-		self.settings.signal_bind('setopt.mouse_enabled', _setup_mouse)
-		_setup_mouse(dict(value=self.settings.mouse_enabled))
-
-		if not self.is_set_up:
-			self.is_set_up = True
-			self.setup()
-			self.win.addstr("loading...")
-			self.win.refresh()
-			curses.setupterm()
-			self._draw_title = curses.tigetflag('hs') # has_status_line
-		self.update_size()
-		self.is_on = True
-
-		if self.settings.update_tmux_title:
-			sys.stdout.write("\033kranger\033\\")
-			sys.stdout.flush()
-
-	def suspend(self):
-		"""Turn off curses"""
-		self.win.keypad(0)
-		curses.nocbreak()
-		curses.echo()
-		try:
-			curses.curs_set(1)
-		except:
-			pass
-		if self.settings.mouse_enabled:
-			_setup_mouse(dict(value=False))
-		curses.endwin()
-		self.is_on = False
-
-	def set_load_mode(self, boolean):
-		boolean = bool(boolean)
-		if boolean != self.load_mode:
-			self.load_mode = boolean
-
-			if boolean:
-				# don't wait for key presses in the load mode
-				curses.cbreak()
-				self.win.nodelay(1)
-			else:
-				self.win.nodelay(0)
-				curses.halfdelay(20)
-
-	def destroy(self):
-		"""Destroy all widgets and turn off curses"""
-		self.suspend()
-		DisplayableContainer.destroy(self)
-
-	def handle_mouse(self):
-		"""Handles mouse input"""
-		try:
-			event = MouseEvent(curses.getmouse())
-		except _curses.error:
-			return
-		if not self.console.visible:
-			DisplayableContainer.click(self, event)
-
-	def handle_key(self, key):
-		"""Handles key input"""
-
-		if hasattr(self, 'hint'):
-			self.hint()
-
-		if key < 0:
-			self.keybuffer.clear()
-
-		elif not DisplayableContainer.press(self, key):
-			self.keymaps.use_keymap('browser')
-			self.press(key)
-
-	def press(self, key):
-		keybuffer = self.keybuffer
-		self.status.clear_message()
-
-		keybuffer.add(key)
-		self.fm.hide_bookmarks()
-		self.browser.draw_hints = not keybuffer.finished_parsing \
-				and keybuffer.finished_parsing_quantifier
-
-		if keybuffer.result is not None:
-			try:
-				self.fm.execute_console(keybuffer.result,
-						wildcards=keybuffer.wildcards,
-						quantifier=keybuffer.quantifier)
-			finally:
-				if keybuffer.finished_parsing:
-					keybuffer.clear()
-		elif keybuffer.finished_parsing:
-			keybuffer.clear()
-			return False
-		return True
-
-	def handle_keys(self, *keys):
-		for key in keys:
-			self.handle_key(key)
-
-	def handle_input(self):
-		key = self.win.getch()
-		if key is 27 or key >= 128 and key < 256:
-			# Handle special keys like ALT+X or unicode here:
-			keys = [key]
-			previous_load_mode = self.load_mode
-			self.set_load_mode(True)
-			for n in range(4):
-				getkey = self.win.getch()
-				if getkey is not -1:
-					keys.append(getkey)
-			if len(keys) == 1:
-				keys.append(-1)
-			elif keys[0] == 27:
-				keys[0] = ALT_KEY
-			if self.settings.xterm_alt_key:
-				if len(keys) == 2 and keys[1] in range(127, 256):
-					if keys[0] == 195:
-						keys = [ALT_KEY, keys[1] - 64]
-					elif keys[0] == 194:
-						keys = [ALT_KEY, keys[1] - 128]
-			self.handle_keys(*keys)
-			self.set_load_mode(previous_load_mode)
-			if self.settings.flushinput and not self.console.visible:
-				curses.flushinp()
-		else:
-			# Handle simple key presses, CTRL+X, etc here:
-			if key > 0:
-				if self.settings.flushinput and not self.console.visible:
-					curses.flushinp()
-				if key == curses.KEY_MOUSE:
-					self.handle_mouse()
-				elif key == curses.KEY_RESIZE:
-					self.update_size()
-				else:
-					if not self.fm.input_is_blocked():
-						self.handle_key(key)
-
-	def setup(self):
-		"""Build up the UI by initializing widgets."""
-		from ranger.gui.widgets.browserview import BrowserView
-		from ranger.gui.widgets.titlebar import TitleBar
-		from ranger.gui.widgets.console import Console
-		from ranger.gui.widgets.statusbar import StatusBar
-		from ranger.gui.widgets.taskview import TaskView
-		from ranger.gui.widgets.pager import Pager
-
-		# Create a title bar
-		self.titlebar = TitleBar(self.win)
-		self.add_child(self.titlebar)
-
-		# Create the browser view
-		self.browser = BrowserView(self.win, self.settings.column_ratios)
-		self.settings.signal_bind('setopt.column_ratios',
-				self.browser.change_ratios)
-		self.add_child(self.browser)
-
-		# Create the process manager
-		self.taskview = TaskView(self.win)
-		self.taskview.visible = False
-		self.add_child(self.taskview)
-
-		# Create the status bar
-		self.status = StatusBar(self.win, self.browser.main_column)
-		self.add_child(self.status)
-
-		# Create the console
-		self.console = Console(self.win)
-		self.add_child(self.console)
-		self.console.visible = False
-
-		# Create the pager
-		self.pager = Pager(self.win)
-		self.pager.visible = False
-		self.add_child(self.pager)
-
-	def redraw(self):
-		"""Redraw all widgets"""
-		self.poke()
-
-		# determine which widgets are shown
-		if self.console.wait_for_command_input or self.console.question_queue:
-			self.console.focused = True
-			self.console.visible = True
-			self.status.visible = False
-		else:
-			self.console.focused = False
-			self.console.visible = False
-			self.status.visible = True
-
-		self.draw()
-		self.finalize()
-
-	def redraw_window(self):
-		"""Redraw the window. This only calls self.win.redrawwin()."""
-		self.win.erase()
-		self.win.redrawwin()
-		self.win.refresh()
-		self.win.redrawwin()
-		self.need_redraw = True
-
-	def update_size(self):
-		"""resize all widgets"""
-		self.termsize = self.win.getmaxyx()
-		y, x = self.termsize
-
-		self.browser.resize(self.settings.status_bar_on_top and 2 or 1, 0, y - 2, x)
-		self.taskview.resize(1, 0, y - 2, x)
-		self.pager.resize(1, 0, y - 2, x)
-		self.titlebar.resize(0, 0, 1, x)
-		self.status.resize(self.settings.status_bar_on_top and 1 or y-1, 0, 1, x)
-		self.console.resize(y - 1, 0, 1, x)
-
-	def draw(self):
-		"""Draw all objects in the container"""
-		self.win.touchwin()
-		DisplayableContainer.draw(self)
-		if self._draw_title and self.settings.update_title:
-			cwd = self.fm.thisdir.path
-			if cwd.startswith(self.fm.home_path):
-				cwd = '~' + cwd[len(self.fm.home_path):]
-			if self.settings.shorten_title:
-				split = cwd.rsplit(os.sep, self.settings.shorten_title)
-				if os.sep in split[0]:
-					cwd = os.sep.join(split[1:])
-			try:
-				fixed_cwd = cwd.encode('utf-8', 'surrogateescape'). \
-						decode('utf-8', 'replace')
-				sys.stdout.write("%sranger:%s%s" %
-						(curses.tigetstr('tsl').decode('latin-1'), fixed_cwd,
-						 curses.tigetstr('fsl').decode('latin-1')))
-				sys.stdout.flush()
-			except:
-				pass
-
-		self.win.refresh()
-
-	def finalize(self):
-		"""Finalize every object in container and refresh the window"""
-		DisplayableContainer.finalize(self)
-		self.win.refresh()
-
-	def close_pager(self):
-		if self.console.visible:
-			self.console.focused = True
-		self.pager.close()
-		self.pager.visible = False
-		self.pager.focused = False
-		self.browser.visible = True
-
-	def open_pager(self):
-		if self.console.focused:
-			self.console.focused = False
-		self.pager.open()
-		self.pager.visible = True
-		self.pager.focused = True
-		self.browser.visible = False
-		return self.pager
-
-	def open_embedded_pager(self):
-		self.browser.open_pager()
-		for column in self.browser.columns:
-			if column == self.browser.main_column:
-				break
-			column.level_shift(amount=1)
-		return self.browser.pager
-
-	def close_embedded_pager(self):
-		self.browser.close_pager()
-		for column in self.browser.columns:
-			column.level_restore()
-
-	def open_console(self, string='', prompt=None, position=None):
-		if self.console.open(string, prompt=prompt, position=position):
-			self.status.msg = None
-
-	def close_console(self):
-		self.console.close()
-		self.close_pager()
-
-	def open_taskview(self):
-		self.pager.close()
-		self.pager.visible = False
-		self.pager.focused = False
-		self.console.visible = False
-		self.browser.visible = False
-		self.taskview.visible = True
-		self.taskview.focused = True
-
-	def redraw_main_column(self):
-		self.browser.main_column.need_redraw = True
-
-	def close_taskview(self):
-		self.taskview.visible = False
-		self.browser.visible = True
-		self.taskview.focused = False
-
-	def throbber(self, string='.', remove=False):
-		if remove:
-			self.titlebar.throbber = type(self.titlebar).throbber
-		else:
-			self.titlebar.throbber = string
-
-	def hint(self, text=None):
-		self.status.hint = text
+    is_set_up = False
+    load_mode = False
+    is_on = False
+    termsize = None
+
+    def __init__(self, env=None, fm=None):
+        self.keybuffer = KeyBuffer()
+        self.keymaps = KeyMaps(self.keybuffer)
+
+        if fm is not None:
+            self.fm = fm
+
+    def setup_curses(self):
+        os.environ['ESCDELAY'] = '25'   # don't know a cleaner way
+        try:
+            self.win = curses.initscr()
+        except _curses.error as e:
+            if e.args[0] == "setupterm: could not find terminal":
+                os.environ['TERM'] = 'linux'
+                self.win = curses.initscr()
+        self.keymaps.use_keymap('browser')
+        DisplayableContainer.__init__(self, None)
+
+    def initialize(self):
+        """initialize curses, then call setup (at the first time) and resize."""
+        self.win.leaveok(0)
+        self.win.keypad(1)
+        self.load_mode = False
+
+        curses.cbreak()
+        curses.noecho()
+        curses.halfdelay(20)
+        try:
+            curses.curs_set(int(bool(self.settings.show_cursor)))
+        except:
+            pass
+        curses.start_color()
+        curses.use_default_colors()
+
+        self.settings.signal_bind('setopt.mouse_enabled', _setup_mouse)
+        _setup_mouse(dict(value=self.settings.mouse_enabled))
+
+        if not self.is_set_up:
+            self.is_set_up = True
+            self.setup()
+            self.win.addstr("loading...")
+            self.win.refresh()
+            curses.setupterm()
+            self._draw_title = curses.tigetflag('hs') # has_status_line
+        self.update_size()
+        self.is_on = True
+
+        if self.settings.update_tmux_title:
+            sys.stdout.write("\033kranger\033\\")
+            sys.stdout.flush()
+
+    def suspend(self):
+        """Turn off curses"""
+        self.win.keypad(0)
+        curses.nocbreak()
+        curses.echo()
+        try:
+            curses.curs_set(1)
+        except:
+            pass
+        if self.settings.mouse_enabled:
+            _setup_mouse(dict(value=False))
+        curses.endwin()
+        self.is_on = False
+
+    def set_load_mode(self, boolean):
+        boolean = bool(boolean)
+        if boolean != self.load_mode:
+            self.load_mode = boolean
+
+            if boolean:
+                # don't wait for key presses in the load mode
+                curses.cbreak()
+                self.win.nodelay(1)
+            else:
+                self.win.nodelay(0)
+                curses.halfdelay(20)
+
+    def destroy(self):
+        """Destroy all widgets and turn off curses"""
+        self.suspend()
+        DisplayableContainer.destroy(self)
+
+    def handle_mouse(self):
+        """Handles mouse input"""
+        try:
+            event = MouseEvent(curses.getmouse())
+        except _curses.error:
+            return
+        if not self.console.visible:
+            DisplayableContainer.click(self, event)
+
+    def handle_key(self, key):
+        """Handles key input"""
+
+        if hasattr(self, 'hint'):
+            self.hint()
+
+        if key < 0:
+            self.keybuffer.clear()
+
+        elif not DisplayableContainer.press(self, key):
+            self.keymaps.use_keymap('browser')
+            self.press(key)
+
+    def press(self, key):
+        keybuffer = self.keybuffer
+        self.status.clear_message()
+
+        keybuffer.add(key)
+        self.fm.hide_bookmarks()
+        self.browser.draw_hints = not keybuffer.finished_parsing \
+                and keybuffer.finished_parsing_quantifier
+
+        if keybuffer.result is not None:
+            try:
+                self.fm.execute_console(keybuffer.result,
+                        wildcards=keybuffer.wildcards,
+                        quantifier=keybuffer.quantifier)
+            finally:
+                if keybuffer.finished_parsing:
+                    keybuffer.clear()
+        elif keybuffer.finished_parsing:
+            keybuffer.clear()
+            return False
+        return True
+
+    def handle_keys(self, *keys):
+        for key in keys:
+            self.handle_key(key)
+
+    def handle_input(self):
+        key = self.win.getch()
+        if key is 27 or key >= 128 and key < 256:
+            # Handle special keys like ALT+X or unicode here:
+            keys = [key]
+            previous_load_mode = self.load_mode
+            self.set_load_mode(True)
+            for n in range(4):
+                getkey = self.win.getch()
+                if getkey is not -1:
+                    keys.append(getkey)
+            if len(keys) == 1:
+                keys.append(-1)
+            elif keys[0] == 27:
+                keys[0] = ALT_KEY
+            if self.settings.xterm_alt_key:
+                if len(keys) == 2 and keys[1] in range(127, 256):
+                    if keys[0] == 195:
+                        keys = [ALT_KEY, keys[1] - 64]
+                    elif keys[0] == 194:
+                        keys = [ALT_KEY, keys[1] - 128]
+            self.handle_keys(*keys)
+            self.set_load_mode(previous_load_mode)
+            if self.settings.flushinput and not self.console.visible:
+                curses.flushinp()
+        else:
+            # Handle simple key presses, CTRL+X, etc here:
+            if key > 0:
+                if self.settings.flushinput and not self.console.visible:
+                    curses.flushinp()
+                if key == curses.KEY_MOUSE:
+                    self.handle_mouse()
+                elif key == curses.KEY_RESIZE:
+                    self.update_size()
+                else:
+                    if not self.fm.input_is_blocked():
+                        self.handle_key(key)
+
+    def setup(self):
+        """Build up the UI by initializing widgets."""
+        from ranger.gui.widgets.browserview import BrowserView
+        from ranger.gui.widgets.titlebar import TitleBar
+        from ranger.gui.widgets.console import Console
+        from ranger.gui.widgets.statusbar import StatusBar
+        from ranger.gui.widgets.taskview import TaskView
+        from ranger.gui.widgets.pager import Pager
+
+        # Create a title bar
+        self.titlebar = TitleBar(self.win)
+        self.add_child(self.titlebar)
+
+        # Create the browser view
+        self.browser = BrowserView(self.win, self.settings.column_ratios)
+        self.settings.signal_bind('setopt.column_ratios',
+                self.browser.change_ratios)
+        self.add_child(self.browser)
+
+        # Create the process manager
+        self.taskview = TaskView(self.win)
+        self.taskview.visible = False
+        self.add_child(self.taskview)
+
+        # Create the status bar
+        self.status = StatusBar(self.win, self.browser.main_column)
+        self.add_child(self.status)
+
+        # Create the console
+        self.console = Console(self.win)
+        self.add_child(self.console)
+        self.console.visible = False
+
+        # Create the pager
+        self.pager = Pager(self.win)
+        self.pager.visible = False
+        self.add_child(self.pager)
+
+    def redraw(self):
+        """Redraw all widgets"""
+        self.poke()
+
+        # determine which widgets are shown
+        if self.console.wait_for_command_input or self.console.question_queue:
+            self.console.focused = True
+            self.console.visible = True
+            self.status.visible = False
+        else:
+            self.console.focused = False
+            self.console.visible = False
+            self.status.visible = True
+
+        self.draw()
+        self.finalize()
+
+    def redraw_window(self):
+        """Redraw the window. This only calls self.win.redrawwin()."""
+        self.win.erase()
+        self.win.redrawwin()
+        self.win.refresh()
+        self.win.redrawwin()
+        self.need_redraw = True
+
+    def update_size(self):
+        """resize all widgets"""
+        self.termsize = self.win.getmaxyx()
+        y, x = self.termsize
+
+        self.browser.resize(self.settings.status_bar_on_top and 2 or 1, 0, y - 2, x)
+        self.taskview.resize(1, 0, y - 2, x)
+        self.pager.resize(1, 0, y - 2, x)
+        self.titlebar.resize(0, 0, 1, x)
+        self.status.resize(self.settings.status_bar_on_top and 1 or y-1, 0, 1, x)
+        self.console.resize(y - 1, 0, 1, x)
+
+    def draw(self):
+        """Draw all objects in the container"""
+        self.win.touchwin()
+        DisplayableContainer.draw(self)
+        if self._draw_title and self.settings.update_title:
+            cwd = self.fm.thisdir.path
+            if cwd.startswith(self.fm.home_path):
+                cwd = '~' + cwd[len(self.fm.home_path):]
+            if self.settings.shorten_title:
+                split = cwd.rsplit(os.sep, self.settings.shorten_title)
+                if os.sep in split[0]:
+                    cwd = os.sep.join(split[1:])
+            try:
+                fixed_cwd = cwd.encode('utf-8', 'surrogateescape'). \
+                        decode('utf-8', 'replace')
+                sys.stdout.write("%sranger:%s%s" %
+                        (curses.tigetstr('tsl').decode('latin-1'), fixed_cwd,
+                         curses.tigetstr('fsl').decode('latin-1')))
+                sys.stdout.flush()
+            except:
+                pass
+
+        self.win.refresh()
+
+    def finalize(self):
+        """Finalize every object in container and refresh the window"""
+        DisplayableContainer.finalize(self)
+        self.win.refresh()
+
+    def close_pager(self):
+        if self.console.visible:
+            self.console.focused = True
+        self.pager.close()
+        self.pager.visible = False
+        self.pager.focused = False
+        self.browser.visible = True
+
+    def open_pager(self):
+        if self.console.focused:
+            self.console.focused = False
+        self.pager.open()
+        self.pager.visible = True
+        self.pager.focused = True
+        self.browser.visible = False
+        return self.pager
+
+    def open_embedded_pager(self):
+        self.browser.open_pager()
+        for column in self.browser.columns:
+            if column == self.browser.main_column:
+                break
+            column.level_shift(amount=1)
+        return self.browser.pager
+
+    def close_embedded_pager(self):
+        self.browser.close_pager()
+        for column in self.browser.columns:
+            column.level_restore()
+
+    def open_console(self, string='', prompt=None, position=None):
+        if self.console.open(string, prompt=prompt, position=position):
+            self.status.msg = None
+
+    def close_console(self):
+        self.console.close()
+        self.close_pager()
+
+    def open_taskview(self):
+        self.pager.close()
+        self.pager.visible = False
+        self.pager.focused = False
+        self.console.visible = False
+        self.browser.visible = False
+        self.taskview.visible = True
+        self.taskview.focused = True
+
+    def redraw_main_column(self):
+        self.browser.main_column.need_redraw = True
+
+    def close_taskview(self):
+        self.taskview.visible = False
+        self.browser.visible = True
+        self.taskview.focused = False
+
+    def throbber(self, string='.', remove=False):
+        if remove:
+            self.titlebar.throbber = type(self.titlebar).throbber
+        else:
+            self.titlebar.throbber = string
+
+    def hint(self, text=None):
+        self.status.hint = text
diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py
index 82e592ee..2a930b6c 100644
--- a/ranger/gui/widgets/__init__.py
+++ b/ranger/gui/widgets/__init__.py
@@ -1,7 +1,7 @@
 from ranger.gui.displayable import Displayable
 
 class Widget(Displayable):
-	"""
-	The Widget class defines no methods and only exists for
-	classification of widgets.
-	"""
+    """
+    The Widget class defines no methods and only exists for
+    classification of widgets.
+    """
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index 3324d9a8..a6653070 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -13,382 +13,382 @@ from ranger.fsobject import BAD_INFO
 from ranger.ext.widestring import WideString
 
 class BrowserColumn(Pager):
-	main_column = False
-	display_infostring = False
-	scroll_begin = 0
-	target = None
-	last_redraw_time = -1
-	ellipsis = { False: '~', True: '…' }
-
-	old_dir = None
-	old_thisfile = None
-
-	def __init__(self, win, level):
-		"""
-		win = the curses window object of the BrowserView
-		level = what to display?
-
-		level >0 => previews
-		level 0 => current file/directory
-		level <0 => parent directories
-		"""
-		Pager.__init__(self, win)
-		Widget.__init__(self, win)
-		self.level = level
-		self.original_level = level
-
-		self.settings.signal_bind('setopt.display_size_in_main_column',
-				self.request_redraw, weak=True)
-
-	def request_redraw(self):
-		self.need_redraw = True
-
-	def resize(self, y, x, hei, wid):
-		Widget.resize(self, y, x, hei, wid)
-
-	def click(self, event):
-		"""Handle a MouseEvent"""
-		direction = event.mouse_wheel_direction()
-		if not (event.pressed(1) or event.pressed(3) or direction):
-			return False
-
-		if self.target is None:
-			pass
-
-		elif self.target.is_directory:
-			if self.target.accessible and self.target.content_loaded:
-				index = self.scroll_begin + event.y - self.y
-
-				if direction:
-					if self.level == -1:
-						self.fm.move_parent(direction)
-					else:
-						return False
-				elif event.pressed(1):
-					if not self.main_column:
-						self.fm.enter_dir(self.target.path)
-
-					if index < len(self.target):
-						self.fm.move(to=index)
-				elif event.pressed(3):
-					try:
-						clicked_file = self.target.files[index]
-						if clicked_file.is_directory:
-							self.fm.enter_dir(clicked_file.path)
-						elif self.level == 0:
-							self.fm.thisdir.move_to_obj(clicked_file)
-							self.fm.execute_file(clicked_file)
-					except:
-						pass
-
-		else:
-			if self.level > 0 and not direction:
-				self.fm.move(right=0)
-
-		return True
-
-	def execute_curses_batch(self, line, commands):
-		"""
-		Executes a list of "commands" which can be easily cached.
-
-		"commands" is a list of lists.  Each element contains
-		a text and an attribute.  First, the attribute will be
-		set with attrset, then the text is printed.
-
-		Example:
-		execute_curses_batch(0, [["hello ", 0], ["world", curses.A_BOLD]])
-		"""
-		try:
-			self.win.move(line, 0)
-		except:
-			return
-		for entry in commands:
-			text, attr = entry
-			self.addstr(text, attr)
-
-	def has_preview(self):
-		if self.target is None:
-			return False
-
-		if self.target.is_file:
-			if not self.target.has_preview():
-				return False
-
-		if self.target.is_directory:
-			if self.level > 0 and not self.settings.preview_directories:
-				return False
-
-		return True
-
-	def level_shift(self, amount):
-		self.level = self.original_level + amount
-
-	def level_restore(self):
-		self.level = self.original_level
-
-	def poke(self):
-		Widget.poke(self)
-		self.target = self.fm.thistab.at_level(self.level)
-
-	def draw(self):
-		"""Call either _draw_file() or _draw_directory()"""
-		if self.target != self.old_dir:
-			self.need_redraw = True
-			self.old_dir = self.target
-
-		if self.target:  # don't garbage collect this directory please
-			self.target.use()
-
-		if self.target and self.target.is_directory \
-				and (self.level <= 0 or self.settings.preview_directories):
-			if self.target.pointed_obj != self.old_thisfile:
-				self.need_redraw = True
-				self.old_thisfile = self.target.pointed_obj
-
-			if self.target.load_content_if_outdated() \
-			or self.target.sort_if_outdated() \
-			or self.last_redraw_time < self.target.last_update_time:
-				self.need_redraw = True
-
-		if self.need_redraw:
-			self.win.erase()
-			if self.target is None:
-				pass
-			elif self.target.is_file:
-				Pager.open(self)
-				self._draw_file()
-			elif self.target.is_directory:
-				self._draw_directory()
-				Widget.draw(self)
-			self.need_redraw = False
-			self.last_redraw_time = time()
-
-	def _draw_file(self):
-		"""Draw a preview of the file, if the settings allow it"""
-		self.win.move(0, 0)
-		if not self.target.accessible:
-			self.addnstr("not accessible", self.wid)
-			Pager.close(self)
-			return
-
-		if self.target is None or not self.target.has_preview():
-			Pager.close(self)
-			return
-
-		if self.fm.settings.preview_images and self.target.image:
-			self.set_image(self.target.realpath)
-			Pager.draw(self)
-		else:
-			f = self.target.get_preview_source(self.wid, self.hei)
-			if f is None:
-				Pager.close(self)
-			else:
-				self.set_source(f)
-				Pager.draw(self)
-
-	def _draw_directory(self):
-		"""Draw the contents of a directory"""
-		if self.image:
-			self.image = None
-			self.need_clear_image = True
-			Pager.clear_image(self)
-
-		if self.level > 0 and not self.settings.preview_directories:
-			return
-
-		base_color = ['in_browser']
-
-		self.win.move(0, 0)
-
-		if not self.target.content_loaded:
-			self.color(tuple(base_color))
-			self.addnstr("...", self.wid)
-			self.color_reset()
-			return
-
-		if self.main_column:
-			base_color.append('main_column')
-
-		if not self.target.accessible:
-			self.color(tuple(base_color + ['error']))
-			self.addnstr("not accessible", self.wid)
-			self.color_reset()
-			return
-
-		if self.target.empty():
-			self.color(tuple(base_color + ['empty']))
-			self.addnstr("empty", self.wid)
-			self.color_reset()
-			return
-
-		self._set_scroll_begin()
-
-		copied = [f.path for f in self.fm.copy_buffer]
-		ellipsis = self.ellipsis[self.settings.unicode_ellipsis]
-
-		selected_i = self.target.pointer
-		for line in range(self.hei):
-			i = line + self.scroll_begin
-			if line > self.hei:
-				break
-
-			try:
-				drawn = self.target.files[i]
-			except IndexError:
-				break
-
-			tagged = self.fm.tags and drawn.realpath in self.fm.tags
-			if tagged:
-				tagged_marker = self.fm.tags.marker(drawn.realpath)
-			else:
-				tagged_marker = " "
-
-			key = (self.wid, selected_i == i, drawn.marked, self.main_column,
-					drawn.path in copied, tagged_marker, drawn.infostring,
-					self.fm.do_cut)
-
-			if key in drawn.display_data:
-				self.execute_curses_batch(line, drawn.display_data[key])
-				self.color_reset()
-				continue
-
-			display_data = []
-			drawn.display_data[key] = display_data
-
-			if self.display_infostring and drawn.infostring \
-					and self.settings.display_size_in_main_column:
-				infostring = str(drawn.infostring) + " "
-			else:
-				infostring = ""
-
-			this_color = base_color + list(drawn.mimetype_tuple)
-			text = drawn.basename
-
-			space = self.wid - len(infostring)
-			if self.main_column:
-				space -= 2
-			elif self.settings.display_tags_in_all_columns:
-				space -= 1
-
-			if i == selected_i:
-				this_color.append('selected')
-
-			if drawn.marked:
-				this_color.append('marked')
-				if self.main_column or self.settings.display_tags_in_all_columns:
-					text = " " + text
-
-			if tagged:
-				this_color.append('tagged')
-
-			if drawn.is_directory:
-				this_color.append('directory')
-			else:
-				this_color.append('file')
-
-			if drawn.stat:
-				mode = drawn.stat.st_mode
-				if mode & stat.S_IXUSR:
-					this_color.append('executable')
-				if stat.S_ISFIFO(mode):
-					this_color.append('fifo')
-				if stat.S_ISSOCK(mode):
-					this_color.append('socket')
-				if drawn.is_device:
-					this_color.append('device')
-
-			if drawn.path in copied:
-				this_color.append('cut' if self.fm.do_cut else 'copied')
-
-			if drawn.is_link:
-				this_color.append('link')
-				this_color.append(drawn.exists and 'good' or 'bad')
-
-			attr = self.settings.colorscheme.get_attr(*this_color)
-
-			if (self.main_column or self.settings.display_tags_in_all_columns) \
-					and tagged and self.wid > 2:
-				this_color.append('tag_marker')
-				tag_attr = self.settings.colorscheme.get_attr(*this_color)
-				display_data.append([tagged_marker, tag_attr])
-			else:
-				text = " " + text
-				space += 1
-
-			wtext = WideString(text)
-			if len(wtext) > space:
-				wtext = wtext[:max(0, space - 1)] + ellipsis
-			text = str(wtext)
-
-			display_data.append([text, attr])
-
-			padding = self.wid - len(wtext)
-			if tagged and (self.main_column or \
-					self.settings.display_tags_in_all_columns):
-				padding -= 1
-			if infostring:
-				if len(wtext) + 1 + len(infostring) > self.wid:
-					pass
-				else:
-					padding -= len(infostring)
-					padding = max(0, padding)
-					infostring = (" " * padding) + infostring
-					display_data.append([infostring, attr])
-			else:
-				display_data.append([" " * max(0, padding), attr])
-
-			self.execute_curses_batch(line, display_data)
-			self.color_reset()
-
-	def _get_scroll_begin(self):
-		"""Determines scroll_begin (the position of the first displayed file)"""
-		offset = self.settings.scroll_offset
-		dirsize = len(self.target)
-		winsize = self.hei
-		halfwinsize = winsize // 2
-		index = self.target.pointer or 0
-		original = self.target.scroll_begin
-		projected = index - original
-
-		upper_limit = winsize - 1 - offset
-		lower_limit = offset
-
-		if original < 0:
-			return 0
-
-		if dirsize < winsize:
-			return 0
-
-		if halfwinsize < offset:
-			return min( dirsize - winsize, max( 0, index - halfwinsize ))
-
-		if original > dirsize - winsize:
-			self.target.scroll_begin = dirsize - winsize
-			return self._get_scroll_begin()
-
-		if projected < upper_limit and projected > lower_limit:
-			return original
-
-		if projected > upper_limit:
-			return min( dirsize - winsize,
-					original + (projected - upper_limit))
-
-		if projected < upper_limit:
-			return max( 0,
-					original - (lower_limit - projected))
-
-		return original
-
-	def _set_scroll_begin(self):
-		"""Updates the scroll_begin value"""
-		self.scroll_begin = self._get_scroll_begin()
-		self.target.scroll_begin = self.scroll_begin
-
-	def scroll(self, n):
-		"""scroll down by n lines"""
-		self.need_redraw = True
-		self.target.move(down=n)
-		self.target.scroll_begin += 3 * n
-
-	def __str__(self):
-		return self.__class__.__name__ + ' at level ' + str(self.level)
+    main_column = False
+    display_infostring = False
+    scroll_begin = 0
+    target = None
+    last_redraw_time = -1
+    ellipsis = { False: '~', True: '…' }
+
+    old_dir = None
+    old_thisfile = None
+
+    def __init__(self, win, level):
+        """
+        win = the curses window object of the BrowserView
+        level = what to display?
+
+        level >0 => previews
+        level 0 => current file/directory
+        level <0 => parent directories
+        """
+        Pager.__init__(self, win)
+        Widget.__init__(self, win)
+        self.level = level
+        self.original_level = level
+
+        self.settings.signal_bind('setopt.display_size_in_main_column',
+                self.request_redraw, weak=True)
+
+    def request_redraw(self):
+        self.need_redraw = True
+
+    def resize(self, y, x, hei, wid):
+        Widget.resize(self, y, x, hei, wid)
+
+    def click(self, event):
+        """Handle a MouseEvent"""
+        direction = event.mouse_wheel_direction()
+        if not (event.pressed(1) or event.pressed(3) or direction):
+            return False
+
+        if self.target is None:
+            pass
+
+        elif self.target.is_directory:
+            if self.target.accessible and self.target.content_loaded:
+                index = self.scroll_begin + event.y - self.y
+
+                if direction:
+                    if self.level == -1:
+                        self.fm.move_parent(direction)
+                    else:
+                        return False
+                elif event.pressed(1):
+                    if not self.main_column:
+                        self.fm.enter_dir(self.target.path)
+
+                    if index < len(self.target):
+                        self.fm.move(to=index)
+                elif event.pressed(3):
+                    try:
+                        clicked_file = self.target.files[index]
+                        if clicked_file.is_directory:
+                            self.fm.enter_dir(clicked_file.path)
+                        elif self.level == 0:
+                            self.fm.thisdir.move_to_obj(clicked_file)
+                            self.fm.execute_file(clicked_file)
+                    except:
+                        pass
+
+        else:
+            if self.level > 0 and not direction:
+                self.fm.move(right=0)
+
+        return True
+
+    def execute_curses_batch(self, line, commands):
+        """
+        Executes a list of "commands" which can be easily cached.
+
+        "commands" is a list of lists.  Each element contains
+        a text and an attribute.  First, the attribute will be
+        set with attrset, then the text is printed.
+
+        Example:
+        execute_curses_batch(0, [["hello ", 0], ["world", curses.A_BOLD]])
+        """
+        try:
+            self.win.move(line, 0)
+        except:
+            return
+        for entry in commands:
+            text, attr = entry
+            self.addstr(text, attr)
+
+    def has_preview(self):
+        if self.target is None:
+            return False
+
+        if self.target.is_file:
+            if not self.target.has_preview():
+                return False
+
+        if self.target.is_directory:
+            if self.level > 0 and not self.settings.preview_directories:
+                return False
+
+        return True
+
+    def level_shift(self, amount):
+        self.level = self.original_level + amount
+
+    def level_restore(self):
+        self.level = self.original_level
+
+    def poke(self):
+        Widget.poke(self)
+        self.target = self.fm.thistab.at_level(self.level)
+
+    def draw(self):
+        """Call either _draw_file() or _draw_directory()"""
+        if self.target != self.old_dir:
+            self.need_redraw = True
+            self.old_dir = self.target
+
+        if self.target:  # don't garbage collect this directory please
+            self.target.use()
+
+        if self.target and self.target.is_directory \
+                and (self.level <= 0 or self.settings.preview_directories):
+            if self.target.pointed_obj != self.old_thisfile:
+                self.need_redraw = True
+                self.old_thisfile = self.target.pointed_obj
+
+            if self.target.load_content_if_outdated() \
+            or self.target.sort_if_outdated() \
+            or self.last_redraw_time < self.target.last_update_time:
+                self.need_redraw = True
+
+        if self.need_redraw:
+            self.win.erase()
+            if self.target is None:
+                pass
+            elif self.target.is_file:
+                Pager.open(self)
+                self._draw_file()
+            elif self.target.is_directory:
+                self._draw_directory()
+                Widget.draw(self)
+            self.need_redraw = False
+            self.last_redraw_time = time()
+
+    def _draw_file(self):
+        """Draw a preview of the file, if the settings allow it"""
+        self.win.move(0, 0)
+        if not self.target.accessible:
+            self.addnstr("not accessible", self.wid)
+            Pager.close(self)
+            return
+
+        if self.target is None or not self.target.has_preview():
+            Pager.close(self)
+            return
+
+        if self.fm.settings.preview_images and self.target.image:
+            self.set_image(self.target.realpath)
+            Pager.draw(self)
+        else:
+            f = self.target.get_preview_source(self.wid, self.hei)
+            if f is None:
+                Pager.close(self)
+            else:
+                self.set_source(f)
+                Pager.draw(self)
+
+    def _draw_directory(self):
+        """Draw the contents of a directory"""
+        if self.image:
+            self.image = None
+            self.need_clear_image = True
+            Pager.clear_image(self)
+
+        if self.level > 0 and not self.settings.preview_directories:
+            return
+
+        base_color = ['in_browser']
+
+        self.win.move(0, 0)
+
+        if not self.target.content_loaded:
+            self.color(tuple(base_color))
+            self.addnstr("...", self.wid)
+            self.color_reset()
+            return
+
+        if self.main_column:
+            base_color.append('main_column')
+
+        if not self.target.accessible:
+            self.color(tuple(base_color + ['error']))
+            self.addnstr("not accessible", self.wid)
+            self.color_reset()
+            return
+
+        if self.target.empty():
+            self.color(tuple(base_color + ['empty']))
+            self.addnstr("empty", self.wid)
+            self.color_reset()
+            return
+
+        self._set_scroll_begin()
+
+        copied = [f.path for f in self.fm.copy_buffer]
+        ellipsis = self.ellipsis[self.settings.unicode_ellipsis]
+
+        selected_i = self.target.pointer
+        for line in range(self.hei):
+            i = line + self.scroll_begin
+            if line > self.hei:
+                break
+
+            try:
+                drawn = self.target.files[i]
+            except IndexError:
+                break
+
+            tagged = self.fm.tags and drawn.realpath in self.fm.tags
+            if tagged:
+                tagged_marker = self.fm.tags.marker(drawn.realpath)
+            else:
+                tagged_marker = " "
+
+            key = (self.wid, selected_i == i, drawn.marked, self.main_column,
+                    drawn.path in copied, tagged_marker, drawn.infostring,
+                    self.fm.do_cut)
+
+            if key in drawn.display_data:
+                self.execute_curses_batch(line, drawn.display_data[key])
+                self.color_reset()
+                continue
+
+            display_data = []
+            drawn.display_data[key] = display_data
+
+            if self.display_infostring and drawn.infostring \
+                    and self.settings.display_size_in_main_column:
+                infostring = str(drawn.infostring) + " "
+            else:
+                infostring = ""
+
+            this_color = base_color + list(drawn.mimetype_tuple)
+            text = drawn.basename
+
+            space = self.wid - len(infostring)
+            if self.main_column:
+                space -= 2
+            elif self.settings.display_tags_in_all_columns:
+                space -= 1
+
+            if i == selected_i:
+                this_color.append('selected')
+
+            if drawn.marked:
+                this_color.append('marked')
+                if self.main_column or self.settings.display_tags_in_all_columns:
+                    text = " " + text
+
+            if tagged:
+                this_color.append('tagged')
+
+            if drawn.is_directory:
+                this_color.append('directory')
+            else:
+                this_color.append('file')
+
+            if drawn.stat:
+                mode = drawn.stat.st_mode
+                if mode & stat.S_IXUSR:
+                    this_color.append('executable')
+                if stat.S_ISFIFO(mode):
+                    this_color.append('fifo')
+                if stat.S_ISSOCK(mode):
+                    this_color.append('socket')
+                if drawn.is_device:
+                    this_color.append('device')
+
+            if drawn.path in copied:
+                this_color.append('cut' if self.fm.do_cut else 'copied')
+
+            if drawn.is_link:
+                this_color.append('link')
+                this_color.append(drawn.exists and 'good' or 'bad')
+
+            attr = self.settings.colorscheme.get_attr(*this_color)
+
+            if (self.main_column or self.settings.display_tags_in_all_columns) \
+                    and tagged and self.wid > 2:
+                this_color.append('tag_marker')
+                tag_attr = self.settings.colorscheme.get_attr(*this_color)
+                display_data.append([tagged_marker, tag_attr])
+            else:
+                text = " " + text
+                space += 1
+
+            wtext = WideString(text)
+            if len(wtext) > space:
+                wtext = wtext[:max(0, space - 1)] + ellipsis
+            text = str(wtext)
+
+            display_data.append([text, attr])
+
+            padding = self.wid - len(wtext)
+            if tagged and (self.main_column or \
+                    self.settings.display_tags_in_all_columns):
+                padding -= 1
+            if infostring:
+                if len(wtext) + 1 + len(infostring) > self.wid:
+                    pass
+                else:
+                    padding -= len(infostring)
+                    padding = max(0, padding)
+                    infostring = (" " * padding) + infostring
+                    display_data.append([infostring, attr])
+            else:
+                display_data.append([" " * max(0, padding), attr])
+
+            self.execute_curses_batch(line, display_data)
+            self.color_reset()
+
+    def _get_scroll_begin(self):
+        """Determines scroll_begin (the position of the first displayed file)"""
+        offset = self.settings.scroll_offset
+        dirsize = len(self.target)
+        winsize = self.hei
+        halfwinsize = winsize // 2
+        index = self.target.pointer or 0
+        original = self.target.scroll_begin
+        projected = index - original
+
+        upper_limit = winsize - 1 - offset
+        lower_limit = offset
+
+        if original < 0:
+            return 0
+
+        if dirsize < winsize:
+            return 0
+
+        if halfwinsize < offset:
+            return min( dirsize - winsize, max( 0, index - halfwinsize ))
+
+        if original > dirsize - winsize:
+            self.target.scroll_begin = dirsize - winsize
+            return self._get_scroll_begin()
+
+        if projected < upper_limit and projected > lower_limit:
+            return original
+
+        if projected > upper_limit:
+            return min( dirsize - winsize,
+                    original + (projected - upper_limit))
+
+        if projected < upper_limit:
+            return max( 0,
+                    original - (lower_limit - projected))
+
+        return original
+
+    def _set_scroll_begin(self):
+        """Updates the scroll_begin value"""
+        self.scroll_begin = self._get_scroll_begin()
+        self.target.scroll_begin = self.scroll_begin
+
+    def scroll(self, n):
+        """scroll down by n lines"""
+        self.need_redraw = True
+        self.target.move(down=n)
+        self.target.scroll_begin += 3 * n
+
+    def __str__(self):
+        return self.__class__.__name__ + ' at level ' + str(self.level)
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index e4ab2166..742bd9d4 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -11,334 +11,334 @@ from .pager import Pager
 from ..displayable import DisplayableContainer
 
 class BrowserView(Widget, DisplayableContainer):
-	ratios = None
-	preview = True
-	is_collapsed = False
-	draw_bookmarks = False
-	stretch_ratios = None
-	need_clear = False
-	old_collapse = False
-	draw_hints = False
-	draw_info = False
-
-	def __init__(self, win, ratios, preview = True):
-		DisplayableContainer.__init__(self, win)
-		self.preview = preview
-		self.columns = []
-
-		self.pager = Pager(self.win, embedded=True)
-		self.pager.visible = False
-		self.add_child(self.pager)
-
-		self.change_ratios(ratios)
-
-		for option in ('preview_directories', 'preview_files'):
-			self.settings.signal_bind('setopt.' + option,
-					self._request_clear_if_has_borders, weak=True)
-
-		self.fm.signal_bind('move', self.request_clear)
-		self.settings.signal_bind('setopt.column_ratios', self.request_clear)
-
-	def change_ratios(self, ratios):
-		if isinstance(ratios, Signal):
-			ratios = ratios.value
-
-		for column in self.columns:
-			column.destroy()
-			self.remove_child(column)
-		self.columns = []
-
-		ratio_sum = float(sum(ratios))
-		self.ratios = tuple(x / ratio_sum for x in ratios)
-
-		last = 0.1 if self.settings.padding_right else 0
-		if len(self.ratios) >= 2:
-			self.stretch_ratios = self.ratios[:-2] + \
-					((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
-					(self.ratios[-1] * last))
-
-		offset = 1 - len(ratios)
-		if self.preview: offset += 1
-
-		for level in range(len(ratios)):
-			fl = BrowserColumn(self.win, level + offset)
-			self.add_child(fl)
-			self.columns.append(fl)
-
-		try:
-			self.main_column = self.columns[self.preview and -2 or -1]
-		except IndexError:
-			self.main_column = None
-		else:
-			self.main_column.display_infostring = True
-			self.main_column.main_column = True
-
-		self.resize(self.y, self.x, self.hei, self.wid)
-
-	def _request_clear_if_has_borders(self):
-		if self.settings.draw_borders:
-			self.request_clear()
-
-	def request_clear(self):
-		self.need_clear = True
-
-	def draw(self):
-		if self.need_clear:
-			self.win.erase()
-			self.need_redraw = True
-			self.need_clear = False
-		for tab in self.fm.tabs.values():
-			directory = tab.thisdir
-			if directory:
-				directory.load_content_if_outdated()
-				directory.use()
-		DisplayableContainer.draw(self)
-		if self.settings.draw_borders:
-			self._draw_borders()
-		if self.draw_bookmarks:
-			self._draw_bookmarks()
-		elif self.draw_hints:
-			self._draw_hints()
-		elif self.draw_info:
-			self._draw_info(self.draw_info)
-
-	def finalize(self):
-		if self.pager.visible:
-			try:
-				self.fm.ui.win.move(self.main_column.y, self.main_column.x)
-			except:
-				pass
-			self.pager.draw_image()
-		else:
-			try:
-				x = self.main_column.x
-				y = self.main_column.y + self.main_column.target.pointer\
-						- self.main_column.scroll_begin
-				self.fm.ui.win.move(y, x)
-			except:
-				pass
-			self.columns[-1].draw_image()
-
-	def _draw_borders(self):
-		win = self.win
-		self.color('in_browser', 'border')
-
-		left_start = 0
-		right_end = self.wid - 1
-
-		for child in self.columns:
-			if not child.has_preview():
-				left_start = child.x + child.wid
-			else:
-				break
-		if not self.pager.visible:
-			for child in reversed(self.columns):
-				if not child.has_preview():
-					right_end = child.x - 1
-				else:
-					break
-			if right_end < left_start:
-				right_end = self.wid - 1
-
-		try:
-			win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
-			win.hline(self.hei - 1, left_start, curses.ACS_HLINE,
-					right_end - left_start)
-			win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
-		except _curses.error:
-			pass
-
-		for child in self.columns:
-			if not child.has_preview():
-				continue
-			if child.main_column and self.pager.visible:
-				win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
-				break
-			x = child.x + child.wid
-			y = self.hei - 1
-			try:
-				win.vline(1, x, curses.ACS_VLINE, y - 1)
-				win.addch(0, x, curses.ACS_TTEE, 0)
-				win.addch(y, x, curses.ACS_BTEE, 0)
-			except:
-				# in case it's off the boundaries
-				pass
-
-		self.addch(0, left_start, curses.ACS_ULCORNER)
-		self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
-		self.addch(0, right_end, curses.ACS_URCORNER)
-		self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
-
-	def _draw_bookmarks(self):
-		self.fm.bookmarks.update_if_outdated()
-		self.color_reset()
-		self.need_clear = True
-
-		sorted_bookmarks = sorted((item for item in self.fm.bookmarks \
-			if self.fm.settings.show_hidden_bookmarks or \
-			'/.' not in item[1].path), key=lambda t: t[0].lower())
-
-		hei = min(self.hei - 1, len(sorted_bookmarks))
-		ystart = self.hei - hei
-
-		maxlen = self.wid
-		self.addnstr(ystart - 1, 0, "mark  path".ljust(self.wid), self.wid)
-
-		whitespace = " " * maxlen
-		for line, items in zip(range(self.hei-1), sorted_bookmarks):
-			key, mark = items
-			string = " " + key + "   " + mark.path
-			self.addstr(ystart + line, 0, whitespace)
-			self.addnstr(ystart + line, 0, string, self.wid)
-
-		self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
-
-	def _draw_info(self, lines):
-		self.need_clear = True
-		hei = min(self.hei - 1, len(lines))
-		ystart = self.hei - hei
-		i = ystart
-		whitespace = " " * self.wid
-		for line in lines:
-			if i >= self.hei:
-				break
-			self.addstr(i, 0, whitespace)
-			self.addnstr(i, 0, line, self.wid)
-			i += 1
-
-	def _draw_hints(self):
-		self.need_clear = True
-		hints = []
-		for k, v in self.fm.ui.keybuffer.pointer.items():
-			k = key_to_string(k)
-			if isinstance(v, dict):
-				text = '...'
-			else:
-				text = v
-			if text.startswith('hint') or text.startswith('chain hint'):
-				continue
-			hints.append((k, text))
-		hints.sort(key=lambda t: t[1])
-
-		hei = min(self.hei - 1, len(hints))
-		ystart = self.hei - hei
-		self.addnstr(ystart - 1, 0, "key          command".ljust(self.wid),
-				self.wid)
-		try:
-			self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
-		except:
-			pass
-		whitespace = " " * self.wid
-		i = ystart
-		for key, cmd in hints:
-			string = " " + key.ljust(11) + " " + cmd
-			self.addstr(i, 0, whitespace)
-			self.addnstr(i, 0, string, self.wid)
-			i += 1
-
-	def _collapse(self):
-		# Should the last column be cut off? (Because there is no preview)
-		if not self.settings.collapse_preview or not self.preview \
-				or not self.stretch_ratios:
-			return False
-		result = not self.columns[-1].has_preview()
-		target = self.columns[-1].target
-		if not result and target and target.is_file:
-			if target.image and self.fm.settings.preview_images:
-				result = False  # don't collapse when drawing images
-			elif self.fm.settings.preview_script and \
-					self.fm.settings.use_preview_script:
-				try:
-					result = not self.fm.previews[target.realpath]['foundpreview']
-				except:
-					return self.old_collapse
-
-		self.old_collapse = result
-		return result
-
-	def resize(self, y, x, hei, wid):
-		"""Resize all the columns according to the given ratio"""
-		DisplayableContainer.resize(self, y, x, hei, wid)
-		borders = self.settings.draw_borders
-		pad = 1 if borders else 0
-		left = pad
-
-		self.is_collapsed = self._collapse()
-		if self.is_collapsed:
-			generator = enumerate(self.stretch_ratios)
-		else:
-			generator = enumerate(self.ratios)
-
-		last_i = len(self.ratios) - 1
-
-		for i, ratio in generator:
-			wid = int(ratio * self.wid)
-
-			cut_off = self.is_collapsed and not self.settings.padding_right
-			if i == last_i:
-				if not cut_off:
-					wid = int(self.wid - left + 1 - pad)
-				else:
-					self.columns[i].resize(pad, left - 1, hei - pad * 2, 1)
-					self.columns[i].visible = False
-					continue
-
-			if i == last_i - 1:
-				self.pager.resize(pad, left, hei - pad * 2, \
-						max(1, self.wid - left - pad))
-
-				if cut_off:
-					self.columns[i].resize(pad, left, hei - pad * 2, \
-							max(1, self.wid - left - pad))
-					continue
-
-			try:
-				self.columns[i].resize(pad, left, hei - pad * 2, \
-						max(1, wid - 1))
-			except KeyError:
-				pass
-
-			left += wid
-
-	def click(self, event):
-		if DisplayableContainer.click(self, event):
-			return True
-		direction = event.mouse_wheel_direction()
-		if direction:
-			self.main_column.scroll(direction)
-		return False
-
-	def open_pager(self):
-		self.pager.visible = True
-		self.pager.focused = True
-		self.need_clear = True
-		self.pager.open()
-		try:
-			self.columns[-1].visible = False
-			self.columns[-2].visible = False
-		except IndexError:
-			pass
-
-	def close_pager(self):
-		self.pager.visible = False
-		self.pager.focused = False
-		self.need_clear = True
-		self.pager.close()
-		try:
-			self.columns[-1].visible = True
-			self.columns[-2].visible = True
-		except IndexError:
-			pass
-
-	def poke(self):
-		DisplayableContainer.poke(self)
-
-		# Show the preview column when it has a preview but has
-		# been hidden (e.g. because of padding_right = False)
-		if not self.pager.visible and not self.columns[-1].visible and \
-		self.columns[-1].target and self.columns[-1].target.is_directory \
-		or self.columns[-1].has_preview() and not self.pager.visible:
-			self.columns[-1].visible = True
-
-		if self.preview and self.is_collapsed != self._collapse():
-			self.resize(self.y, self.x, self.hei, self.wid)
+    ratios = None
+    preview = True
+    is_collapsed = False
+    draw_bookmarks = False
+    stretch_ratios = None
+    need_clear = False
+    old_collapse = False
+    draw_hints = False
+    draw_info = False
+
+    def __init__(self, win, ratios, preview = True):
+        DisplayableContainer.__init__(self, win)
+        self.preview = preview
+        self.columns = []
+
+        self.pager = Pager(self.win, embedded=True)
+        self.pager.visible = False
+        self.add_child(self.pager)
+
+        self.change_ratios(ratios)
+
+        for option in ('preview_directories', 'preview_files'):
+            self.settings.signal_bind('setopt.' + option,
+                    self._request_clear_if_has_borders, weak=True)
+
+        self.fm.signal_bind('move', self.request_clear)
+        self.settings.signal_bind('setopt.column_ratios', self.request_clear)
+
+    def change_ratios(self, ratios):
+        if isinstance(ratios, Signal):
+            ratios = ratios.value
+
+        for column in self.columns:
+            column.destroy()
+            self.remove_child(column)
+        self.columns = []
+
+        ratio_sum = float(sum(ratios))
+        self.ratios = tuple(x / ratio_sum for x in ratios)
+
+        last = 0.1 if self.settings.padding_right else 0
+        if len(self.ratios) >= 2:
+            self.stretch_ratios = self.ratios[:-2] + \
+                    ((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
+                    (self.ratios[-1] * last))
+
+        offset = 1 - len(ratios)
+        if self.preview: offset += 1
+
+        for level in range(len(ratios)):
+            fl = BrowserColumn(self.win, level + offset)
+            self.add_child(fl)
+            self.columns.append(fl)
+
+        try:
+            self.main_column = self.columns[self.preview and -2 or -1]
+        except IndexError:
+            self.main_column = None
+        else:
+            self.main_column.display_infostring = True
+            self.main_column.main_column = True
+
+        self.resize(self.y, self.x, self.hei, self.wid)
+
+    def _request_clear_if_has_borders(self):
+        if self.settings.draw_borders:
+            self.request_clear()
+
+    def request_clear(self):
+        self.need_clear = True
+
+    def draw(self):
+        if self.need_clear:
+            self.win.erase()
+            self.need_redraw = True
+            self.need_clear = False
+        for tab in self.fm.tabs.values():
+            directory = tab.thisdir
+            if directory:
+                directory.load_content_if_outdated()
+                directory.use()
+        DisplayableContainer.draw(self)
+        if self.settings.draw_borders:
+            self._draw_borders()
+        if self.draw_bookmarks:
+            self._draw_bookmarks()
+        elif self.draw_hints:
+            self._draw_hints()
+        elif self.draw_info:
+            self._draw_info(self.draw_info)
+
+    def finalize(self):
+        if self.pager.visible:
+            try:
+                self.fm.ui.win.move(self.main_column.y, self.main_column.x)
+            except:
+                pass
+            self.pager.draw_image()
+        else:
+            try:
+                x = self.main_column.x
+                y = self.main_column.y + self.main_column.target.pointer\
+                        - self.main_column.scroll_begin
+                self.fm.ui.win.move(y, x)
+            except:
+                pass
+            self.columns[-1].draw_image()
+
+    def _draw_borders(self):
+        win = self.win
+        self.color('in_browser', 'border')
+
+        left_start = 0
+        right_end = self.wid - 1
+
+        for child in self.columns:
+            if not child.has_preview():
+                left_start = child.x + child.wid
+            else:
+                break
+        if not self.pager.visible:
+            for child in reversed(self.columns):
+                if not child.has_preview():
+                    right_end = child.x - 1
+                else:
+                    break
+            if right_end < left_start:
+                right_end = self.wid - 1
+
+        try:
+            win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
+            win.hline(self.hei - 1, left_start, curses.ACS_HLINE,
+                    right_end - left_start)
+            win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
+        except _curses.error:
+            pass
+
+        for child in self.columns:
+            if not child.has_preview():
+                continue
+            if child.main_column and self.pager.visible:
+                win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
+                break
+            x = child.x + child.wid
+            y = self.hei - 1
+            try:
+                win.vline(1, x, curses.ACS_VLINE, y - 1)
+                win.addch(0, x, curses.ACS_TTEE, 0)
+                win.addch(y, x, curses.ACS_BTEE, 0)
+            except:
+                # in case it's off the boundaries
+                pass
+
+        self.addch(0, left_start, curses.ACS_ULCORNER)
+        self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
+        self.addch(0, right_end, curses.ACS_URCORNER)
+        self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
+
+    def _draw_bookmarks(self):
+        self.fm.bookmarks.update_if_outdated()
+        self.color_reset()
+        self.need_clear = True
+
+        sorted_bookmarks = sorted((item for item in self.fm.bookmarks \
+            if self.fm.settings.show_hidden_bookmarks or \
+            '/.' not in item[1].path), key=lambda t: t[0].lower())
+
+        hei = min(self.hei - 1, len(sorted_bookmarks))
+        ystart = self.hei - hei
+
+        maxlen = self.wid
+        self.addnstr(ystart - 1, 0, "mark  path".ljust(self.wid), self.wid)
+
+        whitespace = " " * maxlen
+        for line, items in zip(range(self.hei-1), sorted_bookmarks):
+            key, mark = items
+            string = " " + key + "   " + mark.path
+            self.addstr(ystart + line, 0, whitespace)
+            self.addnstr(ystart + line, 0, string, self.wid)
+
+        self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
+
+    def _draw_info(self, lines):
+        self.need_clear = True
+        hei = min(self.hei - 1, len(lines))
+        ystart = self.hei - hei
+        i = ystart
+        whitespace = " " * self.wid
+        for line in lines:
+            if i >= self.hei:
+                break
+            self.addstr(i, 0, whitespace)
+            self.addnstr(i, 0, line, self.wid)
+            i += 1
+
+    def _draw_hints(self):
+        self.need_clear = True
+        hints = []
+        for k, v in self.fm.ui.keybuffer.pointer.items():
+            k = key_to_string(k)
+            if isinstance(v, dict):
+                text = '...'
+            else:
+                text = v
+            if text.startswith('hint') or text.startswith('chain hint'):
+                continue
+            hints.append((k, text))
+        hints.sort(key=lambda t: t[1])
+
+        hei = min(self.hei - 1, len(hints))
+        ystart = self.hei - hei
+        self.addnstr(ystart - 1, 0, "key          command".ljust(self.wid),
+                self.wid)
+        try:
+            self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
+        except:
+            pass
+        whitespace = " " * self.wid
+        i = ystart
+        for key, cmd in hints:
+            string = " " + key.ljust(11) + " " + cmd
+            self.addstr(i, 0, whitespace)
+            self.addnstr(i, 0, string, self.wid)
+            i += 1
+
+    def _collapse(self):
+        # Should the last column be cut off? (Because there is no preview)
+        if not self.settings.collapse_preview or not self.preview \
+                or not self.stretch_ratios:
+            return False
+        result = not self.columns[-1].has_preview()
+        target = self.columns[-1].target
+        if not result and target and target.is_file:
+            if target.image and self.fm.settings.preview_images:
+                result = False  # don't collapse when drawing images
+            elif self.fm.settings.preview_script and \
+                    self.fm.settings.use_preview_script:
+                try:
+                    result = not self.fm.previews[target.realpath]['foundpreview']
+                except:
+                    return self.old_collapse
+
+        self.old_collapse = result
+        return result
+
+    def resize(self, y, x, hei, wid):
+        """Resize all the columns according to the given ratio"""
+        DisplayableContainer.resize(self, y, x, hei, wid)
+        borders = self.settings.draw_borders
+        pad = 1 if borders else 0
+        left = pad
+
+        self.is_collapsed = self._collapse()
+        if self.is_collapsed:
+            generator = enumerate(self.stretch_ratios)
+        else:
+            generator = enumerate(self.ratios)
+
+        last_i = len(self.ratios) - 1
+
+        for i, ratio in generator:
+            wid = int(ratio * self.wid)
+
+            cut_off = self.is_collapsed and not self.settings.padding_right
+            if i == last_i:
+                if not cut_off:
+                    wid = int(self.wid - left + 1 - pad)
+                else:
+                    self.columns[i].resize(pad, left - 1, hei - pad * 2, 1)
+                    self.columns[i].visible = False
+                    continue
+
+            if i == last_i - 1:
+                self.pager.resize(pad, left, hei - pad * 2, \
+                        max(1, self.wid - left - pad))
+
+                if cut_off:
+                    self.columns[i].resize(pad, left, hei - pad * 2, \
+                            max(1, self.wid - left - pad))
+                    continue
+
+            try:
+                self.columns[i].resize(pad, left, hei - pad * 2, \
+                        max(1, wid - 1))
+            except KeyError:
+                pass
+
+            left += wid
+
+    def click(self, event):
+        if DisplayableContainer.click(self, event):
+            return True
+        direction = event.mouse_wheel_direction()
+        if direction:
+            self.main_column.scroll(direction)
+        return False
+
+    def open_pager(self):
+        self.pager.visible = True
+        self.pager.focused = True
+        self.need_clear = True
+        self.pager.open()
+        try:
+            self.columns[-1].visible = False
+            self.columns[-2].visible = False
+        except IndexError:
+            pass
+
+    def close_pager(self):
+        self.pager.visible = False
+        self.pager.focused = False
+        self.need_clear = True
+        self.pager.close()
+        try:
+            self.columns[-1].visible = True
+            self.columns[-2].visible = True
+        except IndexError:
+            pass
+
+    def poke(self):
+        DisplayableContainer.poke(self)
+
+        # Show the preview column when it has a preview but has
+        # been hidden (e.g. because of padding_right = False)
+        if not self.pager.visible and not self.columns[-1].visible and \
+        self.columns[-1].target and self.columns[-1].target.is_directory \
+        or self.columns[-1].has_preview() and not self.pager.visible:
+            self.columns[-1].visible = True
+
+        if self.preview and self.is_collapsed != self._collapse():
+            self.resize(self.y, self.x, self.hei, self.wid)
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 4d1855f7..f6f433bb 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -17,422 +17,422 @@ from ranger.container.history import History, HistoryEmptyException
 import ranger
 
 class Console(Widget):
-	visible = False
-	last_cursor_mode = None
-	history_search_pattern = None
-	prompt = ':'
-	copy = ''
-	tab_deque = None
-	original_line = None
-	history = None
-	history_backup = None
-	override = None
-	allow_close = False
-	historypath = None
-	wait_for_command_input = False
-	unicode_buffer = ""
-
-	def __init__(self, win):
-		Widget.__init__(self, win)
-		self.clear()
-		self.history = History(self.settings.max_console_history_size)
-		# load history from files
-		if not ranger.arg.clean:
-			self.historypath = self.fm.confpath('history')
-			try:
-				f = open(self.historypath, 'r')
-			except:
-				pass
-			else:
-				for line in f:
-					self.history.add(line[:-1])
-				f.close()
-		self.line = ""
-		self.history_backup = History(self.history)
-
-		# NOTE: the console is considered in the "question mode" when the
-		# question_queue is non-empty.  In that case, the console will draw the
-		# question instead of the regular console, and the input you give is
-		# used to answer the question instead of typing in commands.
-		#
-		# A question is a tuple of (question_string, callback_func,
-		# tuple_of_choices).  callback_func is a function that is called when
-		# the question is answered which gets the answer as an argument.
-		# tuple_of_choices looks like ('y', 'n').  Only one-letter-answers are
-		# currently supported.  Pressing enter uses the first choice whereas
-		# pressing ESC uses the second choice.
-		self.question_queue = []
-
-	def destroy(self):
-		# save history to files
-		if ranger.arg.clean or not self.settings.save_console_history:
-			return
-		if self.historypath:
-			try:
-				f = open(self.historypath, 'w')
-			except:
-				pass
-			else:
-				for entry in self.history_backup:
-					try:
-						f.write(entry + '\n')
-					except UnicodeEncodeError:
-						pass
-				f.close()
-
-	def draw(self):
-		self.win.erase()
-		if self.question_queue:
-			assert isinstance(self.question_queue[0], tuple)
-			assert len(self.question_queue[0]) == 3
-			self.addstr(0, 0, self.question_queue[0][0])
-			return
-
-		self.addstr(0, 0, self.prompt)
-		line = WideString(self.line)
-		overflow = -self.wid + len(self.prompt) + len(line) + 1
-		if overflow > 0: 
-			self.addstr(0, len(self.prompt), str(line[overflow:]))
-		else:
-			self.addstr(0, len(self.prompt), self.line)
-
-	def finalize(self):
-		move = self.fm.ui.win.move
-		if self.question_queue:
-			try:
-				move(self.y, len(self.question_queue[0][0]))
-			except:
-				pass
-		else:
-			try:
-				pos = uwid(self.line[0:self.pos]) + len(self.prompt)
-				move(self.y, self.x + min(self.wid-1, pos))
-			except:
-				pass
-
-	def open(self, string='', prompt=None, position=None):
-		if prompt is not None:
-			assert isinstance(prompt, str)
-			self.prompt = prompt
-		elif 'prompt' in self.__dict__:
-			del self.prompt
-
-		if self.last_cursor_mode is None:
-			try:
-				self.last_cursor_mode = curses.curs_set(1)
-			except:
-				pass
-		self.allow_close = False
-		self.tab_deque = None
-		self.unicode_buffer = ""
-		self.line = string
-		self.history_search_pattern = self.line
-		self.pos = len(string)
-		if position is not None:
-			self.pos = min(self.pos, position)
-		self.history_backup.fast_forward()
-		self.history = History(self.history_backup)
-		self.history.add('')
-		self.wait_for_command_input = True
-		return True
-
-	def close(self, trigger_cancel_function=True):
-		if self.question_queue:
-			question = self.question_queue[0]
-			answers = question[2]
-			if len(answers) >= 2:
-				self._answer_question(answers[1])
-		else:
-			self._close_command_prompt(trigger_cancel_function)
-
-	def _close_command_prompt(self, trigger_cancel_function=True):
-		if trigger_cancel_function:
-			cmd = self._get_cmd(quiet=True)
-			if cmd:
-				try:
-					cmd.cancel()
-				except Exception as error:
-					self.fm.notify(error)
-		if self.last_cursor_mode is not None:
-			try:
-				curses.curs_set(self.last_cursor_mode)
-			except:
-				pass
-			self.last_cursor_mode = None
-		self.fm.hide_console_info()
-		self.add_to_history()
-		self.tab_deque = None
-		self.clear()
-		self.__class__ = Console
-		self.wait_for_command_input = False
-
-	def clear(self):
-		self.pos = 0
-		self.line = ''
-
-	def press(self, key):
-		self.fm.ui.keymaps.use_keymap('console')
-		if not self.fm.ui.press(key):
-			self.type_key(key)
-
-	def _answer_question(self, answer):
-		if not self.question_queue:
-			return False
-		question = self.question_queue[0]
-		text, callback, answers = question
-		if answer in answers:
-			self.question_queue.pop(0)
-			callback(answer)
-			return True
-		return False
-
-	def type_key(self, key):
-		self.tab_deque = None
-
-		line = "" if self.question_queue else self.line
-		result = self._add_character(key, self.unicode_buffer, line, self.pos)
-		if result[1] == line:
-			# line didn't change, so we don't need to do anything, just update
-			# the unicode _buffer.
-			self.unicode_buffer = result[0]
-			return
-
-		if self.question_queue:
-			self.unicode_buffer, answer, self.pos = result
-			self._answer_question(answer)
-		else:
-			self.unicode_buffer, self.line, self.pos = result
-			self.on_line_change()
-
-	def _add_character(self, key, unicode_buffer, line, pos):
-		# Takes the pressed key, a string "unicode_buffer" containing a
-		# potentially incomplete unicode character, the current line and the
-		# position of the cursor inside the line.
-		# This function returns the new unicode buffer, the modified line and
-		# position.
-		if isinstance(key, int):
-			try:
-				key = chr(key)
-			except ValueError:
-				return unicode_buffer, line, pos
-
-		if self.fm.py3:
-			unicode_buffer += key
-			try:
-				decoded = unicode_buffer.encode("latin-1").decode("utf-8")
-			except UnicodeDecodeError:
-				return unicode_buffer, line, pos
-			except UnicodeEncodeError:
-				return unicode_buffer, line, pos
-			else:
-				unicode_buffer = ""
-				if pos == len(line):
-					line += decoded
-				else:
-					line = line[:pos] + decoded + line[pos:]
-				pos += len(decoded)
-		else:
-			if pos == len(line):
-				line += key
-			else:
-				line = line[:pos] + key + line[pos:]
-			pos += len(key)
-		return unicode_buffer, line, pos
-
-	def history_move(self, n):
-		try:
-			current = self.history.current()
-		except HistoryEmptyException:
-			pass
-		else:
-			if self.line != current and self.line != self.history.top():
-				self.history.modify(self.line)
-			if self.history_search_pattern:
-				self.history.search(self.history_search_pattern, n)
-			else:
-				self.history.move(n)
-			current = self.history.current()
-			if self.line != current:
-				self.line = self.history.current()
-				self.pos = len(self.line)
-
-	def add_to_history(self):
-		self.history_backup.fast_forward()
-		self.history_backup.add(self.line)
-		self.history = History(self.history_backup)
-
-	def move(self, **keywords):
-		direction = Direction(keywords)
-		if direction.horizontal():
-			# Ensure that the pointer is moved utf-char-wise
-			if self.fm.py3:
-				self.pos = direction.move(
-						direction=direction.right(),
-						minimum=0,
-						maximum=len(self.line) + 1,
-						current=self.pos)
-			else:
-				if self.fm.py3:
-					uc = list(self.line)
-					upos = len(self.line[:self.pos])
-				else:
-					uc = list(self.line.decode('utf-8', 'ignore'))
-					upos = len(self.line[:self.pos].decode('utf-8', 'ignore'))
-				newupos = direction.move(
-						direction=direction.right(),
-						minimum=0,
-						maximum=len(uc) + 1,
-						current=upos)
-				self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore'))
-
-	def delete_rest(self, direction):
-		self.tab_deque = None
-		if direction > 0:
-			self.copy = self.line[self.pos:]
-			self.line = self.line[:self.pos]
-		else:
-			self.copy = self.line[:self.pos]
-			self.line = self.line[self.pos:]
-			self.pos = 0
-		self.on_line_change()
-
-	def paste(self):
-		if self.pos == len(self.line):
-			self.line += self.copy
-		else:
-			self.line = self.line[:self.pos] + self.copy + self.line[self.pos:]
-		self.pos += len(self.copy)
-		self.on_line_change()
-
-	def delete_word(self, backward=True):
-		if self.line:
-			self.tab_deque = None
-			if backward:
-				right_part = self.line[self.pos:]
-				i = self.pos - 2
-				while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
-					i -= 1
-				self.copy = self.line[i + 1:self.pos]
-				self.line = self.line[:i + 1] + right_part
-				self.pos = i + 1
-			else:
-				left_part = self.line[:self.pos]
-				i = self.pos + 1
-				while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U):
-					i += 1
-				self.copy = self.line[self.pos:i]
-				if i >= len(self.line):
-					self.line = left_part
-					self.pos = len(self.line)
-				else:
-					self.line = left_part + self.line[i:]
-					self.pos = len(left_part)
-			self.on_line_change()
-
-	def delete(self, mod):
-		self.tab_deque = None
-		if mod == -1 and self.pos == 0:
-			if not self.line:
-				self.close(trigger_cancel_function=False)
-			return
-		# Delete utf-char-wise
-		if self.fm.py3:
-			left_part = self.line[:self.pos + mod]
-			self.pos = len(left_part)
-			self.line = left_part + self.line[self.pos + 1:]
-		else:
-			uc = list(self.line.decode('utf-8', 'ignore'))
-			upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod
-			left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore')
-			self.pos = len(left_part)
-			self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore')
-		self.on_line_change()
-
-	def execute(self, cmd=None):
-		if self.question_queue and cmd is None:
-			question = self.question_queue[0]
-			answers = question[2]
-			if len(answers) >= 1:
-				self._answer_question(answers[0])
-			else:
-				self.question_queue.pop(0)
-			return
-
-		self.allow_close = True
-		self.fm.execute_console(self.line)
-		if self.allow_close:
-			self._close_command_prompt(trigger_cancel_function=False)
-
-	def _get_cmd(self, quiet=False):
-		try:
-			command_class = self._get_cmd_class()
-		except KeyError:
-			if not quiet:
-				error = "Command not found: `%s'" % self.line.split()[0]
-				self.fm.notify(error, bad=True)
-		except:
-			return None
-		else:
-			return command_class(self.line)
-
-	def _get_cmd_class(self):
-		return self.fm.commands.get_command(self.line.split()[0])
-
-	def _get_tab(self):
-		if ' ' in self.line:
-			cmd = self._get_cmd()
-			if cmd:
-				return cmd.tab()
-			else:
-				return None
-
-		return self.fm.commands.command_generator(self.line)
-
-	def tab(self, n=1):
-		if self.tab_deque is None:
-			tab_result = self._get_tab()
-
-			if isinstance(tab_result, str):
-				self.line = tab_result
-				self.pos = len(tab_result)
-				self.on_line_change()
-
-			elif tab_result == None:
-				pass
-
-			elif hasattr(tab_result, '__iter__'):
-				self.tab_deque = deque(tab_result)
-				self.tab_deque.appendleft(self.line)
-
-		if self.tab_deque is not None:
-			self.tab_deque.rotate(-n)
-			self.line = self.tab_deque[0]
-			self.pos = len(self.line)
-			self.on_line_change()
-
-	def on_line_change(self):
-		self.history_search_pattern = self.line
-		try:
-			cls = self._get_cmd_class()
-		except (KeyError, ValueError, IndexError):
-			pass
-		else:
-			cmd = cls(self.line)
-			if cmd and cmd.quick():
-				self.execute(cmd)
-
-	def ask(self, text, callback, choices=['y', 'n']):
-		"""
-		Open a question prompt with predefined choices
-
-		The "text" is displayed as the question text and should include a list
-		of possible keys that the user can type.  The "callback" is a function
-		that is called when the question is answered.  It only gets the answer
-		as an argument.  "choices" is a tuple of one-letter strings that can be
-		typed in by the user.  Every other input gets ignored, except <Enter>
-		and <ESC>.
-
-		The first choice is used when the user presses <Enter>, the second
-		choice is used when the user presses <ESC>.
-		"""
-		self.question_queue.append((text, callback, choices))
+    visible = False
+    last_cursor_mode = None
+    history_search_pattern = None
+    prompt = ':'
+    copy = ''
+    tab_deque = None
+    original_line = None
+    history = None
+    history_backup = None
+    override = None
+    allow_close = False
+    historypath = None
+    wait_for_command_input = False
+    unicode_buffer = ""
+
+    def __init__(self, win):
+        Widget.__init__(self, win)
+        self.clear()
+        self.history = History(self.settings.max_console_history_size)
+        # load history from files
+        if not ranger.arg.clean:
+            self.historypath = self.fm.confpath('history')
+            try:
+                f = open(self.historypath, 'r')
+            except:
+                pass
+            else:
+                for line in f:
+                    self.history.add(line[:-1])
+                f.close()
+        self.line = ""
+        self.history_backup = History(self.history)
+
+        # NOTE: the console is considered in the "question mode" when the
+        # question_queue is non-empty.  In that case, the console will draw the
+        # question instead of the regular console, and the input you give is
+        # used to answer the question instead of typing in commands.
+        #
+        # A question is a tuple of (question_string, callback_func,
+        # tuple_of_choices).  callback_func is a function that is called when
+        # the question is answered which gets the answer as an argument.
+        # tuple_of_choices looks like ('y', 'n').  Only one-letter-answers are
+        # currently supported.  Pressing enter uses the first choice whereas
+        # pressing ESC uses the second choice.
+        self.question_queue = []
+
+    def destroy(self):
+        # save history to files
+        if ranger.arg.clean or not self.settings.save_console_history:
+            return
+        if self.historypath:
+            try:
+                f = open(self.historypath, 'w')
+            except:
+                pass
+            else:
+                for entry in self.history_backup:
+                    try:
+                        f.write(entry + '\n')
+                    except UnicodeEncodeError:
+                        pass
+                f.close()
+
+    def draw(self):
+        self.win.erase()
+        if self.question_queue:
+            assert isinstance(self.question_queue[0], tuple)
+            assert len(self.question_queue[0]) == 3
+            self.addstr(0, 0, self.question_queue[0][0])
+            return
+
+        self.addstr(0, 0, self.prompt)
+        line = WideString(self.line)
+        overflow = -self.wid + len(self.prompt) + len(line) + 1
+        if overflow > 0: 
+            self.addstr(0, len(self.prompt), str(line[overflow:]))
+        else:
+            self.addstr(0, len(self.prompt), self.line)
+
+    def finalize(self):
+        move = self.fm.ui.win.move
+        if self.question_queue:
+            try:
+                move(self.y, len(self.question_queue[0][0]))
+            except:
+                pass
+        else:
+            try:
+                pos = uwid(self.line[0:self.pos]) + len(self.prompt)
+                move(self.y, self.x + min(self.wid-1, pos))
+            except:
+                pass
+
+    def open(self, string='', prompt=None, position=None):
+        if prompt is not None:
+            assert isinstance(prompt, str)
+            self.prompt = prompt
+        elif 'prompt' in self.__dict__:
+            del self.prompt
+
+        if self.last_cursor_mode is None:
+            try:
+                self.last_cursor_mode = curses.curs_set(1)
+            except:
+                pass
+        self.allow_close = False
+        self.tab_deque = None
+        self.unicode_buffer = ""
+        self.line = string
+        self.history_search_pattern = self.line
+        self.pos = len(string)
+        if position is not None:
+            self.pos = min(self.pos, position)
+        self.history_backup.fast_forward()
+        self.history = History(self.history_backup)
+        self.history.add('')
+        self.wait_for_command_input = True
+        return True
+
+    def close(self, trigger_cancel_function=True):
+        if self.question_queue:
+            question = self.question_queue[0]
+            answers = question[2]
+            if len(answers) >= 2:
+                self._answer_question(answers[1])
+        else:
+            self._close_command_prompt(trigger_cancel_function)
+
+    def _close_command_prompt(self, trigger_cancel_function=True):
+        if trigger_cancel_function:
+            cmd = self._get_cmd(quiet=True)
+            if cmd:
+                try:
+                    cmd.cancel()
+                except Exception as error:
+                    self.fm.notify(error)
+        if self.last_cursor_mode is not None:
+            try:
+                curses.curs_set(self.last_cursor_mode)
+            except:
+                pass
+            self.last_cursor_mode = None
+        self.fm.hide_console_info()
+        self.add_to_history()
+        self.tab_deque = None
+        self.clear()
+        self.__class__ = Console
+        self.wait_for_command_input = False
+
+    def clear(self):
+        self.pos = 0
+        self.line = ''
+
+    def press(self, key):
+        self.fm.ui.keymaps.use_keymap('console')
+        if not self.fm.ui.press(key):
+            self.type_key(key)
+
+    def _answer_question(self, answer):
+        if not self.question_queue:
+            return False
+        question = self.question_queue[0]
+        text, callback, answers = question
+        if answer in answers:
+            self.question_queue.pop(0)
+            callback(answer)
+            return True
+        return False
+
+    def type_key(self, key):
+        self.tab_deque = None
+
+        line = "" if self.question_queue else self.line
+        result = self._add_character(key, self.unicode_buffer, line, self.pos)
+        if result[1] == line:
+            # line didn't change, so we don't need to do anything, just update
+            # the unicode _buffer.
+            self.unicode_buffer = result[0]
+            return
+
+        if self.question_queue:
+            self.unicode_buffer, answer, self.pos = result
+            self._answer_question(answer)
+        else:
+            self.unicode_buffer, self.line, self.pos = result
+            self.on_line_change()
+
+    def _add_character(self, key, unicode_buffer, line, pos):
+        # Takes the pressed key, a string "unicode_buffer" containing a
+        # potentially incomplete unicode character, the current line and the
+        # position of the cursor inside the line.
+        # This function returns the new unicode buffer, the modified line and
+        # position.
+        if isinstance(key, int):
+            try:
+                key = chr(key)
+            except ValueError:
+                return unicode_buffer, line, pos
+
+        if self.fm.py3:
+            unicode_buffer += key
+            try:
+                decoded = unicode_buffer.encode("latin-1").decode("utf-8")
+            except UnicodeDecodeError:
+                return unicode_buffer, line, pos
+            except UnicodeEncodeError:
+                return unicode_buffer, line, pos
+            else:
+                unicode_buffer = ""
+                if pos == len(line):
+                    line += decoded
+                else:
+                    line = line[:pos] + decoded + line[pos:]
+                pos += len(decoded)
+        else:
+            if pos == len(line):
+                line += key
+            else:
+                line = line[:pos] + key + line[pos:]
+            pos += len(key)
+        return unicode_buffer, line, pos
+
+    def history_move(self, n):
+        try:
+            current = self.history.current()
+        except HistoryEmptyException:
+            pass
+        else:
+            if self.line != current and self.line != self.history.top():
+                self.history.modify(self.line)
+            if self.history_search_pattern:
+                self.history.search(self.history_search_pattern, n)
+            else:
+                self.history.move(n)
+            current = self.history.current()
+            if self.line != current:
+                self.line = self.history.current()
+                self.pos = len(self.line)
+
+    def add_to_history(self):
+        self.history_backup.fast_forward()
+        self.history_backup.add(self.line)
+        self.history = History(self.history_backup)
+
+    def move(self, **keywords):
+        direction = Direction(keywords)
+        if direction.horizontal():
+            # Ensure that the pointer is moved utf-char-wise
+            if self.fm.py3:
+                self.pos = direction.move(
+                        direction=direction.right(),
+                        minimum=0,
+                        maximum=len(self.line) + 1,
+                        current=self.pos)
+            else:
+                if self.fm.py3:
+                    uc = list(self.line)
+                    upos = len(self.line[:self.pos])
+                else:
+                    uc = list(self.line.decode('utf-8', 'ignore'))
+                    upos = len(self.line[:self.pos].decode('utf-8', 'ignore'))
+                newupos = direction.move(
+                        direction=direction.right(),
+                        minimum=0,
+                        maximum=len(uc) + 1,
+                        current=upos)
+                self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore'))
+
+    def delete_rest(self, direction):
+        self.tab_deque = None
+        if direction > 0:
+            self.copy = self.line[self.pos:]
+            self.line = self.line[:self.pos]
+        else:
+            self.copy = self.line[:self.pos]
+            self.line = self.line[self.pos:]
+            self.pos = 0
+        self.on_line_change()
+
+    def paste(self):
+        if self.pos == len(self.line):
+            self.line += self.copy
+        else:
+            self.line = self.line[:self.pos] + self.copy + self.line[self.pos:]
+        self.pos += len(self.copy)
+        self.on_line_change()
+
+    def delete_word(self, backward=True):
+        if self.line:
+            self.tab_deque = None
+            if backward:
+                right_part = self.line[self.pos:]
+                i = self.pos - 2
+                while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
+                    i -= 1
+                self.copy = self.line[i + 1:self.pos]
+                self.line = self.line[:i + 1] + right_part
+                self.pos = i + 1
+            else:
+                left_part = self.line[:self.pos]
+                i = self.pos + 1
+                while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U):
+                    i += 1
+                self.copy = self.line[self.pos:i]
+                if i >= len(self.line):
+                    self.line = left_part
+                    self.pos = len(self.line)
+                else:
+                    self.line = left_part + self.line[i:]
+                    self.pos = len(left_part)
+            self.on_line_change()
+
+    def delete(self, mod):
+        self.tab_deque = None
+        if mod == -1 and self.pos == 0:
+            if not self.line:
+                self.close(trigger_cancel_function=False)
+            return
+        # Delete utf-char-wise
+        if self.fm.py3:
+            left_part = self.line[:self.pos + mod]
+            self.pos = len(left_part)
+            self.line = left_part + self.line[self.pos + 1:]
+        else:
+            uc = list(self.line.decode('utf-8', 'ignore'))
+            upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod
+            left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore')
+            self.pos = len(left_part)
+            self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore')
+        self.on_line_change()
+
+    def execute(self, cmd=None):
+        if self.question_queue and cmd is None:
+            question = self.question_queue[0]
+            answers = question[2]
+            if len(answers) >= 1:
+                self._answer_question(answers[0])
+            else:
+                self.question_queue.pop(0)
+            return
+
+        self.allow_close = True
+        self.fm.execute_console(self.line)
+        if self.allow_close:
+            self._close_command_prompt(trigger_cancel_function=False)
+
+    def _get_cmd(self, quiet=False):
+        try:
+            command_class = self._get_cmd_class()
+        except KeyError:
+            if not quiet:
+                error = "Command not found: `%s'" % self.line.split()[0]
+                self.fm.notify(error, bad=True)
+        except:
+            return None
+        else:
+            return command_class(self.line)
+
+    def _get_cmd_class(self):
+        return self.fm.commands.get_command(self.line.split()[0])
+
+    def _get_tab(self):
+        if ' ' in self.line:
+            cmd = self._get_cmd()
+            if cmd:
+                return cmd.tab()
+            else:
+                return None
+
+        return self.fm.commands.command_generator(self.line)
+
+    def tab(self, n=1):
+        if self.tab_deque is None:
+            tab_result = self._get_tab()
+
+            if isinstance(tab_result, str):
+                self.line = tab_result
+                self.pos = len(tab_result)
+                self.on_line_change()
+
+            elif tab_result == None:
+                pass
+
+            elif hasattr(tab_result, '__iter__'):
+                self.tab_deque = deque(tab_result)
+                self.tab_deque.appendleft(self.line)
+
+        if self.tab_deque is not None:
+            self.tab_deque.rotate(-n)
+            self.line = self.tab_deque[0]
+            self.pos = len(self.line)
+            self.on_line_change()
+
+    def on_line_change(self):
+        self.history_search_pattern = self.line
+        try:
+            cls = self._get_cmd_class()
+        except (KeyError, ValueError, IndexError):
+            pass
+        else:
+            cmd = cls(self.line)
+            if cmd and cmd.quick():
+                self.execute(cmd)
+
+    def ask(self, text, callback, choices=['y', 'n']):
+        """
+        Open a question prompt with predefined choices
+
+        The "text" is displayed as the question text and should include a list
+        of possible keys that the user can type.  The "callback" is a function
+        that is called when the question is answered.  It only gets the answer
+        as an argument.  "choices" is a tuple of one-letter strings that can be
+        typed in by the user.  Every other input gets ignored, except <Enter>
+        and <ESC>.
+
+        The first choice is used when the user presses <Enter>, the second
+        choice is used when the user presses <ESC>.
+        """
+        self.question_queue.append((text, callback, choices))
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index 6ebde5a9..e9ce8b5c 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -12,202 +12,202 @@ import ranger.ext.img_display as img_display
 
 # TODO: Scrolling in embedded pager
 class Pager(Widget):
-	source = None
-	source_is_stream = False
-
-	old_source = None
-	old_scroll_begin = 0
-	old_startx = 0
-	need_clear_image = False
-	need_redraw_image = False
-	max_width = None
-	def __init__(self, win, embedded=False):
-		Widget.__init__(self, win)
-		self.embedded = embedded
-		self.scroll_begin = 0
-		self.startx = 0
-		self.markup = None
-		self.lines = []
-		self.image = None
-
-	def open(self):
-		self.scroll_begin = 0
-		self.markup = None
-		self.max_width = 0
-		self.startx = 0
-		self.need_redraw = True
-
-	def clear_image(self):
-		if self.need_clear_image:
-			img_display.clear(self.x, self.y, self.wid, self.hei)
-			self.need_clear_image = False
-
-	def close(self):
-		if self.image:
-			self.need_clear_image = True
-			self.clear_image()
-		if self.source and self.source_is_stream:
-			self.source.close()
-
-	def finalize(self):
-		self.fm.ui.win.move(self.y, self.x)
-
-	def draw(self):
-		if self.need_clear_image:
-			self.need_redraw = True
-
-		if self.old_source != self.source:
-			self.old_source = self.source
-			self.need_redraw = True
-
-		if self.old_scroll_begin != self.scroll_begin or \
-				self.old_startx != self.startx:
-			self.old_startx = self.startx
-			self.old_scroll_begin = self.scroll_begin
-
-		if self.need_redraw:
-			self.need_redraw_image = True
-			self.clear_image()
-
-			if not self.image:
-				line_gen = self._generate_lines(
-						starty=self.scroll_begin, startx=self.startx)
-
-				for line, i in zip(line_gen, range(self.hei)):
-					self._draw_line(i, line)
-
-			self.need_redraw = False
-
-	def draw_image(self):
-		if self.image and self.need_redraw_image:
-			self.source = None
-			self.need_redraw_image = False
-			try:
-				img_display.draw(self.image, self.x, self.y, self.wid, self.hei)
-			except Exception as e:
-				self.fm.notify(e, bad=True)
-
-	def _draw_line(self, i, line):
-		if self.markup is None:
-			self.addstr(i, 0, line)
-		elif self.markup == 'ansi':
-			try:
-				self.win.move(i, 0)
-			except:
-				pass
-			else:
-				for chunk in ansi.text_with_fg_bg_attr(line):
-					if isinstance(chunk, tuple):
-						self.set_fg_bg_attr(*chunk)
-					else:
-						self.addstr(chunk)
-
-	def move(self, narg=None, **kw):
-		direction = Direction(kw)
-		if direction.horizontal():
-			self.startx = direction.move(
-					direction=direction.right(),
-					override=narg,
-					maximum=self.max_width,
-					current=self.startx,
-					pagesize=self.wid,
-					offset=-self.wid + 1)
-		if direction.vertical():
-			if self.source_is_stream:
-				self._get_line(self.scroll_begin + self.hei * 2)
-			self.scroll_begin = direction.move(
-					direction=direction.down(),
-					override=narg,
-					maximum=len(self.lines),
-					current=self.scroll_begin,
-					pagesize=self.hei,
-					offset=-self.hei + 1)
-
-	def press(self, key):
-		self.fm.ui.keymaps.use_keymap('pager')
-		self.fm.ui.press(key)
-
-	def set_image(self, image):
-		if self.image:
-			self.need_clear_image = True
-		self.image = image
-
-		if self.source and self.source_is_stream:
-			self.source.close()
-		self.source = None
-		self.source_is_stream = False
-
-	def set_source(self, source, strip=False):
-		if self.image:
-			self.image = None
-			self.need_clear_image = True
-
-		if self.source and self.source_is_stream:
-			self.source.close()
-
-		self.max_width = 0
-		if isinstance(source, str):
-			self.source_is_stream = False
-			self.lines = source.splitlines()
-			if self.lines:
-				self.max_width = max(len(line) for line in self.lines)
-		elif hasattr(source, '__getitem__'):
-			self.source_is_stream = False
-			self.lines = source
-			if self.lines:
-				self.max_width = max(len(line) for line in source)
-		elif hasattr(source, 'readline'):
-			self.source_is_stream = True
-			self.lines = []
-		else:
-			self.source = None
-			self.source_is_stream = False
-			return False
-		self.markup = 'ansi'
-
-		if not self.source_is_stream and strip:
-			self.lines = map(lambda x: x.strip(), self.lines)
-
-		self.source = source
-		return True
-
-	def click(self, event):
-		n = event.ctrl() and 1 or 3
-		direction = event.mouse_wheel_direction()
-		if direction:
-			self.move(down=direction * n)
-		return True
-
-	def _get_line(self, n, attempt_to_read=True):
-		assert isinstance(n, int), n
-		try:
-			return self.lines[n]
-		except (KeyError, IndexError):
-			if attempt_to_read and self.source_is_stream:
-				try:
-					for l in self.source:
-						if len(l) > self.max_width:
-							self.max_width = len(l)
-						self.lines.append(l)
-						if len(self.lines) > n:
-							break
-				except (UnicodeError, IOError):
-					pass
-				return self._get_line(n, attempt_to_read=False)
-			return ""
-
-	def _generate_lines(self, starty, startx):
-		i = starty
-		if not self.source:
-			raise StopIteration
-		while True:
-			try:
-				line = self._get_line(i).expandtabs(4)
-				if self.markup is 'ansi':
-					line = ansi.char_slice(line, startx, self.wid) + ansi.reset
-				else:
-					line = line[startx:self.wid + startx]
-				yield line.rstrip()
-			except IndexError:
-				raise StopIteration
-			i += 1
+    source = None
+    source_is_stream = False
+
+    old_source = None
+    old_scroll_begin = 0
+    old_startx = 0
+    need_clear_image = False
+    need_redraw_image = False
+    max_width = None
+    def __init__(self, win, embedded=False):
+        Widget.__init__(self, win)
+        self.embedded = embedded
+        self.scroll_begin = 0
+        self.startx = 0
+        self.markup = None
+        self.lines = []
+        self.image = None
+
+    def open(self):
+        self.scroll_begin = 0
+        self.markup = None
+        self.max_width = 0
+        self.startx = 0
+        self.need_redraw = True
+
+    def clear_image(self):
+        if self.need_clear_image:
+            img_display.clear(self.x, self.y, self.wid, self.hei)
+            self.need_clear_image = False
+
+    def close(self):
+        if self.image:
+            self.need_clear_image = True
+            self.clear_image()
+        if self.source and self.source_is_stream:
+            self.source.close()
+
+    def finalize(self):
+        self.fm.ui.win.move(self.y, self.x)
+
+    def draw(self):
+        if self.need_clear_image:
+            self.need_redraw = True
+
+        if self.old_source != self.source:
+            self.old_source = self.source
+            self.need_redraw = True
+
+        if self.old_scroll_begin != self.scroll_begin or \
+                self.old_startx != self.startx:
+            self.old_startx = self.startx
+            self.old_scroll_begin = self.scroll_begin
+
+        if self.need_redraw:
+            self.need_redraw_image = True
+            self.clear_image()
+
+            if not self.image:
+                line_gen = self._generate_lines(
+                        starty=self.scroll_begin, startx=self.startx)
+
+                for line, i in zip(line_gen, range(self.hei)):
+                    self._draw_line(i, line)
+
+            self.need_redraw = False
+
+    def draw_image(self):
+        if self.image and self.need_redraw_image:
+            self.source = None
+            self.need_redraw_image = False
+            try:
+                img_display.draw(self.image, self.x, self.y, self.wid, self.hei)
+            except Exception as e:
+                self.fm.notify(e, bad=True)
+
+    def _draw_line(self, i, line):
+        if self.markup is None:
+            self.addstr(i, 0, line)
+        elif self.markup == 'ansi':
+            try:
+                self.win.move(i, 0)
+            except:
+                pass
+            else:
+                for chunk in ansi.text_with_fg_bg_attr(line):
+                    if isinstance(chunk, tuple):
+                        self.set_fg_bg_attr(*chunk)
+                    else:
+                        self.addstr(chunk)
+
+    def move(self, narg=None, **kw):
+        direction = Direction(kw)
+        if direction.horizontal():
+            self.startx = direction.move(
+                    direction=direction.right(),
+                    override=narg,
+                    maximum=self.max_width,
+                    current=self.startx,
+                    pagesize=self.wid,
+                    offset=-self.wid + 1)
+        if direction.vertical():
+            if self.source_is_stream:
+                self._get_line(self.scroll_begin + self.hei * 2)
+            self.scroll_begin = direction.move(
+                    direction=direction.down(),
+                    override=narg,
+                    maximum=len(self.lines),
+                    current=self.scroll_begin,
+                    pagesize=self.hei,
+                    offset=-self.hei + 1)
+
+    def press(self, key):
+        self.fm.ui.keymaps.use_keymap('pager')
+        self.fm.ui.press(key)
+
+    def set_image(self, image):
+        if self.image:
+            self.need_clear_image = True
+        self.image = image
+
+        if self.source and self.source_is_stream:
+            self.source.close()
+        self.source = None
+        self.source_is_stream = False
+
+    def set_source(self, source, strip=False):
+        if self.image:
+            self.image = None
+            self.need_clear_image = True
+
+        if self.source and self.source_is_stream:
+            self.source.close()
+
+        self.max_width = 0
+        if isinstance(source, str):
+            self.source_is_stream = False
+            self.lines = source.splitlines()
+            if self.lines:
+                self.max_width = max(len(line) for line in self.lines)
+        elif hasattr(source, '__getitem__'):
+            self.source_is_stream = False
+            self.lines = source
+            if self.lines:
+                self.max_width = max(len(line) for line in source)
+        elif hasattr(source, 'readline'):
+            self.source_is_stream = True
+            self.lines = []
+        else:
+            self.source = None
+            self.source_is_stream = False
+            return False
+        self.markup = 'ansi'
+
+        if not self.source_is_stream and strip:
+            self.lines = map(lambda x: x.strip(), self.lines)
+
+        self.source = source
+        return True
+
+    def click(self, event):
+        n = event.ctrl() and 1 or 3
+        direction = event.mouse_wheel_direction()
+        if direction:
+            self.move(down=direction * n)
+        return True
+
+    def _get_line(self, n, attempt_to_read=True):
+        assert isinstance(n, int), n
+        try:
+            return self.lines[n]
+        except (KeyError, IndexError):
+            if attempt_to_read and self.source_is_stream:
+                try:
+                    for l in self.source:
+                        if len(l) > self.max_width:
+                            self.max_width = len(l)
+                        self.lines.append(l)
+                        if len(self.lines) > n:
+                            break
+                except (UnicodeError, IOError):
+                    pass
+                return self._get_line(n, attempt_to_read=False)
+            return ""
+
+    def _generate_lines(self, starty, startx):
+        i = starty
+        if not self.source:
+            raise StopIteration
+        while True:
+            try:
+                line = self._get_line(i).expandtabs(4)
+                if self.markup is 'ansi':
+                    line = ansi.char_slice(line, startx, self.wid) + ansi.reset
+                else:
+                    line = line[startx:self.wid + startx]
+                yield line.rstrip()
+            except IndexError:
+                raise StopIteration
+            i += 1
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 247ebe62..a2ce3830 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -20,282 +20,282 @@ from . import Widget
 from ranger.gui.bar import Bar
 
 class StatusBar(Widget):
-	__doc__ = __doc__
-	owners = {}
-	groups = {}
-	timeformat = '%Y-%m-%d %H:%M'
-	hint = None
-	msg = None
-
-	old_thisfile = None
-	old_ctime = None
-	old_du = None
-	old_hint = None
-	result = None
-
-	def __init__(self, win, column=None):
-		Widget.__init__(self, win)
-		self.column = column
-		self.settings.signal_bind('setopt.display_size_in_status_bar',
-				self.request_redraw, weak=True)
-
-	def request_redraw(self):
-		self.need_redraw = True
-
-	def notify(self, text, duration=0, bad=False):
-		self.msg = Message(text, duration, bad)
-
-	def clear_message(self):
-		self.msg = None
-
-	def draw(self):
-		"""Draw the statusbar"""
-
-		if self.hint and isinstance(self.hint, str):
-			if self.old_hint != self.hint:
-				self.need_redraw = True
-			if self.need_redraw:
-				self._draw_hint()
-			return
-
-		if self.old_hint and not self.hint:
-			self.old_hint = None
-			self.need_redraw = True
-
-		if self.msg:
-			if self.msg.is_alive():
-				self._draw_message()
-				return
-			else:
-				self.msg = None
-				self.need_redraw = True
-
-		if self.fm.thisfile:
-			self.fm.thisfile.load_if_outdated()
-			try:
-				ctime = self.fm.thisfile.stat.st_ctime
-			except:
-				ctime = -1
-		else:
-			ctime = -1
-
-		if not self.result:
-			self.need_redraw = True
-
-		if self.old_du and not self.fm.thisdir.disk_usage:
-			self.old_du = self.fm.thisdir.disk_usage
-			self.need_redraw = True
-
-		if self.old_thisfile != self.fm.thisfile:
-			self.old_thisfile = self.fm.thisfile
-			self.need_redraw = True
-
-		if self.old_ctime != ctime:
-			self.old_ctime = ctime
-			self.need_redraw = True
-
-		if self.need_redraw:
-			self.need_redraw = False
-
-			self._calc_bar()
-			self._print_result(self.result)
-
-	def _calc_bar(self):
-		bar = Bar('in_statusbar')
-		self._get_left_part(bar)
-		self._get_right_part(bar)
-		bar.shrink_by_removing(self.wid)
-
-		self.result = bar.combine()
-
-	def _draw_message(self):
-		self.win.erase()
-		self.color('in_statusbar', 'message',
-				self.msg.bad and 'bad' or 'good')
-		self.addnstr(0, 0, self.msg.text, self.wid)
-
-	def _draw_hint(self):
-		self.win.erase()
-		highlight = True
-		space_left = self.wid
-		starting_point = self.x
-		for string in self.hint.split('*'):
-			highlight = not highlight
-			if highlight:
-				self.color('in_statusbar', 'text', 'highlight')
-			else:
-				self.color('in_statusbar', 'text')
-
-			try:
-				self.addnstr(0, starting_point, string, space_left)
-			except:
-				break
-			space_left -= len(string)
-			starting_point += len(string)
-
-	def _get_left_part(self, bar):
-		left = bar.left
-
-		if self.column is not None and self.column.target is not None\
-				and self.column.target.is_directory:
-			target = self.column.target.pointed_obj
-		else:
-			directory = self.fm.thistab.at_level(0)
-			if directory:
-				target = directory.pointed_obj
-			else:
-				return
-		try:
-			stat = target.stat
-		except:
-			return
-		if stat is None:
-			return
-
-		if self.fm.mode != 'normal':
-			perms = '--%s--' % self.fm.mode.upper()
-		else:
-			perms = target.get_permission_string()
-		how = getuid() == stat.st_uid and 'good' or 'bad'
-		left.add(perms, 'permissions', how)
-		left.add_space()
-		left.add(str(stat.st_nlink), 'nlink')
-		left.add_space()
-		left.add(self._get_owner(target), 'owner')
-		left.add_space()
-		left.add(self._get_group(target), 'group')
-
-		if target.is_link:
-			how = target.exists and 'good' or 'bad'
-			try:
-				dest = readlink(target.path)
-			except:
-				dest = '?'
-			left.add(' -> ' + dest, 'link', how)
-		else:
-			left.add_space()
-
-			if self.settings.display_size_in_status_bar and target.infostring:
-				left.add(target.infostring.replace(" ", ""))
-
-			left.add_space()
-
-			left.add(strftime(self.timeformat,
-					localtime(stat.st_mtime)), 'mtime')
-
-	def _get_owner(self, target):
-		uid = target.stat.st_uid
-
-		try:
-			return self.owners[uid]
-		except KeyError:
-			try:
-				self.owners[uid] = getpwuid(uid)[0]
-				return self.owners[uid]
-			except KeyError:
-				return str(uid)
-
-	def _get_group(self, target):
-		gid = target.stat.st_gid
-
-		try:
-			return self.groups[gid]
-		except KeyError:
-			try:
-				self.groups[gid] = getgrgid(gid)[0]
-				return self.groups[gid]
-			except KeyError:
-				return str(gid)
-
-	def _get_right_part(self, bar):
-		right = bar.right
-		if self.column is None:
-			return
-
-		target = self.column.target
-		if target is None \
-				or not target.accessible \
-				or (target.is_directory and target.files is None):
-			return
-
-		pos = target.scroll_begin
-		max_pos = len(target) - self.column.hei
-		base = 'scroll'
-
-		if self.fm.thisdir.filter:
-			right.add(" f=", base, 'filter')
-			right.add(repr(self.fm.thisdir.filter), base, 'filter')
-			right.add(", ", "space")
-
-		if target.marked_items:
-			if len(target.marked_items) == len(target.files):
-				right.add(human_readable(target.disk_usage, separator=''))
-			else:
-				sumsize = sum(f.size for f in target.marked_items if not
-						f.is_directory or f._cumulative_size_calculated)
-				right.add(human_readable(sumsize, separator=''))
-			right.add("/" + str(len(target.marked_items)))
-		else:
-			right.add(human_readable(target.disk_usage, separator='') + " sum")
-			try:
-				free = get_free_space(target.mount_path)
-			except OSError:
-				pass
-			else:
-				right.add(", ", "space")
-				right.add(human_readable(free, separator='') + " free")
-		right.add("  ", "space")
-
-		if target.marked_items:
-			# Indicate that there are marked files. Useful if you scroll
-			# away and don't see them anymore.
-			right.add('Mrk', base, 'marked')
-		elif len(target.files):
-			right.add(str(target.pointer + 1) + '/'
-					+ str(len(target.files)) + '  ', base)
-			if max_pos <= 0:
-				right.add('All', base, 'all')
-			elif pos == 0:
-				right.add('Top', base, 'top')
-			elif pos >= max_pos:
-				right.add('Bot', base, 'bot')
-			else:
-				right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos),
-						base, 'percentage')
-		else:
-			right.add('0/0  All', base, 'all')
-
-	def _print_result(self, result):
-		self.win.move(0, 0)
-		for part in result:
-			self.color(*part.lst)
-			self.addstr(str(part))
-
-		if self.settings.draw_progress_bar_in_status_bar:
-			queue = self.fm.loader.queue
-			states = []
-			for item in queue:
-				if item.progressbar_supported:
-					states.append(item.percent)
-			if states:
-				state = sum(states) / len(states)
-				barwidth = state / 100.0 * self.wid
-				self.color_at(0, 0, int(barwidth), ("in_statusbar", "loaded"))
-				self.color_reset()
+    __doc__ = __doc__
+    owners = {}
+    groups = {}
+    timeformat = '%Y-%m-%d %H:%M'
+    hint = None
+    msg = None
+
+    old_thisfile = None
+    old_ctime = None
+    old_du = None
+    old_hint = None
+    result = None
+
+    def __init__(self, win, column=None):
+        Widget.__init__(self, win)
+        self.column = column
+        self.settings.signal_bind('setopt.display_size_in_status_bar',
+                self.request_redraw, weak=True)
+
+    def request_redraw(self):
+        self.need_redraw = True
+
+    def notify(self, text, duration=0, bad=False):
+        self.msg = Message(text, duration, bad)
+
+    def clear_message(self):
+        self.msg = None
+
+    def draw(self):
+        """Draw the statusbar"""
+
+        if self.hint and isinstance(self.hint, str):
+            if self.old_hint != self.hint:
+                self.need_redraw = True
+            if self.need_redraw:
+                self._draw_hint()
+            return
+
+        if self.old_hint and not self.hint:
+            self.old_hint = None
+            self.need_redraw = True
+
+        if self.msg:
+            if self.msg.is_alive():
+                self._draw_message()
+                return
+            else:
+                self.msg = None
+                self.need_redraw = True
+
+        if self.fm.thisfile:
+            self.fm.thisfile.load_if_outdated()
+            try:
+                ctime = self.fm.thisfile.stat.st_ctime
+            except:
+                ctime = -1
+        else:
+            ctime = -1
+
+        if not self.result:
+            self.need_redraw = True
+
+        if self.old_du and not self.fm.thisdir.disk_usage:
+            self.old_du = self.fm.thisdir.disk_usage
+            self.need_redraw = True
+
+        if self.old_thisfile != self.fm.thisfile:
+            self.old_thisfile = self.fm.thisfile
+            self.need_redraw = True
+
+        if self.old_ctime != ctime:
+            self.old_ctime = ctime
+            self.need_redraw = True
+
+        if self.need_redraw:
+            self.need_redraw = False
+
+            self._calc_bar()
+            self._print_result(self.result)
+
+    def _calc_bar(self):
+        bar = Bar('in_statusbar')
+        self._get_left_part(bar)
+        self._get_right_part(bar)
+        bar.shrink_by_removing(self.wid)
+
+        self.result = bar.combine()
+
+    def _draw_message(self):
+        self.win.erase()
+        self.color('in_statusbar', 'message',
+                self.msg.bad and 'bad' or 'good')
+        self.addnstr(0, 0, self.msg.text, self.wid)
+
+    def _draw_hint(self):
+        self.win.erase()
+        highlight = True
+        space_left = self.wid
+        starting_point = self.x
+        for string in self.hint.split('*'):
+            highlight = not highlight
+            if highlight:
+                self.color('in_statusbar', 'text', 'highlight')
+            else:
+                self.color('in_statusbar', 'text')
+
+            try:
+                self.addnstr(0, starting_point, string, space_left)
+            except:
+                break
+            space_left -= len(string)
+            starting_point += len(string)
+
+    def _get_left_part(self, bar):
+        left = bar.left
+
+        if self.column is not None and self.column.target is not None\
+                and self.column.target.is_directory:
+            target = self.column.target.pointed_obj
+        else:
+            directory = self.fm.thistab.at_level(0)
+            if directory:
+                target = directory.pointed_obj
+            else:
+                return
+        try:
+            stat = target.stat
+        except:
+            return
+        if stat is None:
+            return
+
+        if self.fm.mode != 'normal':
+            perms = '--%s--' % self.fm.mode.upper()
+        else:
+            perms = target.get_permission_string()
+        how = getuid() == stat.st_uid and 'good' or 'bad'
+        left.add(perms, 'permissions', how)
+        left.add_space()
+        left.add(str(stat.st_nlink), 'nlink')
+        left.add_space()
+        left.add(self._get_owner(target), 'owner')
+        left.add_space()
+        left.add(self._get_group(target), 'group')
+
+        if target.is_link:
+            how = target.exists and 'good' or 'bad'
+            try:
+                dest = readlink(target.path)
+            except:
+                dest = '?'
+            left.add(' -> ' + dest, 'link', how)
+        else:
+            left.add_space()
+
+            if self.settings.display_size_in_status_bar and target.infostring:
+                left.add(target.infostring.replace(" ", ""))
+
+            left.add_space()
+
+            left.add(strftime(self.timeformat,
+                    localtime(stat.st_mtime)), 'mtime')
+
+    def _get_owner(self, target):
+        uid = target.stat.st_uid
+
+        try:
+            return self.owners[uid]
+        except KeyError:
+            try:
+                self.owners[uid] = getpwuid(uid)[0]
+                return self.owners[uid]
+            except KeyError:
+                return str(uid)
+
+    def _get_group(self, target):
+        gid = target.stat.st_gid
+
+        try:
+            return self.groups[gid]
+        except KeyError:
+            try:
+                self.groups[gid] = getgrgid(gid)[0]
+                return self.groups[gid]
+            except KeyError:
+                return str(gid)
+
+    def _get_right_part(self, bar):
+        right = bar.right
+        if self.column is None:
+            return
+
+        target = self.column.target
+        if target is None \
+                or not target.accessible \
+                or (target.is_directory and target.files is None):
+            return
+
+        pos = target.scroll_begin
+        max_pos = len(target) - self.column.hei
+        base = 'scroll'
+
+        if self.fm.thisdir.filter:
+            right.add(" f=", base, 'filter')
+            right.add(repr(self.fm.thisdir.filter), base, 'filter')
+            right.add(", ", "space")
+
+        if target.marked_items:
+            if len(target.marked_items) == len(target.files):
+                right.add(human_readable(target.disk_usage, separator=''))
+            else:
+                sumsize = sum(f.size for f in target.marked_items if not
+                        f.is_directory or f._cumulative_size_calculated)
+                right.add(human_readable(sumsize, separator=''))
+            right.add("/" + str(len(target.marked_items)))
+        else:
+            right.add(human_readable(target.disk_usage, separator='') + " sum")
+            try:
+                free = get_free_space(target.mount_path)
+            except OSError:
+                pass
+            else:
+                right.add(", ", "space")
+                right.add(human_readable(free, separator='') + " free")
+        right.add("  ", "space")
+
+        if target.marked_items:
+            # Indicate that there are marked files. Useful if you scroll
+            # away and don't see them anymore.
+            right.add('Mrk', base, 'marked')
+        elif len(target.files):
+            right.add(str(target.pointer + 1) + '/'
+                    + str(len(target.files)) + '  ', base)
+            if max_pos <= 0:
+                right.add('All', base, 'all')
+            elif pos == 0:
+                right.add('Top', base, 'top')
+            elif pos >= max_pos:
+                right.add('Bot', base, 'bot')
+            else:
+                right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos),
+                        base, 'percentage')
+        else:
+            right.add('0/0  All', base, 'all')
+
+    def _print_result(self, result):
+        self.win.move(0, 0)
+        for part in result:
+            self.color(*part.lst)
+            self.addstr(str(part))
+
+        if self.settings.draw_progress_bar_in_status_bar:
+            queue = self.fm.loader.queue
+            states = []
+            for item in queue:
+                if item.progressbar_supported:
+                    states.append(item.percent)
+            if states:
+                state = sum(states) / len(states)
+                barwidth = state / 100.0 * self.wid
+                self.color_at(0, 0, int(barwidth), ("in_statusbar", "loaded"))
+                self.color_reset()
 
 def get_free_space(path):
-	stat = os.statvfs(path)
-	return stat.f_bavail * stat.f_bsize
+    stat = os.statvfs(path)
+    return stat.f_bavail * stat.f_bsize
 
 class Message(object):
-	elapse = None
-	text = None
-	bad = False
+    elapse = None
+    text = None
+    bad = False
 
-	def __init__(self, text, duration, bad):
-		self.text = text
-		self.bad = bad
-		self.elapse = time() + duration
+    def __init__(self, text, duration, bad):
+        self.text = text
+        self.bad = bad
+        self.elapse = time() + duration
 
-	def is_alive(self):
-		return time() <= self.elapse
+    def is_alive(self):
+        return time() <= self.elapse
diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py
index e5efc417..3637d0e3 100644
--- a/ranger/gui/widgets/taskview.py
+++ b/ranger/gui/widgets/taskview.py
@@ -9,87 +9,87 @@ from . import Widget
 from ranger.ext.accumulator import Accumulator
 
 class TaskView(Widget, Accumulator):
-	old_lst = None
-
-	def __init__(self, win):
-		Widget.__init__(self, win)
-		Accumulator.__init__(self)
-		self.scroll_begin = 0
-
-	def draw(self):
-		base_clr = []
-		base_clr.append('in_taskview')
-		lst = self.get_list()
-
-		if self.old_lst != lst:
-			self.old_lst = lst
-			self.need_redraw = True
-
-		if self.need_redraw:
-			self.win.erase()
-			if not self.pointer_is_synced():
-				self.sync_index()
-
-			if self.hei <= 0:
-				return
-
-			self.addstr(0, 0, "Task View")
-			self.color_at(0, 0, self.wid, tuple(base_clr), 'title')
-
-			if lst:
-				for i in range(self.hei - 1):
-					i += self.scroll_begin
-					try:
-						obj = lst[i]
-					except IndexError:
-						break
-
-					y = i + 1
-					clr = list(base_clr)
-
-					if self.pointer == i:
-						clr.append('selected')
-
-					descr = obj.get_description()
-					if obj.progressbar_supported and obj.percent >= 0 \
-							and obj.percent <= 100:
-						self.addstr(y, 0, "%3d%% - %s" % \
-								(obj.percent, descr), self.wid)
-						wid = int(self.wid / 100.0 * obj.percent)
-						self.color_at(y, 0, self.wid, tuple(clr))
-						self.color_at(y, 0, wid, tuple(clr), 'loaded')
-					else:
-						self.addstr(y, 0, descr, self.wid)
-						self.color_at(y, 0, self.wid, tuple(clr))
-
-			else:
-				if self.hei > 1:
-					self.addstr(1, 0, "No task in the queue.")
-					self.color_at(1, 0, self.wid, tuple(base_clr), 'error')
-
-			self.color_reset()
-
-	def finalize(self):
-		y = self.y + 1 + self.pointer - self.scroll_begin
-		self.fm.ui.win.move(y, self.x)
-
-
-	def task_remove(self, i=None):
-		if i is None:
-			i = self.pointer
-
-		if self.fm.loader.queue:
-			self.fm.loader.remove(index=i)
-
-	def task_move(self, to, i=None):
-		if i is None:
-			i = self.pointer
-
-		self.fm.loader.move(_from=i, to=to)
-
-	def press(self, key):
-		self.fm.ui.keymaps.use_keymap('taskview')
-		self.fm.ui.press(key)
-
-	def get_list(self):
-		return self.fm.loader.queue
+    old_lst = None
+
+    def __init__(self, win):
+        Widget.__init__(self, win)
+        Accumulator.__init__(self)
+        self.scroll_begin = 0
+
+    def draw(self):
+        base_clr = []
+        base_clr.append('in_taskview')
+        lst = self.get_list()
+
+        if self.old_lst != lst:
+            self.old_lst = lst
+            self.need_redraw = True
+
+        if self.need_redraw:
+            self.win.erase()
+            if not self.pointer_is_synced():
+                self.sync_index()
+
+            if self.hei <= 0:
+                return
+
+            self.addstr(0, 0, "Task View")
+            self.color_at(0, 0, self.wid, tuple(base_clr), 'title')
+
+            if lst:
+                for i in range(self.hei - 1):
+                    i += self.scroll_begin
+                    try:
+                        obj = lst[i]
+                    except IndexError:
+                        break
+
+                    y = i + 1
+                    clr = list(base_clr)
+
+                    if self.pointer == i:
+                        clr.append('selected')
+
+                    descr = obj.get_description()
+                    if obj.progressbar_supported and obj.percent >= 0 \
+                            and obj.percent <= 100:
+                        self.addstr(y, 0, "%3d%% - %s" % \
+                                (obj.percent, descr), self.wid)
+                        wid = int(self.wid / 100.0 * obj.percent)
+                        self.color_at(y, 0, self.wid, tuple(clr))
+                        self.color_at(y, 0, wid, tuple(clr), 'loaded')
+                    else:
+                        self.addstr(y, 0, descr, self.wid)
+                        self.color_at(y, 0, self.wid, tuple(clr))
+
+            else:
+                if self.hei > 1:
+                    self.addstr(1, 0, "No task in the queue.")
+                    self.color_at(1, 0, self.wid, tuple(base_clr), 'error')
+
+            self.color_reset()
+
+    def finalize(self):
+        y = self.y + 1 + self.pointer - self.scroll_begin
+        self.fm.ui.win.move(y, self.x)
+
+
+    def task_remove(self, i=None):
+        if i is None:
+            i = self.pointer
+
+        if self.fm.loader.queue:
+            self.fm.loader.remove(index=i)
+
+    def task_move(self, to, i=None):
+        if i is None:
+            i = self.pointer
+
+        self.fm.loader.move(_from=i, to=to)
+
+    def press(self, key):
+        self.fm.ui.keymaps.use_keymap('taskview')
+        self.fm.ui.press(key)
+
+    def get_list(self):
+        return self.fm.loader.queue
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index d37a2fd3..5986ec7a 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -13,142 +13,142 @@ from . import Widget
 from ranger.gui.bar import Bar
 
 class TitleBar(Widget):
-	old_thisfile = None
-	old_keybuffer = None
-	old_wid = None
-	result = None
-	throbber = ' '
-	need_redraw = False
-	tab_width = 0
-
-	def __init__(self, *args, **keywords):
-		Widget.__init__(self, *args, **keywords)
-		self.fm.signal_bind('tab.change', self.request_redraw, weak=True)
-
-	def request_redraw(self):
-		self.need_redraw = True
-
-	def draw(self):
-		if self.need_redraw or \
-				self.fm.thisfile != self.old_thisfile or\
-				str(self.fm.ui.keybuffer) != str(self.old_keybuffer) or\
-				self.wid != self.old_wid:
-			self.need_redraw = False
-			self.old_wid = self.wid
-			self.old_thisfile = self.fm.thisfile
-			self._calc_bar()
-		self._print_result(self.result)
-		if self.wid > 2:
-			self.color('in_titlebar', 'throbber')
-			self.addnstr(self.y, self.wid - 2 - self.tab_width,
-					self.throbber, 1)
-
-	def click(self, event):
-		"""Handle a MouseEvent"""
-		direction = event.mouse_wheel_direction()
-		if direction:
-			self.fm.tab_move(direction)
-			self.need_redraw = True
-			return True
-
-		if not event.pressed(1) or not self.result:
-			return False
-
-		pos = self.wid - 1
-		for tabname in reversed(self.fm._get_tab_list()):
-			tabtext = self._get_tab_text(tabname)
-			pos -= len(tabtext)
-			if event.x > pos:
-				self.fm.tab_open(tabname)
-				self.need_redraw = True
-				return True
-
-		pos = 0
-		for i, part in enumerate(self.result):
-			pos += len(part)
-			if event.x < pos:
-				if i < 2:
-					self.fm.enter_dir("~")
-				elif i == 2:
-					self.fm.enter_dir("/")
-				else:
-					try:
-						self.fm.enter_dir(part.directory)
-					except:
-						pass
-				return True
-		return False
-
-	def _calc_bar(self):
-		bar = Bar('in_titlebar')
-		self._get_left_part(bar)
-		self._get_right_part(bar)
-		try:
-			bar.shrink_from_the_left(self.wid)
-		except ValueError:
-			bar.shrink_by_removing(self.wid)
-		self.result = bar.combine()
-
-	def _get_left_part(self, bar):
-		# TODO: Properly escape non-printable chars without breaking unicode
-		if self.fm.username == 'root':
-			clr = 'bad'
-		else:
-			clr = 'good'
-
-		bar.add(self.fm.username, 'hostname', clr, fixed=True)
-		bar.add('@', 'hostname', clr, fixed=True)
-		bar.add(self.fm.hostname, 'hostname', clr, fixed=True)
-		bar.add(':', 'hostname', clr, fixed=True)
-
-		pathway = self.fm.thistab.pathway
-		if self.settings.tilde_in_titlebar and \
-				self.fm.thisdir.path.startswith(self.fm.home_path):
-			pathway = pathway[self.fm.home_path.count('/')+1:]
-			bar.add('~/', 'directory', fixed=True)
-
-		for path in pathway:
-			if path.is_link:
-				clr = 'link'
-			else:
-				clr = 'directory'
-
-			bar.add(path.basename, clr, directory=path)
-			bar.add('/', clr, fixed=True, directory=path)
-
-		if self.fm.thisfile is not None:
-			bar.add(self.fm.thisfile.basename, 'file')
-
-	def _get_right_part(self, bar):
-		# TODO: fix that pressed keys are cut off when chaining CTRL keys
-		kb = str(self.fm.ui.keybuffer)
-		self.old_keybuffer = kb
-		bar.addright(kb, 'keybuffer', fixed=True)
-		bar.addright('  ', 'space', fixed=True)
-		self.tab_width = 0
-		if len(self.fm.tabs) > 1:
-			for tabname in self.fm._get_tab_list():
-				tabtext = self._get_tab_text(tabname)
-				self.tab_width += len(tabtext)
-				clr = 'good' if tabname == self.fm.current_tab else 'bad'
-				bar.addright(tabtext, 'tab', clr, fixed=True)
-
-	def _get_tab_text(self, tabname):
-		result = ' ' + str(tabname)
-		if self.settings.dirname_in_tabs:
-			dirname = basename(self.fm.tabs[tabname].path)
-			if not dirname:
-				result += ":/"
-			elif len(dirname) > 15:
-				result += ":" + dirname[:14] + "~"
-			else:
-				result += ":" + dirname
-		return result
-
-	def _print_result(self, result):
-		self.win.move(0, 0)
-		for part in result:
-			self.color(*part.lst)
-			y, x = self.win.getyx()
-			self.addstr(y, x, str(part))
-		self.color_reset()
+    old_thisfile = None
+    old_keybuffer = None
+    old_wid = None
+    result = None
+    throbber = ' '
+    need_redraw = False
+    tab_width = 0
+
+    def __init__(self, *args, **keywords):
+        Widget.__init__(self, *args, **keywords)
+        self.fm.signal_bind('tab.change', self.request_redraw, weak=True)
+
+    def request_redraw(self):
+        self.need_redraw = True
+
+    def draw(self):
+        if self.need_redraw or \
+                self.fm.thisfile != self.old_thisfile or\
+                str(self.fm.ui.keybuffer) != str(self.old_keybuffer) or\
+                self.wid != self.old_wid:
+            self.need_redraw = False
+            self.old_wid = self.wid
+            self.old_thisfile = self.fm.thisfile
+            self._calc_bar()
+        self._print_result(self.result)
+        if self.wid > 2:
+            self.color('in_titlebar', 'throbber')
+            self.addnstr(self.y, self.wid - 2 - self.tab_width,
+                    self.throbber, 1)
+
+    def click(self, event):
+        """Handle a MouseEvent"""
+        direction = event.mouse_wheel_direction()
+        if direction:
+            self.fm.tab_move(direction)
+            self.need_redraw = True
+            return True
+
+        if not event.pressed(1) or not self.result:
+            return False
+
+        pos = self.wid - 1
+        for tabname in reversed(self.fm._get_tab_list()):
+            tabtext = self._get_tab_text(tabname)
+            pos -= len(tabtext)
+            if event.x > pos:
+                self.fm.tab_open(tabname)
+                self.need_redraw = True
+                return True
+
+        pos = 0
+        for i, part in enumerate(self.result):
+            pos += len(part)
+            if event.x < pos:
+                if i < 2:
+                    self.fm.enter_dir("~")
+                elif i == 2:
+                    self.fm.enter_dir("/")
+                else:
+                    try:
+                        self.fm.enter_dir(part.directory)
+                    except:
+                        pass
+                return True
+        return False
+
+    def _calc_bar(self):
+        bar = Bar('in_titlebar')
+        self._get_left_part(bar)
+        self._get_right_part(bar)
+        try:
+            bar.shrink_from_the_left(self.wid)
+        except ValueError:
+            bar.shrink_by_removing(self.wid)
+        self.result = bar.combine()
+
+    def _get_left_part(self, bar):
+        # TODO: Properly escape non-printable chars without breaking unicode
+        if self.fm.username == 'root':
+            clr = 'bad'
+        else:
+            clr = 'good'
+
+        bar.add(self.fm.username, 'hostname', clr, fixed=True)
+        bar.add('@', 'hostname', clr, fixed=True)
+        bar.add(self.fm.hostname, 'hostname', clr, fixed=True)
+        bar.add(':', 'hostname', clr, fixed=True)
+
+        pathway = self.fm.thistab.pathway
+        if self.settings.tilde_in_titlebar and \
+                self.fm.thisdir.path.startswith(self.fm.home_path):
+            pathway = pathway[self.fm.home_path.count('/')+1:]
+            bar.add('~/', 'directory', fixed=True)
+
+        for path in pathway:
+            if path.is_link:
+                clr = 'link'
+            else:
+                clr = 'directory'
+
+            bar.add(path.basename, clr, directory=path)
+            bar.add('/', clr, fixed=True, directory=path)
+
+        if self.fm.thisfile is not None:
+            bar.add(self.fm.thisfile.basename, 'file')
+
+    def _get_right_part(self, bar):
+        # TODO: fix that pressed keys are cut off when chaining CTRL keys
+        kb = str(self.fm.ui.keybuffer)
+        self.old_keybuffer = kb
+        bar.addright(kb, 'keybuffer', fixed=True)
+        bar.addright('  ', 'space', fixed=True)
+        self.tab_width = 0
+        if len(self.fm.tabs) > 1:
+            for tabname in self.fm._get_tab_list():
+                tabtext = self._get_tab_text(tabname)
+                self.tab_width += len(tabtext)
+                clr = 'good' if tabname == self.fm.current_tab else 'bad'
+                bar.addright(tabtext, 'tab', clr, fixed=True)
+
+    def _get_tab_text(self, tabname):
+        result = ' ' + str(tabname)
+        if self.settings.dirname_in_tabs:
+            dirname = basename(self.fm.tabs[tabname].path)
+            if not dirname:
+                result += ":/"
+            elif len(dirname) > 15:
+                result += ":" + dirname[:14] + "~"
+            else:
+                result += ":" + dirname
+        return result
+
+    def _print_result(self, result):
+        self.win.move(0, 0)
+        for part in result:
+            self.color(*part.lst)
+            y, x = self.win.getyx()
+            self.addstr(y, x, str(part))
+        self.color_reset()