summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README8
-rw-r--r--doc/ranger.17
-rwxr-xr-xranger.py47
-rw-r--r--ranger/__init__.py62
-rw-r--r--ranger/api/apps.py2
-rw-r--r--ranger/api/commands.py2
-rw-r--r--ranger/container/settingobject.py (renamed from ranger/shared/settings.py)45
-rw-r--r--ranger/core/actions.py199
-rw-r--r--ranger/core/environment.py7
-rw-r--r--ranger/core/fm.py77
-rw-r--r--ranger/core/helper.py (renamed from ranger/__main__.py)187
-rw-r--r--ranger/core/loader.py137
-rw-r--r--ranger/core/main.py108
-rw-r--r--ranger/core/shared.py88
-rwxr-xr-xranger/data/scope.sh68
-rw-r--r--ranger/defaults/apps.py12
-rw-r--r--ranger/defaults/commands.py48
-rw-r--r--ranger/defaults/keys.py16
-rw-r--r--ranger/defaults/options.py14
-rw-r--r--ranger/ext/human_readable.py10
-rw-r--r--ranger/ext/keybinding_parser.py4
-rw-r--r--ranger/ext/shutil_generatorized.py302
-rw-r--r--ranger/ext/signals.py (renamed from ranger/ext/signal_dispatcher.py)0
-rw-r--r--ranger/ext/utfwidth.py4
-rw-r--r--ranger/fsobject/directory.py26
-rw-r--r--ranger/fsobject/file.py28
-rw-r--r--ranger/fsobject/fsobject.py26
-rw-r--r--ranger/gui/ansi.py96
-rw-r--r--ranger/gui/bar.py24
-rw-r--r--ranger/gui/color.py1
-rw-r--r--ranger/gui/colorscheme.py44
-rw-r--r--ranger/gui/context.py2
-rw-r--r--ranger/gui/curses_shortcuts.py11
-rw-r--r--ranger/gui/displayable.py2
-rw-r--r--ranger/gui/widgets/browsercolumn.py41
-rw-r--r--ranger/gui/widgets/browserview.py38
-rw-r--r--ranger/gui/widgets/console.py79
-rw-r--r--ranger/gui/widgets/pager.py31
-rw-r--r--ranger/gui/widgets/statusbar.py23
-rw-r--r--ranger/gui/widgets/titlebar.py16
-rw-r--r--ranger/help/console.py1
-rw-r--r--ranger/help/invocation.py6
-rw-r--r--ranger/help/movement.py3
-rw-r--r--ranger/shared/__init__.py35
-rw-r--r--ranger/shared/mimetype.py26
-rwxr-xr-xsetup.py4
-rw-r--r--test/tc_ansi.py40
-rw-r--r--test/tc_directory.py8
-rw-r--r--test/tc_loader.py2
-rw-r--r--test/tc_signal.py2
50 files changed, 1228 insertions, 841 deletions
diff --git a/README b/README
index 79b3a767..bbf4c926 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ranger v.1.2.3
+Ranger v.1.3.1
 ==============
 
 Ranger is a free console file manager that gives you greater flexibility
@@ -62,6 +62,12 @@ Optional:
 * The "file" program
 * A pager ("less" by default)
 
+For scope.sh: (enhanced file previews)
+* img2txt (from caca-utils) for previewing images
+* highlight for syntax highlighting of code
+* atool for previews of archives
+* lynx or elinks for previews of html pages
+
 
 Getting Started
 ---------------
diff --git a/doc/ranger.1 b/doc/ranger.1
index e098c2b0..8eee794c 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.TH RANGER 1 ranger-1.2.3
+.TH RANGER 1 ranger-1.3.1
 .SH NAME
 ranger - visual file manager
 .\"-----------------------------------------
@@ -33,6 +33,11 @@ Activate the clean mode:  Ranger will not access or create any configuration
 files nor will it leave any traces on your system.  This is useful when
 your configuration is broken, when you want to avoid clutter, etc.
 .TP
+--copy-config=\fIwhich\fR
+Create copies of the default configuration files in your local configuration
+directory.  Existing ones will not be overwritten.  Possible values:
+all, apps, commands, keys, options, scope.
+.TP
 --fail-unless-cd
 Return the exit code 1 if ranger is used to run a file, for example with
 `ranger --fail-unless-cd filename`.  This can be useful for scripts.
diff --git a/ranger.py b/ranger.py
index 5652ba69..240b3687 100755
--- a/ranger.py
+++ b/ranger.py
@@ -1,5 +1,5 @@
 #!/usr/bin/python -O
-# coding=utf-8
+# -*- coding: utf-8 -*-
 #
 # Ranger: Explore your forest of files from inside your terminal
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
@@ -16,18 +16,17 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-# ----------------------------------------------------------------------------
-#
-# An embedded shell script. It allows you to change the directory
-# after you exit ranger by starting it with: source ranger ranger
+
+# Embed a script which allows you to change the directory of the parent shell
+# after you exit ranger.  Run it with the command: source ranger ranger
 """":
-if [ $1 ]; then
+if [ ! -z "$1" ]; then
+	$@ --fail-unless-cd &&
 	if [ -z "$XDG_CONFIG_HOME" ]; then
-		"$@" --fail-unless-cd && cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)"
+		cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)"
 	else
-		"$@" --fail-unless-cd && cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)"
-	fi
+		cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)"
+	fi && return 0
 else
 	echo "usage: source path/to/ranger.py path/to/ranger.py"
 fi
@@ -36,21 +35,17 @@ return 1
 
 import sys
 
-# Redefine the docstring, since the previous one was hijacked to
-# embed a shellscript.
+# When using the --clean option, not even bytecode should be written.
+# Thus, we need to find out if --clean is used as soon as possible.
+try:
+	argv = sys.argv[0:sys.argv.index('--')]
+except:
+	argv = sys.argv
+sys.dont_write_bytecode = '-c' in argv or '--clean' in argv
+
+# Set the actual docstring
 __doc__ = """Ranger - file browser for the unix terminal"""
 
-# Importing the main method may fail if the ranger directory
-# is neither in the same directory as this file, nor in one of
-# pythons global import paths.
-try:
-	from ranger.__main__ import main
-except ImportError:
-	if '-d' not in sys.argv and '--debug' not in sys.argv:
-		print("Can't import the main module.")
-		print("To run an uninstalled copy of ranger,")
-		print("launch ranger.py in the top directory.")
-	else:
-		raise
-else:
-	sys.exit(main())
+# Start ranger
+import ranger
+sys.exit(ranger.main())
diff --git a/ranger/__init__.py b/ranger/__init__.py
index e0a4f4e0..3f7339f1 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -13,57 +13,25 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-"""Ranger - file browser for the unix terminal"""
+"""
+Console-based visual file manager.
+
+Ranger is a file manager with an ncurses frontend written in Python.
+It is designed to give you a broader overview of the file system by
+displaying previews and backviews, dividing the screen into columns.
+
+The keybindings are similar to those of other console programs like
+vim, mutt or ncmpcpp so the usage will be intuitive and efficient.
+"""
 
 import os
-import sys
-from ranger.ext.openstruct import OpenStruct
+from ranger.core.main import main
 
+# Information
 __license__ = 'GPL3'
-__version__ = '1.2.3'
-__credits__ = 'Roman Zimbelmann'
-__author__ = 'Roman Zimbelmann'
-__maintainer__ = 'Roman Zimbelmann'
+__version__ = '1.3.1'
+__author__ = __maintainer__ = 'Roman Zimbelmann'
 __email__ = 'romanz@lavabit.com'
 
-__copyright__ = """
-Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
-"""
-
-USAGE = '%prog [options] [path/filename]'
-if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
-	DEFAULT_CONFDIR = os.environ['XDG_CONFIG_HOME'] + '/ranger'
-else:
-	DEFAULT_CONFDIR = '~/.config/ranger'
+# Constants
 RANGERDIR = os.path.dirname(__file__)
-LOGFILE = '/tmp/errorlog'
-arg = OpenStruct(
-		debug=False, clean=False, confdir=DEFAULT_CONFDIR,
-		mode=0, flags='', targets=[])
-
-#for python3-only versions, this could be replaced with:
-#def log(*objects, start='ranger:', sep=' ', end='\n'):
-#	print(start, *objects, end=end, sep=sep, file=open(LOGFILE, 'a'))
-def log(*objects, **keywords):
-	"""
-	Writes objects to a logfile (for the purpose of debugging only.)
-	Has the same arguments as print() in python3.
-	"""
-	if LOGFILE is None or arg.clean:
-		return
-	start = 'start' in keywords and keywords['start'] or 'ranger:'
-	sep   =   'sep' in keywords and keywords['sep']   or ' '
-	_file =  'file' in keywords and keywords['file']  or open(LOGFILE, 'a')
-	end   =   'end' in keywords and keywords['end']   or '\n'
-	_file.write(sep.join(map(str, (start, ) + objects)) + end)
-
-def relpath_conf(*paths):
-	"""returns the path relative to rangers configuration directory"""
-	if arg.clean:
-		assert 0, "Should not access relpath_conf in clean mode!"
-	else:
-		return os.path.join(arg.confdir, *paths)
-
-def relpath(*paths):
-	"""returns the path relative to rangers library directory"""
-	return os.path.join(RANGERDIR, *paths)
diff --git a/ranger/api/apps.py b/ranger/api/apps.py
index 91aae357..45432705 100644
--- a/ranger/api/apps.py
+++ b/ranger/api/apps.py
@@ -17,7 +17,7 @@ import os, sys, re
 from ranger.api import *
 from ranger.ext.iter_tools import flatten
 from ranger.ext.get_executables import get_executables
-from ranger.shared import FileManagerAware
+from ranger.core.shared import FileManagerAware
 
 
 class Applications(FileManagerAware):
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 9dd8ffd9..a491c927 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -16,7 +16,7 @@
 import os
 from collections import deque
 from ranger.api import *
-from ranger.shared import FileManagerAware
+from ranger.core.shared import FileManagerAware
 from ranger.ext.command_parser import LazyParser as parse
 
 # A dummy that allows the generation of docstrings in ranger.defaults.commands
diff --git a/ranger/shared/settings.py b/ranger/container/settingobject.py
index 7604af12..4c910bf4 100644
--- a/ranger/shared/settings.py
+++ b/ranger/container/settingobject.py
@@ -13,11 +13,9 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import sys
 from inspect import isfunction
-import ranger
-from ranger.ext.signal_dispatcher import SignalDispatcher
-from ranger.ext.openstruct import OpenStruct
+from ranger.ext.signals import SignalDispatcher
+from ranger.core.shared import FileManagerAware
 
 ALLOWED_SETTINGS = {
 	'autosave_bookmarks': bool,
@@ -37,6 +35,8 @@ ALLOWED_SETTINGS = {
 	'mouse_enabled': bool,
 	'preview_directories': bool,
 	'preview_files': bool,
+	'preview_script': (str, type(None)),
+	'padding_right': bool,
 	'save_console_history': bool,
 	'scroll_offset': int,
 	'shorten_title': int,  # Note: False is an instance of int
@@ -49,11 +49,12 @@ ALLOWED_SETTINGS = {
 	'sort': str,
 	'tilde_in_titlebar': bool,
 	'update_title': bool,
+	'use_preview_script': bool,
 	'xterm_alt_key': bool,
 }
 
 
-class SettingObject(SignalDispatcher):
+class SettingObject(SignalDispatcher, FileManagerAware):
 	def __init__(self):
 		SignalDispatcher.__init__(self)
 		self.__dict__['_settings'] = dict()
@@ -71,7 +72,7 @@ class SettingObject(SignalDispatcher):
 				getattr(self, name)
 			assert self._check_type(name, value)
 			kws = dict(setting=name, value=value,
-					previous=self._settings[name])
+					previous=self._settings[name], fm=self.fm)
 			self.signal_emit('setopt', **kws)
 			self.signal_emit('setopt.'+name, **kws)
 
@@ -128,35 +129,3 @@ class SettingObject(SignalDispatcher):
 
 	def _raw_set_with_signal(self, signal):
 		self._settings[signal.setting] = signal.value
-
-
-# -- globalize the settings --
-class SettingsAware(object):
-	settings = OpenStruct()
-
-	@staticmethod
-	def _setup():
-		settings = SettingObject()
-
-		from ranger.gui.colorscheme import _colorscheme_name_to_class
-		settings.signal_bind('setopt.colorscheme',
-				_colorscheme_name_to_class, priority=1)
-
-		if not ranger.arg.clean:
-			# overwrite single default options with custom options
-			sys.path[0:0] = [ranger.arg.confdir]
-			try:
-				import options as my_options
-			except ImportError:
-				pass
-			else:
-				settings._setting_sources.append(my_options)
-			del sys.path[0]
-
-		from ranger.defaults import options as default_options
-		settings._setting_sources.append(default_options)
-		assert all(hasattr(default_options, setting) \
-				for setting in ALLOWED_SETTINGS), \
-				"Ensure that all options are defined in the defaults!"
-
-		SettingsAware.settings = settings
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index a19927a4..d5f740a3 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -17,7 +17,7 @@ import os
 import re
 import shutil
 import string
-from os.path import join, isdir
+from os.path import join, isdir, realpath
 from os import symlink, getcwd
 from inspect import cleandoc
 
@@ -26,10 +26,10 @@ from ranger.ext.direction import Direction
 from ranger.ext.relative_symlink import relative_symlink
 from ranger.ext.shell_escape import shell_quote
 from ranger import fsobject
-from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware
+from ranger.core.shared import FileManagerAware, EnvironmentAware, \
+		SettingsAware
 from ranger.fsobject import File
-from ranger.ext import shutil_generatorized as shutil_g
-from ranger.core.loader import LoadableObject
+from ranger.core.loader import CommandLoader
 
 class _MacroTemplate(string.Template):
 	"""A template for substituting macros in commands"""
@@ -51,6 +51,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def reset(self):
 		"""Reset the filemanager, clearing the directory buffer"""
 		old_path = self.env.cwd.path
+		self.previews = {}
 		self.env.garbage_collect(-1)
 		self.enter_dir(old_path)
 
@@ -367,16 +368,16 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# -- Searching
 	# --------------------------
 
-	def search_file(self, text, regexp=True):
+	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.env.last_search = text
-		self.search(order='search')
+		self.search(order='search', offset=offset)
 
-	def search(self, order=None, forward=True):
+	def search(self, order=None, offset=1, forward=True):
 		original_order = order
 		if self.search_forward:
 			direction = bool(forward)
@@ -400,7 +401,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			elif order == 'tag':
 				fnc = lambda x: x.realpath in self.tags
 
-			return self.env.cwd.search_fnc(fnc=fnc, forward=forward)
+			return self.env.cwd.search_fnc(fnc=fnc, offset=offset, forward=forward)
 
 		elif order in ('size', 'mimetype', 'ctime'):
 			cwd = self.env.cwd
@@ -429,39 +430,33 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# Tags are saved in ~/.config/ranger/tagged and simply mark if a
 	# file is important to you in any context.
 
-	def tag_toggle(self, movedown=None):
-		try:
-			toggle = self.tags.toggle
-		except AttributeError:
+	def tag_toggle(self, paths=None, value=None, movedown=None):
+		if not self.tags:
 			return
-
-		sel = self.env.get_selection()
-		toggle(*tuple(map(lambda x: x.realpath, sel)))
+		if paths is None:
+			tags = tuple(x.realpath for x in self.env.get_selection())
+		else:
+			tags = [realpath(path) for path in paths]
+		if value is True:
+			self.tags.add(*tags)
+		elif value is False:
+			self.tags.remove(*tags)
+		else:
+			self.tags.toggle(*tags)
 
 		if movedown is None:
-			movedown = len(sel) == 1
+			movedown = len(tags) == 1 and paths is None
 		if movedown:
 			self.move(down=1)
 
 		if hasattr(self.ui, 'redraw_main_column'):
 			self.ui.redraw_main_column()
 
-	def tag_remove(self, movedown=None):
-		try:
-			remove = self.tags.remove
-		except AttributeError:
-			return
+	def tag_remove(self, paths=None, movedown=None):
+		self.tag_toggle(paths=paths, value=False, movedown=movedown)
 
-		sel = self.env.get_selection()
-		remove(*tuple(map(lambda x: x.realpath, sel)))
-
-		if movedown is None:
-			movedown = len(sel) == 1
-		if movedown:
-			self.move(down=1)
-
-		if hasattr(self.ui, 'redraw_main_column'):
-			self.ui.redraw_main_column()
+	def tag_add(self, paths=None, movedown=None):
+		self.tag_toggle(paths=paths, value=True, movedown=movedown)
 
 	# --------------------------
 	# -- Bookmarks
@@ -471,6 +466,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def enter_bookmark(self, key):
 		"""Enter the bookmark with the name <key>"""
 		try:
+			self.bookmarks.update_if_outdated()
 			destination = self.bookmarks[key]
 			cwd = self.env.cwd
 			if destination.path != cwd.path:
@@ -481,10 +477,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def set_bookmark(self, key):
 		"""Set the bookmark with the name <key> to the current directory"""
+		self.bookmarks.update_if_outdated()
 		self.bookmarks[key] = self.env.cwd
 
 	def unset_bookmark(self, key):
 		"""Delete the bookmark with the name <key>"""
+		self.bookmarks.update_if_outdated()
 		self.bookmarks.delete(key)
 
 	def draw_bookmarks(self):
@@ -564,14 +562,86 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	def display_file(self):
 		if not hasattr(self.ui, 'open_embedded_pager'):
 			return
+		if not self.env.cf or not self.env.cf.is_file:
+			return
+
+		pager = self.ui.open_embedded_pager()
+		pager.set_source(self.env.cf.get_preview_source(pager.wid, pager.hei))
 
+	# --------------------------
+	# -- Previews
+	# --------------------------
+	def update_preview(self, path):
 		try:
-			f = open(self.env.cf.path, 'r')
+			del self.previews[path]
+			self.ui.need_redraw = True
 		except:
-			pass
+			return False
+
+	def get_preview(self, path, width, height):
+		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)
+			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:
+				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:
+						data[(-1, -1)] = open(path, 'r').read(1024 * 32)
+					else:
+						data[(-1, -1)] = None
+					if self.env.cf.realpath == path:
+						self.ui.browser.need_redraw = True
+					data['loading'] = False
+					pager = self.ui.browser.pager
+					if self.env.cf and self.env.cf.is_file:
+						pager.set_source(self.env.cf.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:
-			pager = self.ui.open_embedded_pager()
-			pager.set_source(f)
+			try:
+				return open(path, 'r')
+			except:
+				return None
 
 	# --------------------------
 	# -- Tabs
@@ -681,11 +751,19 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if not copied_files:
 			return
 
-		original_path = self.env.cwd.path
-		try:
-			one_file = copied_files[0]
-		except:
-			one_file = None
+		def refresh(_):
+			cwd = self.env.get_directory(original_path)
+			cwd.load_content()
+
+		cwd = self.env.cwd
+		original_path = cwd.path
+		one_file = copied_files[0]
+		if overwrite:
+			cp_flags = ['-af', '--']
+			mv_flags = ['-f', '--']
+		else:
+			cp_flags = ['--backup=numbered', '-a', '--']
+			mv_flags = ['--backup=numbered', '--']
 
 		if self.env.cut:
 			self.env.copy.clear()
@@ -694,36 +772,27 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				descr = "moving: " + one_file.path
 			else:
 				descr = "moving files from: " + one_file.dirname
-			def generate():
-				for f in copied_files:
-					for _ in shutil_g.move(src=f.path,
-							dst=original_path,
-							overwrite=overwrite):
-						yield
-				cwd = self.env.get_directory(original_path)
-				cwd.load_content()
+			obj = CommandLoader(args=['mv'] + mv_flags \
+					+ [f.path for f in copied_files] \
+					+ [cwd.path], descr=descr)
 		else:
 			if len(copied_files) == 1:
 				descr = "copying: " + one_file.path
 			else:
 				descr = "copying files from: " + one_file.dirname
-			def generate():
-				for f in self.env.copy:
-					if isdir(f.path):
-						for _ in shutil_g.copytree(src=f.path,
-								dst=join(original_path, f.basename),
-								symlinks=True,
-								overwrite=overwrite):
-							yield
-					else:
-						for _ in shutil_g.copy2(f.path, original_path,
-								symlinks=True,
-								overwrite=overwrite):
-							yield
-				cwd = self.env.get_directory(original_path)
-				cwd.load_content()
-
-		self.loader.add(LoadableObject(generate(), descr))
+			if not overwrite and len(copied_files) == 1 \
+					and one_file.dirname == cwd.path:
+				# Special case: yypp
+				# copying a file onto itself -> create a backup
+				obj = CommandLoader(args=['cp', '-f'] + cp_flags \
+						+ [one_file.path, one_file.path], descr=descr)
+			else:
+				obj = CommandLoader(args=['cp'] + cp_flags \
+						+ [f.path for f in copied_files] \
+						+ [cwd.path], descr=descr)
+
+		obj.signal_bind('after', refresh)
+		self.loader.add(obj)
 
 	def delete(self):
 		self.notify("Deleting!", duration=1)
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index 417e36c0..cf140410 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -21,8 +21,8 @@ from os.path import abspath, normpath, join, expanduser, isdir
 
 from ranger.fsobject import Directory
 from ranger.container import KeyBuffer, KeyManager, History
-from ranger.ext.signal_dispatcher import SignalDispatcher
-from ranger.shared import SettingsAware
+from ranger.ext.signals import SignalDispatcher
+from ranger.core.shared import SettingsAware
 
 ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview',
 		'console')
@@ -124,7 +124,8 @@ class Environment(SettingsAware, SignalDispatcher):
 		"""Delete unused directory objects"""
 		for key in tuple(self.directories):
 			value = self.directories[key]
-			if value.is_older_than(age) and not value in self.pathway:
+			if age == -1 or \
+					(value.is_older_than(age) and not value in self.pathway):
 				del self.directories[key]
 				if value.is_directory:
 					value.files = None
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 05b3e52b..116717ca 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -19,7 +19,9 @@ The File Manager, putting the pieces together
 
 from time import time
 from collections import deque
+import mimetypes
 import os
+import stat
 import sys
 
 import ranger
@@ -28,10 +30,9 @@ from ranger.container.tags import Tags
 from ranger.gui.defaultui import DefaultUI
 from ranger.container import Bookmarks
 from ranger.core.runner import Runner
-from ranger import relpath_conf
 from ranger.ext.get_executables import get_executables
 from ranger.fsobject import Directory
-from ranger.ext.signal_dispatcher import SignalDispatcher
+from ranger.ext.signals import SignalDispatcher
 from ranger import __version__
 from ranger.core.loader import Loader
 
@@ -50,6 +51,8 @@ class FM(Actions, SignalDispatcher):
 		self.bookmarks = bookmarks
 		self.tags = tags
 		self.tabs = {}
+		self.py3 = sys.version_info >= (3, )
+		self.previews = {}
 		self.current_tab = 1
 		self.loader = Loader()
 
@@ -69,7 +72,7 @@ class FM(Actions, SignalDispatcher):
 			if ranger.arg.clean:
 				bookmarkfile = None
 			else:
-				bookmarkfile = relpath_conf('bookmarks')
+				bookmarkfile = self.confpath('bookmarks')
 			self.bookmarks = Bookmarks(
 					bookmarkfile=bookmarkfile,
 					bookmarktype=Directory,
@@ -80,7 +83,7 @@ class FM(Actions, SignalDispatcher):
 			self.bookmarks = bookmarks
 
 		if not ranger.arg.clean and self.tags is None:
-			self.tags = Tags(relpath_conf('tagged'))
+			self.tags = Tags(self.confpath('tagged'))
 
 		if self.ui is None:
 			self.ui = DefaultUI()
@@ -93,6 +96,25 @@ class FM(Actions, SignalDispatcher):
 
 		self.env.signal_bind('cd', self._update_current_tab)
 
+		mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
+		mimetypes.knownfiles.append(self.relpath('data/mime.types'))
+		self.mimetypes = mimetypes.MimeTypes()
+
+	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 block_input(self, sec=0):
 		self.input_blocked = sec != 0
 		self.input_blocked_until = time() + sec
@@ -102,6 +124,47 @@ class FM(Actions, SignalDispatcher):
 			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 == 'apps' or which == 'all':
+			copy('defaults/apps.py', 'apps.py')
+		if which == 'commands' or which == 'all':
+			copy('defaults/commands.py', 'commands.py')
+		if which == 'keys' or which == 'all':
+			copy('defaults/keys.py', 'keys.py')
+		if which == 'options' or which == 'all':
+			copy('defaults/options.py', 'options.py')
+		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 not in \
+				('all', 'apps', 'scope', 'commands', 'keys', 'options'):
+			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 loop(self):
 		"""
 		The main loop consists of:
@@ -119,14 +182,12 @@ class FM(Actions, SignalDispatcher):
 		# for faster lookup:
 		ui = self.ui
 		throbber = ui.throbber
-		bookmarks = self.bookmarks
 		loader = self.loader
 		env = self.env
 		has_throbber = hasattr(ui, 'throbber')
 
 		try:
 			while True:
-				bookmarks.update_if_outdated()
 				loader.work()
 				if has_throbber:
 					if loader.has_work():
@@ -151,5 +212,5 @@ class FM(Actions, SignalDispatcher):
 			raise SystemExit
 
 		finally:
-			bookmarks.remember(env.cwd)
-			bookmarks.save()
+			self.bookmarks.remember(env.cwd)
+			self.bookmarks.save()
diff --git a/ranger/__main__.py b/ranger/core/helper.py
index 7a31f6a3..3c018192 100644
--- a/ranger/__main__.py
+++ b/ranger/core/helper.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python
-# coding=utf-8
-#
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
 #
 # This program is free software: you can redistribute it and/or modify
@@ -16,19 +13,26 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# Most import statements in this module are inside the functions.
-# This enables more convenient exception handling in ranger.py
-# (ImportError will imply that this module can't be found)
+"""Helper functions"""
 
-import locale
 import os.path
 import sys
+from ranger import *
+
+LOGFILE = '/tmp/errorlog'
 
 def parse_arguments():
 	"""Parse the program arguments"""
 	from optparse import OptionParser, SUPPRESS_HELP
-	from ranger import __version__, USAGE, DEFAULT_CONFDIR
+	from ranger import __version__
 	from ranger.ext.openstruct import OpenStruct
+	from os.path import expanduser
+
+	if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']:
+		default_confdir = os.environ['XDG_CONFIG_HOME'] + '/ranger'
+	else:
+		default_confdir = '~/.config/ranger'
+	usage = '%prog [options] [path/filename]'
 
 	minor_version = __version__[2:]  # assumes major version number is <10
 	if '.' in minor_version:
@@ -39,7 +43,7 @@ def parse_arguments():
 	else:
 		version_string = 'ranger ' + __version__ + version_tag
 
-	parser = OptionParser(usage=USAGE, version=version_string)
+	parser = OptionParser(usage=usage, version=version_string)
 
 	parser.add_option('-d', '--debug', action='store_true',
 			help="activate debug mode")
@@ -47,11 +51,14 @@ def parse_arguments():
 			help="don't touch/require any config files. ")
 	parser.add_option('--fail-if-run', action='store_true', # COMPAT
 			help=SUPPRESS_HELP)
+	parser.add_option('--copy-config', type='string', metavar='which',
+			help="copy the default configs to the local config directory. "
+			"Possible values: all, apps, commands, keys, options, 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,
+			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")
@@ -61,7 +68,7 @@ def parse_arguments():
 
 	options, positional = parser.parse_args()
 	arg = OpenStruct(options.__dict__, targets=positional)
-	arg.confdir = os.path.expanduser(arg.confdir)
+	arg.confdir = expanduser(arg.confdir)
 	if arg.fail_if_run:
 		arg.fail_unless_cd = arg.fail_if_run
 		del arg['fail_if_run']
@@ -69,26 +76,8 @@ def parse_arguments():
 	return arg
 
 
-def allow_access_to_confdir(confdir, allow):
-	if allow:
-		try:
-			os.makedirs(confdir)
-		except OSError as err:
-			if err.errno != 17:  # 17 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]
-
-
 def load_settings(fm, clean):
-	import ranger.shared
+	import ranger.core.shared
 	import ranger.api.commands
 	import ranger.api.keys
 	if not clean:
@@ -113,24 +102,19 @@ def load_settings(fm, clean):
 			from ranger.defaults import apps
 
 		# Load keys
-		keymanager = ranger.shared.EnvironmentAware.env.keymanager
+		keymanager = ranger.core.shared.EnvironmentAware.env.keymanager
 		ranger.api.keys.keymanager = keymanager
 		from ranger.defaults import keys
 		try:
 			import keys
 		except ImportError:
 			pass
-		# COMPAT WARNING
-		if hasattr(keys, 'initialize_commands'):
-			print("Warning: the syntax for ~/.config/ranger/keys.py has changed.")
-			print("Your custom keys are not loaded."\
-					"  Please update your configuration.")
 		allow_access_to_confdir(ranger.arg.confdir, False)
 	else:
 		comcont = ranger.api.commands.CommandContainer()
 		ranger.api.commands.alias = comcont.alias
 		from ranger.api import keys
-		keymanager = ranger.shared.EnvironmentAware.env.keymanager
+		keymanager = ranger.core.shared.EnvironmentAware.env.keymanager
 		ranger.api.keys.keymanager = keymanager
 		from ranger.defaults import commands, keys, apps
 		comcont.load_commands_from_module(commands)
@@ -154,96 +138,43 @@ def load_apps(fm, clean):
 	fm.apps = apps.CustomApplications()
 
 
-def main():
-	"""initialize objects and run the filemanager"""
-	try:
-		import curses
-	except ImportError as errormessage:
-		print(errormessage)
-		print('ranger requires the python curses module. Aborting.')
-		sys.exit(1)
-
-	try: locale.setlocale(locale.LC_ALL, '')
-	except: print("Warning: Unable to set locale.  Expect encoding problems.")
-
-	if not 'SHELL' in os.environ:
-		os.environ['SHELL'] = 'bash'
+def allow_access_to_confdir(confdir, allow):
+	if allow:
+		try:
+			os.makedirs(confdir)
+		except OSError as err:
+			if err.errno != 17:  # 17 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]
 
-	arg = parse_arguments()
-	if arg.clean:
-		sys.dont_write_bytecode = True
 
-	# Need to decide whether to write bytecode or not before importing.
-	import ranger
-	from ranger.ext import curses_interrupt_handler
-	from ranger.core.runner import Runner
-	from ranger.core.fm import FM
-	from ranger.core.environment import Environment
-	from ranger.gui.defaultui import DefaultUI as UI
-	from ranger.fsobject import File
-	from ranger.shared import (EnvironmentAware, FileManagerAware,
-			SettingsAware)
-
-	if not arg.debug:
-		curses_interrupt_handler.install_interrupt_handler()
-	ranger.arg = arg
-
-	SettingsAware._setup()
-
-	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)
-			sys.exit(1)
-		elif os.path.isfile(target):
-			def print_function(string):
-				print(string)
-			runner = Runner(logfunc=print_function)
-			load_apps(runner, ranger.arg.clean)
-			runner(files=[File(target)], mode=arg.mode, flags=arg.flags)
-			sys.exit(1 if arg.fail_unless_cd else 0)
-
-	crash_traceback = None
-	try:
-		# Initialize objects
-		EnvironmentAware._assign(Environment(target))
-		fm = FM()
-		fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \
-				in enumerate(targets[:9]))
-		load_settings(fm, ranger.arg.clean)
-		if fm.env.username == 'root':
-			fm.settings.preview_files = False
-		FileManagerAware._assign(fm)
-		fm.ui = UI()
-
-		# Run the file manager
-		fm.initialize()
-		fm.ui.initialize()
-		fm.loop()
-	except Exception:
-		import traceback
-		crash_traceback = traceback.format_exc()
-	except SystemExit as error:
-		return error.args[0]
-	finally:
-		try:
-			fm.ui.destroy()
-		except (AttributeError, NameError):
-			pass
-		if crash_traceback:
-			print(crash_traceback)
-			print("Ranger crashed.  " \
-					"Please report this (including the traceback) at:")
-			print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem")
-			return 1
-		return 0
-
-
-if __name__ == '__main__':
-	# The ranger directory can be executed directly, for example by typing
-	# python /usr/lib/python2.6/site-packages/ranger
-	sys.path.insert(0, os.path.dirname(sys.path[0]))
-	main()
+# Debugging functions.  These will be activated when run with --debug.
+# 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 = 'start' in keywords and keywords['start'] or 'ranger:'
+	sep   =   'sep' in keywords and keywords['sep']   or ' '
+	_file =  'file' in keywords and keywords['file']  or open(LOGFILE, 'a')
+	end   =   'end' in keywords and keywords['end']   or '\n'
+	_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'))
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 4f4424e4..4341595c 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -14,19 +14,19 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from collections import deque
+from time import time, sleep
+from subprocess import Popen, PIPE
 from time import time
-from ranger.shared import FileManagerAware
+from ranger.core.shared import FileManagerAware
+from ranger.ext.signals import SignalDispatcher
 import math
+import os
+import sys
+import select
 
-def status_generator():
-	"""Generate a rotating line which can be used as a throbber"""
-	while True:
-		yield '/'
-		yield '-'
-		yield '\\'
-		yield '|'
 
-class LoadableObject(object):
+class Loadable(object):
+	paused = False
 	def __init__(self, gen, descr):
 		self.load_generator = gen
 		self.description = descr
@@ -34,22 +34,127 @@ class LoadableObject(object):
 	def get_description(self):
 		return self.description
 
+	def pause(self):
+		self.paused = True
+
+	def unpause(self):
+		try:
+			del self.paused
+		except:
+			pass
+
+	def destroy(self):
+		pass
+
+
+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):
+		self.process = process = Popen(self.args,
+				stdout=PIPE, stderr=PIPE)
+		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 = read.decode('utf-8')
+							if read:
+								self.fm.notify(read, bad=True)
+						elif rd == process.stdout:
+							read = rd.read(512)
+							if py3:
+								read = read.decode('utf-8')
+							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 = l.decode('utf-8')
+					self.fm.notify(l, bad=True)
+			if self.read:
+				read = process.stdout.read()
+				if py3:
+					read = read.decode('utf-8')
+				self.stdout_buffer += read
+		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()
+
 
 class Loader(FileManagerAware):
 	seconds_of_work_time = 0.03
+	throbber_chars = r'/-\|'
 
 	def __init__(self):
 		self.queue = deque()
 		self.item = None
 		self.load_generator = None
-		self.status_generator = status_generator()
+		self.throbber_status = 0
 		self.rotate()
 		self.old_item = None
 
 	def rotate(self):
 		"""Rotate the throbber"""
 		# TODO: move all throbber logic to UI
-		self.status = next(self.status_generator)
+		self.throbber_status = \
+			(self.throbber_status + 1) % len(self.throbber_chars)
+		self.status = self.throbber_chars[self.throbber_status]
 
 	def add(self, obj):
 		"""
@@ -70,6 +175,8 @@ class Loader(FileManagerAware):
 
 		if to == 0:
 			self.queue.appendleft(item)
+			if _from != 0:
+				self.queue[1].pause()
 		elif to == -1:
 			self.queue.append(item)
 		else:
@@ -89,6 +196,7 @@ class Loader(FileManagerAware):
 				item = self.queue[index]
 			if hasattr(item, 'unload'):
 				item.unload()
+			item.destroy()
 			del self.queue[index]
 
 	def work(self):
@@ -109,7 +217,10 @@ class Loader(FileManagerAware):
 
 		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
 
@@ -125,3 +236,7 @@ class Loader(FileManagerAware):
 	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
new file mode 100644
index 00000000..ade3bab5
--- /dev/null
+++ b/ranger/core/main.py
@@ -0,0 +1,108 @@
+# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+The main function responsible to initialize the FM object and stuff.
+"""
+
+from ranger.core.helper import *
+
+def main():
+	"""initialize objects and run the filemanager"""
+	import locale
+	import os.path
+	import ranger
+	from ranger.core.shared import (EnvironmentAware, FileManagerAware,
+			SettingsAware)
+	from ranger.core.fm import FM
+
+	try:
+		locale.setlocale(locale.LC_ALL, '')
+	except:
+		print("Warning: Unable to set locale.  Expect encoding problems.")
+
+	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
+
+	SettingsAware._setup(clean=arg.clean)
+
+	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.core.runner import Runner
+			from ranger.fsobject import File
+			runner = Runner(logfunc=print_function)
+			load_apps(runner, arg.clean)
+			runner(files=[File(target)], mode=arg.mode, flags=arg.flags)
+			return 1 if arg.fail_unless_cd else 0
+
+	crash_traceback = None
+	try:
+		# Initialize objects
+		from ranger.core.environment import Environment
+		fm = FM()
+		FileManagerAware.fm = fm
+		EnvironmentAware.env = Environment(target)
+		fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \
+				in enumerate(targets[:9]))
+		load_settings(fm, arg.clean)
+		if fm.env.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()
+		fm.ui.initialize()
+		fm.loop()
+	except Exception:
+		import traceback
+		crash_traceback = traceback.format_exc()
+	except SystemExit as error:
+		return error.args[0]
+	finally:
+		if crash_traceback:
+			filepath = fm.env.cf.path if fm.env.cf else "None"
+		try:
+			fm.ui.destroy()
+		except (AttributeError, NameError):
+			pass
+		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()))
+			print("Current file: %s" % filepath)
+			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
diff --git a/ranger/core/shared.py b/ranger/core/shared.py
new file mode 100644
index 00000000..38dc7cdd
--- /dev/null
+++ b/ranger/core/shared.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Shared objects contain singleton variables which can be
+inherited, essentially acting like global variables."""
+
+from ranger.ext.lazy_property import lazy_property
+import os.path
+
+class Awareness(object):
+	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(".")
+
+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()
+
+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()
+
+	@staticmethod
+	def _setup(clean=True):
+		from ranger.container.settingobject import SettingObject, \
+				ALLOWED_SETTINGS
+		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)
+
+		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:
+				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)
+
+		if not clean:
+			# add the custom options to the list of setting sources
+			sys.path[0:0] = [ranger.arg.confdir]
+			try:
+				import options as my_options
+			except ImportError:
+				pass
+			else:
+				settings._setting_sources.append(my_options)
+			del sys.path[0]
+
+		from ranger.defaults import options as default_options
+		settings._setting_sources.append(default_options)
+		assert all(hasattr(default_options, setting) \
+				for setting in ALLOWED_SETTINGS), \
+				"Ensure that all options are defined in the defaults!"
+		SettingsAware.settings = settings
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
new file mode 100755
index 00000000..754b54d0
--- /dev/null
+++ b/ranger/data/scope.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+# This script is called whenever you preview a file.
+# Its output is used as the preview.  ANSI color codes are supported.
+
+# NOTES: This script is considered a configuration file.  If you upgrade
+# ranger, it will be left untouched. (You must update it yourself)
+# NEVER make this script interactive. (by starting mplayer or something)
+
+# Meanings of exit codes:
+# code | meaning    | action of ranger
+# -----+------------+-------------------------------------------
+# 0    | success    | success. display stdout as preview
+# 1    | no preview | failure. display no preview at all
+# 2    | plain text | display the plain content of the file
+# 3    | fix width  | success. Don't reload when width changes
+# 4    | fix height | success. Don't reload when height changes
+# 5    | fix both   | success. Don't ever reload
+
+# Meaningful aliases for arguments:
+path="$1"    # Full path of the selected file
+width="$2"   # Width of the preview pane (number of fitting characters)
+height="$3"  # Height of the preview pane (number of fitting characters)
+
+maxln=200    # Stop after $maxln lines.  Can be used like ls | head -n $maxln
+
+# Find out something about the file:
+mimetype=$(file --mime-type -Lb "$path")
+extension=$(echo "$path" | grep '\.' | grep -o '[^.]\+$')
+
+# Functions:
+# "have $1" succeeds if $1 is an existing command/installed program
+function have { type -P "$1" > /dev/null; }
+# "sucess" returns the exit code of the first program in the last pipe chain
+function success { test ${PIPESTATUS[0]} = 0; }
+
+case "$extension" in
+	# Archive extensions:
+	7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
+	rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)
+		atool -l "$path" | head -n $maxln && exit 3
+		exit 1;;
+	# PDF documents:
+	pdf)
+		pdftotext -q "$path" - | head -n $maxln
+		success && exit 3 || exit 1;;
+	# HTML Pages:
+	htm|html|xhtml)
+		have lynx   && lynx   -dump "$path" | head -n $maxln && exit 5
+		have elinks && elinks -dump "$path" | head -n $maxln && exit 5
+		;; # fall back to highlight/cat if theres no lynx/elinks
+esac
+
+case "$mimetype" in
+	# Syntax highlight for text files:
+	text/* | */xml)
+		highlight --ansi "$path" | head -n $maxln
+		success && exit 5 || exit 2;;
+	# Ascii-previews of images:
+	image/*)
+		img2txt --gamma=0.6 --width="$width" "$path" && exit 4 || exit 1;;
+	# Display information about media files:
+	video/* | audio/*)
+		# Use sed to remove spaces so the output fits into the narrow window
+		mediainfo "$path" | sed 's/  \+:/: /;'
+		success && exit 5 || exit 1;;
+esac
+
+exit 1
diff --git a/ranger/defaults/apps.py b/ranger/defaults/apps.py
index 1f9ba763..85abb2dc 100644
--- a/ranger/defaults/apps.py
+++ b/ranger/defaults/apps.py
@@ -90,6 +90,10 @@ class CustomApplications(Applications):
 		if f.document or f.filetype.startswith('text') or f.size == 0:
 			return self.either(c, 'editor')
 
+		# You can put this at the top of the function and mimeopen will
+		# always be used for every file.
+		return self.either(c, 'mimeopen')
+
 
 	# ----------------------------------------- application definitions
 	# Note: Trivial applications are defined at the bottom
@@ -181,6 +185,14 @@ class CustomApplications(Applications):
 		if c.mode is 1:
 			return tup("totem", "--fullscreen", *c)
 
+	@depends_on('mimeopen')
+	def app_mimeopen(self, c):
+		if c.mode is 0:
+			return tup("mimeopen", *c)
+		if c.mode is 1: 
+			# Will ask user to select program
+			# aka "Open with..."
+			return tup("mimeopen", "--ask", *c)
 
 # Often a programs invocation is trivial.  For example:
 #    vim test.py readme.txt [...]
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index b874c5fa..9861ddeb 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -91,6 +91,11 @@ class search(Command):
 		self.fm.search_file(parse(self.line).rest(1), regexp=True)
 
 
+class search_inc(Command):
+	def quick(self):
+		self.fm.search_file(parse(self.line).rest(1), regexp=True, offset=0)
+
+
 class shell(Command):
 	def execute(self):
 		line = parse(self.line)
@@ -135,7 +140,7 @@ class open_with(Command):
 		line = parse(self.line)
 		app, flags, mode = self._get_app_flags_mode(line.rest(1))
 		self.fm.execute_file(
-				files = [self.fm.env.cf],
+				files = [f for f in self.fm.env.cwd.get_selection()],
 				app = app,
 				flags = flags,
 				mode = mode)
@@ -288,12 +293,13 @@ class find(Command):
 		return self.count == 1
 
 
-class set(Command):
+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):
 		line = parse(self.line)
 		name = line.chunk(1)
@@ -307,8 +313,6 @@ class set(Command):
 
 	def tab(self):
 		line = parse(self.line)
-		from ranger import log
-		log(line.parse_setting_line())
 		name, value, name_done = line.parse_setting_line()
 		settings = self.fm.settings
 		if not name:
@@ -436,6 +440,42 @@ class mark(Command):
 		self.fm.ui.need_redraw = True
 
 
+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:
+			f = open(self.fm.confpath(self.copy_buffer_filename), 'r')
+		except:
+			return self.fm.notify("Cannot open file %s" % fname, bad=True)
+		self.fm.env.copy = 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):
+		try:
+			f = open(self.fm.confpath(self.copy_buffer_filename), 'w')
+		except:
+			return self.fm.notify("Cannot open file %s" % fname, bad=True)
+		f.write("\n".join(f.path for f in self.fm.env.copy))
+		f.close()
+
+
 class unmark(mark):
 	"""
 	:unmark <regexp>
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index e9ac4bde..782af310 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -170,6 +170,9 @@ map('u<C-V><dir>', fm.mark_in_direction(val=False))
 map('yy', 'y<dir>', fm.copy())
 map('ya', fm.copy(mode='add'))
 map('yr', fm.copy(mode='remove'))
+map('yp', fm.execute_console('shell -d echo -n %d/%f | xsel -i'))
+map('yd', fm.execute_console('shell -d echo -n %d | xsel -i'))
+map('yn', fm.execute_console('shell -d echo -n %f | xsel -i'))
 map('dd', 'd<dir>', fm.cut())
 map('da', fm.cut(mode='add'))
 map('dr', fm.cut(mode='remove'))
@@ -189,11 +192,12 @@ map('E', fm.edit_file())
 map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size'))
 
 # -------------------------------------------------- toggle options
-map('z<bg>', fm.hint("[*cdfhimpPs*] show_*h*idden *p*review_files "\
+map('z<bg>', fm.hint("[*cdfhimpPsv*] show_*h*idden *p*review_files "\
 		"*P*review_dirs *f*ilter flush*i*nput *m*ouse"))
 map('zh', '<C-h>', '<backspace>', fm.toggle_boolean_option('show_hidden'))
 map('zp', fm.toggle_boolean_option('preview_files'))
 map('zP', fm.toggle_boolean_option('preview_directories'))
+map('zv', fm.toggle_boolean_option('use_preview_script'))
 map('zi', fm.toggle_boolean_option('flushinput'))
 map('zd', fm.toggle_boolean_option('sort_directories_first'))
 map('zc', fm.toggle_boolean_option('collapse_preview'))
@@ -202,12 +206,12 @@ map('zm', fm.toggle_boolean_option('mouse_enabled'))
 map('zf', fm.open_console('filter '))
 
 # ------------------------------------------------------------ sort
-map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*ase*n*ame *m*time" \
-	" *t*ype *r*everse"))
+map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*asename *m*time" \
+	" *t*ype *r*everse *n*atural"))
 sort_dict = {
 	's': 'size',
 	'b': 'basename',
-	'n': 'basename',
+	'n': 'natural',
 	'm': 'mtime',
 	't': 'type',
 }
@@ -257,8 +261,8 @@ map('gR', fm.cd(RANGERDIR))
 
 # ------------------------------------------------------------ tabs
 map('gc', '<C-W>', fm.tab_close())
-map('gt', '<TAB>', fm.tab_move(1))
-map('gT', '<S-TAB>', fm.tab_move(-1))
+map('gt', '<TAB>', '<A-Right>', fm.tab_move(1))
+map('gT', '<S-TAB>', '<A-Left>', fm.tab_move(-1))
 @map('gn', '<C-N>')
 def newtab_and_gohome(arg):
 	arg.fm.tab_new()
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index 126d4bad..baaec7a3 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -36,9 +36,17 @@ from ranger.api.options import *
 # Which files should be hidden?  Toggle this by typing `zh' or
 # changing the setting `show_hidden'
 hidden_filter = regexp(
-	r'^\.|\.(?:pyc|pyo|bak|swp)$|~$|lost\+found')
+	r'^\.|\.(?:pyc|pyo|bak|swp)$|^lost\+found$')
 show_hidden = False
 
+# Which script is used to generate file previews?
+# Ranger ships with scope.sh, a script that calls external programs (see
+# README for dependencies) to preview images, archives, etc.
+preview_script = '~/.config/ranger/scope.sh'
+
+# Use that external preview script or display internal plain text previews?
+use_preview_script = True
+
 # Show dotfiles in the bookmark preview box?
 show_hidden_bookmarks = True
 
@@ -93,6 +101,10 @@ scroll_offset = 8
 # Flush the input after each key hit?  (Noticable when ranger lags)
 flushinput = True
 
+# Padding on the right when there's no preview?
+# This allows you to click into the space to run the file.
+padding_right = True
+
 # Save bookmarks (used with mX and `X) instantly?
 # This helps to synchronize bookmarks between multiple ranger
 # instances but leads to *slight* performance loss.
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index 40111b6d..3ce6498a 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -18,11 +18,11 @@ def human_readable(byte, seperator=' '):
 	Convert a large number of bytes to an easily readable format.
 
 	>>> human_readable(54)
-	"54 B"
+	'54 B'
 	>>> human_readable(1500)
-	"1.46 K"
+	'1.46 K'
 	>>> human_readable(2 ** 20 * 1023)
-	"1023 M"
+	'1023 M'
 	"""
 	if byte <= 0:
 		return '0'
@@ -49,3 +49,7 @@ def human_readable(byte, seperator=' '):
 	if byte < 2**60:
 		return '%.4g%sP' % (byte / 2**50.0, seperator)
 	return '>9000'
+
+if __name__ == '__main__':
+	import doctest
+	doctest.testmod()
diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py
index 0330c8ec..93119bce 100644
--- a/ranger/ext/keybinding_parser.py
+++ b/ranger/ext/keybinding_parser.py
@@ -21,7 +21,9 @@ def parse_keybinding(obj):
 	Translate a keybinding to a sequence of integers
 
 	Example:
-	lol<CR>   =>   (108, 111, 108, 10)
+	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):
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
deleted file mode 100644
index 436d2cb7..00000000
--- a/ranger/ext/shutil_generatorized.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# This file was taken from the python standard library and has been
-# slightly modified to do a "yield" after every 16KB of copying
-"""Utility functions for copying files and directory trees.
-
-XXX The functions here don't copy the resource fork or other metadata on Mac.
-
-"""
-
-import os
-import sys
-import stat
-from os.path import abspath
-
-__all__ = ["copyfileobj","copyfile","copystat","copy2",
-           "copytree","move","rmtree","Error", "SpecialFileError"]
-
-APPENDIX = '_'
-
-class Error(EnvironmentError):
-    pass
-
-class SpecialFileError(EnvironmentError):
-    """Raised when trying to do a kind of operation (e.g. copying) which is
-    not supported on a special file (e.g. a named pipe)"""
-
-try:
-    WindowsError
-except NameError:
-    WindowsError = None
-
-def copyfileobj(fsrc, fdst, length=16*1024):
-    """copy data from file-like object fsrc to file-like object fdst"""
-    while 1:
-        buf = fsrc.read(length)
-        if not buf:
-            break
-        fdst.write(buf)
-        yield
-
-def _samefile(src, dst):
-    # Macintosh, Unix.
-    if hasattr(os.path,'samefile'):
-        try:
-            return os.path.samefile(src, dst)
-        except OSError:
-            return False
-
-    # All other platforms: check for same pathname.
-    return (os.path.normcase(abspath(src)) ==
-            os.path.normcase(abspath(dst)))
-
-def copyfile(src, dst):
-    """Copy data from src to dst"""
-    if _samefile(src, dst):
-        raise Error("`%s` and `%s` are the same file" % (src, dst))
-
-    fsrc = None
-    fdst = None
-    for fn in [src, dst]:
-        try:
-            st = os.stat(fn)
-        except OSError:
-            # File most likely does not exist
-            pass
-        else:
-            # XXX What about other special files? (sockets, devices...)
-            if stat.S_ISFIFO(st.st_mode):
-                raise SpecialFileError("`%s` is a named pipe" % fn)
-    try:
-        fsrc = open(src, 'rb')
-        fdst = open(dst, 'wb')
-        for _ in copyfileobj(fsrc, fdst):
-            yield
-    finally:
-        if fdst:
-            fdst.close()
-        if fsrc:
-            fsrc.close()
-
-def copystat(src, dst):
-    """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
-    st = os.stat(src)
-    mode = stat.S_IMODE(st.st_mode)
-    if hasattr(os, 'utime'):
-        try: os.utime(dst, (st.st_atime, st.st_mtime))
-        except: pass
-    if hasattr(os, 'chmod'):
-        try: os.chmod(dst, mode)
-        except: pass
-    if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
-        try: os.chflags(dst, st.st_flags)
-        except: pass
-
-def copy2(src, dst, overwrite=False, symlinks=False):
-    """Copy data and all stat info ("cp -p src dst").
-
-    The destination may be a directory.
-
-    """
-    if os.path.isdir(dst):
-        dst = os.path.join(dst, os.path.basename(src))
-    if not overwrite:
-        dst = get_safe_path(dst)
-    if symlinks and os.path.islink(src):
-        linkto = os.readlink(src)
-        os.symlink(linkto, dst)
-    else:
-        for _ in copyfile(src, dst):
-            yield
-        copystat(src, dst)
-
-def get_safe_path(dst):
-    if not os.path.exists(dst):
-        return dst
-    if not dst.endswith(APPENDIX):
-        dst += APPENDIX
-        if not os.path.exists(dst):
-            return dst
-    n = 0
-    test_dst = dst + str(n)
-    while os.path.exists(test_dst):
-        n += 1
-        test_dst = dst + str(n)
-
-    return test_dst
-
-def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
-    """Recursively copy a directory tree using copy2().
-
-    The destination directory must not already exist.
-    If exception(s) occur, an Error is raised with a list of reasons.
-
-    If the optional symlinks flag is true, symbolic links in the
-    source tree result in symbolic links in the destination tree; if
-    it is false, the contents of the files pointed to by symbolic
-    links are copied.
-
-    The optional ignore argument is a callable. If given, it
-    is called with the `src` parameter, which is the directory
-    being visited by copytree(), and `names` which is the list of
-    `src` contents, as returned by os.listdir():
-
-        callable(src, names) -> ignored_names
-
-    Since copytree() is called recursively, the callable will be
-    called once for each directory that is copied. It returns a
-    list of names relative to the `src` directory that should
-    not be copied.
-
-    XXX Consider this example code rather than the ultimate tool.
-
-    """
-    names = os.listdir(src)
-    if ignore is not None:
-        ignored_names = ignore(src, names)
-    else:
-        ignored_names = set()
-
-    errors = []
-    try:
-        os.makedirs(dst)
-    except Exception as err:
-        if not overwrite:
-            dst = get_safe_path(dst)
-            os.makedirs(dst)
-    for name in names:
-        if name in ignored_names:
-            continue
-        srcname = os.path.join(src, name)
-        dstname = os.path.join(dst, name)
-        try:
-            if symlinks and os.path.islink(srcname):
-                linkto = os.readlink(srcname)
-                if os.path.lexists(dstname):
-                    if not os.path.islink(dstname) \
-                    or os.readlink(dstname) != linkto:
-                        os.unlink(dstname)
-                        os.symlink(linkto, dstname)
-            elif os.path.isdir(srcname):
-                for _ in copytree(srcname, dstname, symlinks,
-                        ignore, overwrite):
-                    yield
-            else:
-                # Will raise a SpecialFileError for unsupported file types
-                for _ in copy2(srcname, dstname,
-                        overwrite=overwrite, symlinks=symlinks):
-                    yield
-        # catch the Error from the recursive copytree so that we can
-        # continue with other files
-        except Error as err:
-            errors.extend(err.args[0])
-        except EnvironmentError as why:
-            errors.append((srcname, dstname, str(why)))
-    try:
-        copystat(src, dst)
-    except OSError as why:
-        if WindowsError is not None and isinstance(why, WindowsError):
-            # Copying file access times may fail on Windows
-            pass
-        else:
-            errors.extend((src, dst, str(why)))
-    if errors:
-        raise Error(errors)
-
-def rmtree(path, ignore_errors=False, onerror=None):
-    """Recursively delete a directory tree.
-
-    If ignore_errors is set, errors are ignored; otherwise, if onerror
-    is set, it is called to handle the error with arguments (func,
-    path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
-    path is the argument to that function that caused it to fail; and
-    exc_info is a tuple returned by sys.exc_info().  If ignore_errors
-    is false and onerror is None, an exception is raised.
-
-    """
-    if ignore_errors:
-        def onerror(*args):
-            pass
-    elif onerror is None:
-        def onerror(*args):
-            raise
-    try:
-        if os.path.islink(path):
-            # symlinks to directories are forbidden, see bug #1669
-            raise OSError("Cannot call rmtree on a symbolic link")
-    except OSError:
-        onerror(os.path.islink, path, sys.exc_info())
-        # can't continue even if onerror hook returns
-        return
-    names = []
-    try:
-        names = os.listdir(path)
-    except os.error as err:
-        onerror(os.listdir, path, sys.exc_info())
-    for name in names:
-        fullname = os.path.join(path, name)
-        try:
-            mode = os.lstat(fullname).st_mode
-        except os.error:
-            mode = 0
-        if stat.S_ISDIR(mode):
-            rmtree(fullname, ignore_errors, onerror)
-        else:
-            try:
-                os.remove(fullname)
-            except os.error as err:
-                onerror(os.remove, fullname, sys.exc_info())
-    try:
-        os.rmdir(path)
-    except os.error:
-        onerror(os.rmdir, path, sys.exc_info())
-
-
-def _basename(path):
-    # A basename() variant which first strips the trailing slash, if present.
-    # Thus we always get the last component of the path, even for directories.
-    return os.path.basename(path.rstrip(os.path.sep))
-
-def move(src, dst, overwrite=False):
-    """Recursively move a file or directory to another location. This is
-    similar to the Unix "mv" command.
-
-    If the destination is a directory or a symlink to a directory, the source
-    is moved inside the directory. The destination path must not already
-    exist.
-
-    If the destination already exists but is not a directory, it may be
-    overwritten depending on os.rename() semantics.
-
-    If the destination is on our current filesystem, then rename() is used.
-    Otherwise, src is copied to the destination and then removed.
-    A lot more could be done here...  A look at a mv.c shows a lot of
-    the issues this implementation glosses over.
-
-    """
-    real_dst = os.path.join(dst, _basename(src))
-    if not overwrite:
-        real_dst = get_safe_path(real_dst)
-    try:
-        os.rename(src, real_dst)
-    except OSError:
-        if os.path.isdir(src):
-            if _destinsrc(src, dst):
-                raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
-            for _ in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
-                yield
-            rmtree(src)
-        else:
-            for _ in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
-                yield
-            os.unlink(src)
-
-def _destinsrc(src, dst):
-    src = abspath(src)
-    dst = abspath(dst)
-    if not src.endswith(os.path.sep):
-        src += os.path.sep
-    if not dst.endswith(os.path.sep):
-        dst += os.path.sep
-    return dst.startswith(src)
-
-# vi: expandtab sts=4 ts=4 sw=4
diff --git a/ranger/ext/signal_dispatcher.py b/ranger/ext/signals.py
index 5d80590a..5d80590a 100644
--- a/ranger/ext/signal_dispatcher.py
+++ b/ranger/ext/signals.py
diff --git a/ranger/ext/utfwidth.py b/ranger/ext/utfwidth.py
index a506c676..0976fee1 100644
--- a/ranger/ext/utfwidth.py
+++ b/ranger/ext/utfwidth.py
@@ -58,9 +58,13 @@ def utf_byte_length(string):
 		return 4
 	return 1  # invalid
 
+
 def utf_char_width(string):
 	"""Return the width of a single character"""
 	u = _utf_char_to_int(string)
+	return utf_char_width_(u)
+
+def utf_char_width_(u):
 	if u < 0x1100:
 		return NARROW
 	# Hangul Jamo init. constonants
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index dcb4bf2c..c5c1e6a9 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -21,9 +21,10 @@ from os.path import join, isdir, basename
 from collections import deque
 from time import time
 
+from ranger.core.loader import Loadable
 from ranger.ext.mount_path import mount_path
 from ranger.fsobject import BAD_INFO, File, FileSystemObject
-from ranger.shared import SettingsAware
+from ranger.core.shared import SettingsAware
 from ranger.ext.accumulator import Accumulator
 import ranger.fsobject
 
@@ -39,6 +40,12 @@ def sort_by_directory(path):
 	"""returns 0 if path is a directory, otherwise 1 (for sorting)"""
 	return 1 - path.is_directory
 
+def sort_naturally(path):
+	return path.basename_natural
+
+def sort_naturally_icase(path):
+	return path.basename_natural_lower
+
 def accept_file(fname, hidden_filter, name_filter):
 	if hidden_filter:
 		try:
@@ -51,7 +58,7 @@ def accept_file(fname, hidden_filter, name_filter):
 		return False
 	return True
 
-class Directory(FileSystemObject, Accumulator, SettingsAware):
+class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 	is_directory = True
 	enterable = False
 	load_generator = None
@@ -76,6 +83,7 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 
 	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),
 		'type': lambda path: path.mimetype or '',
@@ -295,6 +303,10 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 				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:
@@ -327,18 +339,18 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 
 		Accumulator.move_to_obj(self, arg, attr='path')
 
-	def search_fnc(self, fnc, forward=True):
+	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 + 1)) % length \
-					for x in range(length-1))
+			generator = ((self.pointer + (x + offset)) % length \
+					for x in range(length - 1))
 		else:
-			generator = ((self.pointer - (x + 1)) % length \
-					for x in range(length-1))
+			generator = ((self.pointer - (x + offset)) % length \
+					for x in range(length - 1))
 
 		for i in generator:
 			_file = self.files[i]
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 5e79c2d1..9fce3255 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -14,10 +14,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-import zipfile
 from ranger.fsobject import FileSystemObject
+from subprocess import Popen, PIPE
+from ranger.core.runner import devnull
+from ranger.core.loader import CommandLoader
 
-N_FIRST_BYTES = 20
+N_FIRST_BYTES = 256
 control_characters = set(chr(n) for n in
 		set(range(0, 9)) | set(range(14, 32)))
 
@@ -28,12 +30,10 @@ PREVIEW_BLACKLIST = re.compile(r"""
 			# one character extensions:
 				[oa]
 			# media formats:
-				| avi | [mj]pe?g | mp\d | og[gmv] | wm[av] | mkv | flv
-				| png | bmp | vob | wav | mpc | flac | divx? | xcf | pdf
+				| 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
-			# containers:
-				| iso | rar | 7z | tar | gz | bz2 | tgz
 		)
 		# ignore filetype-independent suffixes:
 			(\.part|\.bak|~)?
@@ -54,6 +54,9 @@ PREVIEW_WHITELIST = re.compile(r"""
 
 class File(FileSystemObject):
 	is_file = True
+	preview_data = None
+	preview_known = False
+	preview_loading = False
 
 	@property
 	def firstbytes(self):
@@ -80,17 +83,20 @@ class File(FileSystemObject):
 			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.extension not in ('zip',) and self.is_binary():
+		if self.is_binary():
 			return False
 		return True
 
-	def get_preview_source(self):
-		if self.extension == 'zip':
-			return '\n'.join(zipfile.ZipFile(self.path).namelist())
-		return open(self.path, 'r')
+	def get_preview_source(self, width, height):
+		return self.fm.get_preview(self.realpath, width, height)
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index 4db24eba..bf71ac94 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -14,20 +14,23 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio',
-	'cpt', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar', 'shar',
-	'tar', 'tbz', 'tgz', 'xar', 'xz', 'zip')
+	'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar',
+	'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip')
 
+import re
 from os import access, listdir, lstat, readlink, stat
 from time import time
 from os.path import abspath, basename, dirname, realpath, splitext, extsep
 from . import BAD_INFO
-from ranger.shared import MimeTypeAware, FileManagerAware
+from ranger.core.shared import FileManagerAware
 from ranger.ext.shell_escape import shell_escape
 from ranger.ext.spawn import spawn
 from ranger.ext.lazy_property import lazy_property
 from ranger.ext.human_readable import human_readable
 
-class FileSystemObject(MimeTypeAware, FileManagerAware):
+_extract_number_re = re.compile(r'([^0-9]?)(\d*)')
+
+class FileSystemObject(FileManagerAware):
 	(basename,
 	basename_lower,
 	dirname,
@@ -67,8 +70,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 
 
 	def __init__(self, path, preload=None, path_is_abs=False):
-		MimeTypeAware.__init__(self)
-
 		if not path_is_abs:
 			path = abspath(path)
 		self.path = path
@@ -98,6 +99,16 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		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))]
+
 	for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
 		exec("%s = lazy_property("
 			"lambda self: self.set_mimetype() or self.%s)" % (attr, attr))
@@ -114,7 +125,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		basename = self.basename
 		if self.extension == 'part':
 			basename = basename[0:-5]
-		self._mimetype = self.mimetypes.guess_type(basename, False)[0]
+		self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0]
 		if self._mimetype is None:
 			self._mimetype = ''
 
@@ -170,6 +181,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		filesystem and caches it for later use
 		"""
 
+		self.fm.update_preview(self.path)
 		self.loaded = True
 
 		# Get the stat object, either from preload or from [l]stat
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py
new file mode 100644
index 00000000..ea024977
--- /dev/null
+++ b/ranger/gui/ansi.py
@@ -0,0 +1,96 @@
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
+# Copyright (C) 2010  Roman Zimbelmann <romanz@lavabit.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from ranger.gui import color
+import re
+
+ansi_re = re.compile('(\033' + r'\[\d*(?:;\d+)*?[a-zA-Z])')
+reset = '\033[0m'
+
+def split_ansi_from_text(ansi_text):
+	return ansi_re.split(ansi_text)
+
+def text_with_fg_bg_attr(ansi_text):
+	for chunk in split_ansi_from_text(ansi_text):
+		if chunk and chunk[0] == '\033':
+			if chunk[-1] != 'm':
+				continue
+			match = re.match(r'^.\[(.*).$', chunk)
+			attr_args = match.group(1)
+			fg, bg, attr = -1, -1, 0
+
+			# Convert arguments to attributes/colors
+			for arg in attr_args.split(';'):
+				try:
+					n = int(arg)
+				except:
+					if arg == '':
+						n = 0
+					else:
+						continue
+				if n == 0:
+					fg, bg, attr = -1, -1, 0
+				elif n == 1:
+					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 >= 30 and n <= 37:
+					fg = n - 30
+				elif n == 39:
+					fg = -1
+				elif n >= 40 and n <= 47:
+					bg = n - 40
+				elif n == 49:
+					bg = -1
+			yield (fg, bg, attr)
+		else:
+			yield chunk
+
+def char_len(ansi_text):
+	return len(ansi_re.sub('', ansi_text))
+
+def char_slice(ansi_text, start, end):
+	slice_chunks = []
+	# skip to start
+	last_color = None
+	skip_len_left = start
+	len_left = end - start
+	for chunk in split_ansi_from_text(ansi_text):
+		m = ansi_re.match(chunk)
+		if m:
+			if chunk[-1] == 'm':
+				last_color = chunk
+		else:
+			if skip_len_left > len(chunk):
+				skip_len_left -= len(chunk)
+			else:		# finished skipping to start
+				if skip_len_left > 0:
+					chunk = chunk[skip_len_left:]
+				chunk_left = chunk[:len_left]
+				if len(chunk_left):
+					if last_color is not None:
+						slice_chunks.append(last_color)
+					slice_chunks.append(chunk_left)
+					len_left -= len(chunk_left)
+				if len_left == 0:
+					break
+	return ''.join(slice_chunks)
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index 03ed2f78..aa5c9ab4 100644
--- a/ranger/gui/bar.py
+++ b/ranger/gui/bar.py
@@ -61,23 +61,27 @@ class Bar(object):
 		if sumsize < wid:
 			self.fill_gap(' ', (wid - sumsize), gapwidth=True)
 
-	def shrink_by_cutting(self, wid):
+	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 - 1
+		if oversize <= 0:
+			return self.fill_gap(' ', wid, gapwidth=False)
 		nonfixed_items = self.left.nonfixed_items()
 
-		itemsize = int(float(wid - rightsize - fixedsize) / \
-				(nonfixed_items + 1)) + 1
-
+		# Shrink items to a minimum size of 1 until there is enough room.
 		for item in self.left:
 			if not item.fixed:
-				item.cut_off_to(itemsize)
-
-		self.fill_gap(' ', wid, gapwidth=False)
+				itemlen = len(item)
+				if oversize > itemlen - 1:
+					item.cut_off_to(1)
+					oversize -= (itemlen - 1)
+				else:
+					item.cut_off(oversize)
+					break
 
 	def fill_gap(self, char, wid, gapwidth=False):
 		del self.gap[:]
@@ -127,8 +131,8 @@ class ColoredString(object):
 		self.fixed = False
 
 	def cut_off(self, n):
-		n = max(n, min(len(self.string), 1))
-		self.string = self.string[:-n]
+		if n >= 1:
+			self.string = self.string[:-n]
 
 	def cut_off_to(self, n):
 		self.string = self.string[:n]
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 58f0b38a..67be30f7 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -56,6 +56,7 @@ default = -1
 
 normal     = curses.A_NORMAL
 bold       = curses.A_BOLD
+blink      = curses.A_BLINK
 reverse    = curses.A_REVERSE
 underline  = curses.A_UNDERLINE
 invisible  = curses.A_INVIS
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index 5b317acb..5a2a97ef 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -35,10 +35,7 @@ associated with either True or False.
 
 define which colorscheme to use by having this to your options.py:
 from ranger import colorschemes
-colorscheme = colorschemes.filename
-
-If your colorscheme-file contains more than one colorscheme, specify it with:
-colorscheme = colorschemes.filename.classname
+colorscheme = "name"
 """
 
 import os
@@ -47,8 +44,8 @@ from curses import color_pair
 import ranger
 from ranger.gui.color import get_color
 from ranger.gui.context import Context
-from ranger.__main__ import allow_access_to_confdir
-from ranger.shared.settings import SettingsAware
+from ranger.core.helper import allow_access_to_confdir
+from ranger.core.shared import SettingsAware
 
 # ColorScheme is not SettingsAware but it will gain access
 # to the settings during the initialization.  We can't import
@@ -103,25 +100,14 @@ class ColorScheme(SettingsAware):
 		return attr | color_pair(get_color(fg, bg))
 
 	def use(self, context):
-		"""
-		Use the colorscheme to determine the (fg, bg, attr) tuple.
-
-		When no colorscheme is found, ranger will fall back to this very
-		basic colorscheme where directories are blue and bold, and
-		selected files have the color inverted.
+		"""Use the colorscheme to determine the (fg, bg, attr) tuple.
 
 		Override this method in your own colorscheme.
 		"""
-		fg, attr = -1, 0
-		if context.highlight or context.selected:
-			attr = 262144
-		if context.directory:
-			attr |= 2097152
-			fg = 4
-		return fg, -1, attr
+		return (-1, -1, 0)
 
 def _colorscheme_name_to_class(signal):
-	# Find the colorscheme.  First look for it at ~/.config/ranger/colorschemes,
+	# 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.
@@ -141,26 +127,26 @@ def _colorscheme_name_to_class(signal):
 
 	# create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist
 	if usecustom:
-		if os.path.exists(ranger.relpath_conf('colorschemes')):
-			initpy = ranger.relpath_conf('colorschemes', '__init__.py')
+		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(ranger.relpath_conf('colorschemes', scheme_name)):
+			exists(signal.fm.confpath('colorschemes', scheme_name)):
 		scheme_supermodule = 'colorschemes'
-	elif exists(ranger.relpath('colorschemes', scheme_name)):
+	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:
-		# XXX: dont print while curses is running
-		print("ERROR: colorscheme not found, fall back to builtin scheme")
-		if ranger.arg.debug:
-			raise Exception("Cannot locate colorscheme!")
-		signal.value = ColorScheme()
+		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,
diff --git a/ranger/gui/context.py b/ranger/gui/context.py
index 1e127a2e..20ce2817 100644
--- a/ranger/gui/context.py
+++ b/ranger/gui/context.py
@@ -22,7 +22,7 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo',
 		'selected', 'empty', 'main_column', 'message', 'background',
 		'good', 'bad',
 		'space', 'permissions', 'owner', 'group', 'mtime', 'nlink',
-		'scroll', 'all', 'bot', 'top', 'percentage',
+		'scroll', 'all', 'bot', 'top', 'percentage', 'filter',
 		'marked', 'tagged', 'tag_marker', 'cut', 'copied',
 		'help_markup',
 		'seperator', 'key', 'special', 'border',
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index 3df45700..bae03adc 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -13,10 +14,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import curses
 import _curses
 
 from ranger.ext.iter_tools import flatten
-from ranger.shared import SettingsAware
+from ranger.gui.color import get_color
+from ranger.core.shared import SettingsAware
 
 def ascii_only(string):
 	# Some python versions have problems with invalid unicode strings.
@@ -95,6 +98,12 @@ class CursesShortcuts(SettingsAware):
 		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')
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
index 9ca72b13..70455b35 100644
--- a/ranger/gui/displayable.py
+++ b/ranger/gui/displayable.py
@@ -15,7 +15,7 @@
 
 import _curses
 
-from ranger.shared import FileManagerAware, EnvironmentAware
+from ranger.core.shared import FileManagerAware, EnvironmentAware
 from ranger.gui.curses_shortcuts import CursesShortcuts
 
 class Displayable(EnvironmentAware, FileManagerAware, CursesShortcuts):
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index d617e64e..c0d22658 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -28,6 +28,7 @@ class BrowserColumn(Pager):
 	target = None
 	tagged_marker = '*'
 	last_redraw_time = -1
+	ellipsis = "~"
 
 	old_dir = None
 	old_cf = None
@@ -81,7 +82,11 @@ class BrowserColumn(Pager):
 				elif event.pressed(3):
 					try:
 						clicked_file = self.target.files[index]
-						self.fm.enter_dir(clicked_file.path)
+						if clicked_file.is_directory:
+							self.fm.enter_dir(clicked_file.path)
+						elif self.level == 0:
+							self.fm.env.cwd.move_to_obj(clicked_file)
+							self.fm.execute_file(clicked_file)
 					except:
 						pass
 
@@ -108,6 +113,8 @@ class BrowserColumn(Pager):
 	def poke(self):
 		Widget.poke(self)
 		self.target = self.env.at_level(self.level)
+		if self.target and self.target.is_file and self.has_preview():
+			self.visible = True
 
 	def draw(self):
 		"""Call either _draw_file() or _draw_directory()"""
@@ -155,8 +162,9 @@ class BrowserColumn(Pager):
 			return
 
 		try:
-			f = self.target.get_preview_source()
+			f = self.target.get_preview_source(self.wid, self.hei)
 		except:
+			raise # XXX
 			Pager.close(self)
 		else:
 			if f is None:
@@ -198,6 +206,8 @@ class BrowserColumn(Pager):
 
 		self._set_scroll_begin()
 
+		copied = [f.path for f in self.env.copy]
+
 		selected_i = self.target.pointer
 		for line in range(self.hei):
 			i = line + self.scroll_begin
@@ -207,11 +217,24 @@ class BrowserColumn(Pager):
 			except IndexError:
 				break
 
+			if self.display_infostring and drawn.infostring \
+					and self.settings.display_size_in_main_column:
+				infostring = str(drawn.infostring) + " "
+			else:
+				infostring = ""
+
 			bad_info_color = None
 			this_color = base_color + list(drawn.mimetype_tuple)
 			text = drawn.basename
 			tagged = self.fm.tags and drawn.realpath in self.fm.tags
 
+			space = self.wid - len(infostring)
+			if self.main_column:
+				space -= 2
+
+			if len(text) > space:
+				text = text[:space-1] + self.ellipsis
+
 			if i == selected_i:
 				this_color.append('selected')
 
@@ -241,7 +264,7 @@ class BrowserColumn(Pager):
 				if drawn.is_device:
 					this_color.append('device')
 
-			if self.env.copy and drawn in self.env.copy:
+			if drawn.path in copied:
 				this_color.append('cut' if self.env.cut else 'copied')
 
 			if drawn.is_link:
@@ -257,14 +280,12 @@ class BrowserColumn(Pager):
 			else:
 				self.addnstr(line, 0, text, self.wid)
 
-			if self.display_infostring and drawn.infostring \
-					and self.settings.display_size_in_main_column:
-				info = drawn.infostring
-				x = self.wid - 1 - len(info)
-				if info is BAD_INFO:
-					bad_info_color = (x, len(str(info)))
+			if infostring:
+				x = self.wid - 1 - len(infostring)
+				if infostring is BAD_INFO:
+					bad_info_color = (x, len(infostring))
 				if x > 0:
-					self.addstr(line, x, str(info) + ' ')
+					self.addstr(line, x, infostring)
 
 			self.color_at(line, 0, self.wid, this_color)
 			if bad_info_color:
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index c80e4885..54fc28f2 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -15,7 +15,7 @@
 
 """The BrowserView manages a set of BrowserColumns."""
 import curses
-from ranger.ext.signal_dispatcher import Signal
+from ranger.ext.signals import Signal
 from . import Widget
 from .browsercolumn import BrowserColumn
 from .pager import Pager
@@ -28,6 +28,7 @@ class BrowserView(Widget, DisplayableContainer):
 	draw_bookmarks = False
 	stretch_ratios = None
 	need_clear = False
+	old_collapse = False
 
 	def __init__(self, win, ratios, preview = True):
 		DisplayableContainer.__init__(self, win)
@@ -59,10 +60,11 @@ class BrowserView(Widget, DisplayableContainer):
 		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] * 0.9),
-					(self.ratios[-1] * 0.1))
+					((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
+					(self.ratios[-1] * last))
 
 		offset = 1 - len(ratios)
 		if self.preview: offset += 1
@@ -117,6 +119,7 @@ class BrowserView(Widget, DisplayableContainer):
 				pass
 
 	def _draw_bookmarks(self):
+		self.fm.bookmarks.update_if_outdated()
 		self.color_reset()
 		self.need_clear = True
 
@@ -195,8 +198,20 @@ class BrowserView(Widget, DisplayableContainer):
 
 	def _collapse(self):
 		# Should the last column be cut off? (Because there is no preview)
-		return self.settings.collapse_preview and self.preview and \
-			not self.columns[-1].has_preview() and self.stretch_ratios
+		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 and \
+			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"""
@@ -216,13 +231,24 @@ class BrowserView(Widget, DisplayableContainer):
 		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:
-				wid = int(self.wid - left + 1 - pad)
+				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))
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 57264292..9b1b0642 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -23,10 +23,9 @@ import re
 from collections import deque
 
 from . import Widget
-from ranger import log, relpath_conf
 from ranger.container.keymap import CommandArgs
 from ranger.ext.direction import Direction
-from ranger.ext.utfwidth import uwid, uchars
+from ranger.ext.utfwidth import uwid, uchars, utf_char_width_
 from ranger.container import History
 from ranger.container.history import HistoryEmptyException
 import ranger
@@ -50,7 +49,7 @@ class Console(Widget):
 		self.history = History(self.settings.max_console_history_size)
 		# load history from files
 		if not ranger.arg.clean:
-			self.historypath = relpath_conf('history')
+			self.historypath = self.fm.confpath('history')
 			try:
 				f = open(self.historypath, 'r')
 			except:
@@ -77,7 +76,10 @@ class Console(Widget):
 	def draw(self):
 		self.win.erase()
 		self.addstr(0, 0, self.prompt)
-		overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1
+		if self.fm.py3:
+			overflow = -self.wid + len(self.prompt) + len(self.line) + 1
+		else:
+			overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1
 		if overflow > 0: 
 			#XXX: cut uft-char-wise, consider width
 			self.addstr(self.line[overflow:])
@@ -86,7 +88,11 @@ class Console(Widget):
 
 	def finalize(self):
 		try:
-			xpos = uwid(self.line[0:self.pos]) + len(self.prompt)
+			if self.fm.py3:
+				xpos = sum(utf_char_width_(ord(c)) for c in self.line[0:self.pos]) \
+					+ len(self.prompt)
+			else:
+				xpos = uwid(self.line[0:self.pos]) + len(self.prompt)
 			self.fm.ui.win.move(self.y, self.x + min(self.wid-1, xpos))
 		except:
 			pass
@@ -107,6 +113,7 @@ class Console(Widget):
 		self.tab_deque = None
 		self.focused = True
 		self.visible = True
+		self.unicode_buffer = ""
 		self.line = string
 		self.history_search_pattern = self.line
 		self.pos = len(string)
@@ -169,12 +176,27 @@ class Console(Widget):
 			except ValueError:
 				return
 
-		if self.pos == len(self.line):
-			self.line += key
+		if self.fm.py3:
+			self.unicode_buffer += key
+			try:
+				decoded = self.unicode_buffer.encode("latin-1").decode("utf-8")
+			except UnicodeDecodeError:
+				pass
+			else:
+				self.unicode_buffer = ""
+				if self.pos == len(self.line):
+					self.line += decoded
+				else:
+					pos = self.pos
+					self.line = self.line[:pos] + decoded + self.line[pos:]
+				self.pos += len(decoded)
 		else:
-			self.line = self.line[:self.pos] + key + self.line[self.pos:]
+			if self.pos == len(self.line):
+				self.line += key
+			else:
+				self.line = self.line[:self.pos] + key + self.line[self.pos:]
+			self.pos += len(key)
 
-		self.pos += len(key)
 		self.on_line_change()
 
 	def history_move(self, n):
@@ -202,14 +224,21 @@ class Console(Widget):
 		direction = Direction(keywords)
 		if direction.horizontal():
 			# Ensure that the pointer is moved utf-char-wise
-			uc = uchars(self.line)
-			upos = len(uchars(self.line[:self.pos]))
-			newupos = direction.move(
-					direction=direction.right(),
-					minimum=0,
-					maximum=len(uc) + 1,
-					current=upos)
-			self.pos = len(''.join(uc[:newupos]))
+			if self.fm.py3:
+				self.pos = direction.move(
+						direction=direction.right(),
+						minimum=0,
+						maximum=len(self.line) + 1,
+						current=self.pos)
+			else:
+				uc = uchars(self.line)
+				upos = len(uchars(self.line[:self.pos]))
+				newupos = direction.move(
+						direction=direction.right(),
+						minimum=0,
+						maximum=len(uc) + 1,
+						current=upos)
+				self.pos = len(''.join(uc[:newupos]))
 
 	def delete_rest(self, direction):
 		self.tab_deque = None
@@ -248,14 +277,18 @@ class Console(Widget):
 				self.close()
 			return
 		# Delete utf-char-wise
-		uc = uchars(self.line)
-		upos = len(uchars(self.line[:self.pos])) + mod
-		left_part = ''.join(uc[:upos])
-		self.pos = len(left_part)
-		self.line = left_part + ''.join(uc[upos+1:])
+		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 = uchars(self.line)
+			upos = len(uchars(self.line[:self.pos])) + mod
+			left_part = ''.join(uc[:upos])
+			self.pos = len(left_part)
+			self.line = left_part + ''.join(uc[upos+1:])
 		self.on_line_change()
 
-
 	def execute(self, cmd=None):
 		self.allow_close = True
 		if cmd is None:
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index c0bc98b4..64444064 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,6 +19,7 @@ The pager displays text and allows you to scroll inside it.
 """
 import re
 from . import Widget
+from ranger.gui import ansi
 from ranger.ext.direction import Direction
 from ranger.container.keymap import CommandArgs
 
@@ -33,6 +35,7 @@ class Pager(Widget):
 	old_source = None
 	old_scroll_begin = 0
 	old_startx = 0
+	max_width = None
 	def __init__(self, win, embedded=False):
 		Widget.__init__(self, win)
 		self.embedded = embedded
@@ -44,6 +47,7 @@ class Pager(Widget):
 	def open(self):
 		self.scroll_begin = 0
 		self.markup = None
+		self.max_width = 0
 		self.startx = 0
 		self.need_redraw = True
 
@@ -106,6 +110,13 @@ class Pager(Widget):
 
 			if TITLE_REGEXP.match(line):
 				self.color_at(i, 0, -1, 'title', *baseclr)
+		elif self.markup == 'ansi':
+			self.win.move(i, 0)
+			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)
@@ -113,7 +124,7 @@ class Pager(Widget):
 			self.startx = direction.move(
 					direction=direction.right(),
 					override=narg,
-					maximum=self._get_max_width(),
+					maximum=self.max_width,
 					current=self.startx,
 					pagesize=self.wid,
 					offset=-self.wid + 1)
@@ -158,10 +169,12 @@ class Pager(Widget):
 
 		if isinstance(source, str):
 			self.source_is_stream = False
-			self.lines = source.split('\n')
+			self.lines = source.splitlines()
+			self.max_width = max(len(line) for line in self.lines)
 		elif hasattr(source, '__getitem__'):
 			self.source_is_stream = False
 			self.lines = source
+			self.max_width = max(len(line) for line in source)
 		elif hasattr(source, 'readline'):
 			self.source_is_stream = True
 			self.lines = []
@@ -169,6 +182,7 @@ class Pager(Widget):
 			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)
@@ -191,6 +205,8 @@ class Pager(Widget):
 			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
@@ -206,11 +222,12 @@ class Pager(Widget):
 		while True:
 			try:
 				line = self._get_line(i).expandtabs(4)
-				line = line[startx:self.wid + startx].rstrip()
-				yield line
+				if self.markup is 'ansi':
+					line = ansi.char_slice(line, startx, self.wid + startx) \
+							+ ansi.reset
+				else:
+					line = line[startx:self.wid + startx]
+				yield line.rstrip()
 			except IndexError:
 				raise StopIteration
 			i += 1
-
-	def _get_max_width(self):
-		return max(len(line) for line in self.lines)
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 5eedce05..2f3c67cf 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -221,6 +221,11 @@ class StatusBar(Widget):
 		max_pos = len(target) - self.column.hei
 		base = 'scroll'
 
+		if self.env.cwd.filter:
+			right.add(" f=", base, 'filter')
+			right.add(repr(self.env.cwd.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, seperator=''))
@@ -228,20 +233,24 @@ class StatusBar(Widget):
 				right.add(human_readable(sum(f.size \
 					for f in target.marked_items \
 					if f.is_file), seperator=''))
-			right.add(" / " + str(len(target.marked_items)))
+			right.add("/" + str(len(target.marked_items)))
 		else:
-			right.add(human_readable(target.disk_usage, seperator=''))
-			right.add(", ", "space")
+			right.add(human_readable(target.disk_usage, seperator='') +
+					" sum, ")
 			right.add(human_readable(self.env.get_free_space( \
-					target.mount_path), seperator=''))
+					target.mount_path), seperator='') + " 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 max_pos > 0:
-			if pos == 0:
+		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')
@@ -249,7 +258,7 @@ class StatusBar(Widget):
 				right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos),
 						base, 'percentage')
 		else:
-			right.add('All', base, 'all')
+			right.add('0/0  All', base, 'all')
 
 	def _print_result(self, result):
 		self.win.move(0, 0)
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index 025ad95e..d87a0803 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -96,7 +96,7 @@ class TitleBar(Widget):
 		self._get_left_part(bar)
 		self._get_right_part(bar)
 		try:
-			bar.shrink_by_cutting(self.wid)
+			bar.shrink_from_the_left(self.wid)
 		except ValueError:
 			bar.shrink_by_removing(self.wid)
 		self.result = bar.combine()
@@ -128,7 +128,7 @@ class TitleBar(Widget):
 			bar.add('/', clr, fixed=True, directory=path)
 
 		if self.env.cf is not None:
-			bar.add(self.env.cf.basename, 'file', fixed=True)
+			bar.add(self.env.cf.basename, 'file')
 
 	def _get_right_part(self, bar):
 		kb = str(self.env.keybuffer)
@@ -144,10 +144,16 @@ class TitleBar(Widget):
 				bar.addright(tabtext, 'tab', clr, fixed=True)
 
 	def _get_tab_text(self, tabname):
+		result = ' ' + str(tabname)
 		if self.settings.dirname_in_tabs:
-			return ' ' + str(tabname) + ":" + (basename(self.fm.tabs[tabname]) or '/')
-		else:
-			return ' ' + str(tabname)
+			dirname = basename(self.fm.tabs[tabname])
+			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)
diff --git a/ranger/help/console.py b/ranger/help/console.py
index f03491db..716740b9 100644
--- a/ranger/help/console.py
+++ b/ranger/help/console.py
@@ -12,6 +12,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 3. The Console
 
diff --git a/ranger/help/invocation.py b/ranger/help/invocation.py
index 26cffd4a..688308f1 100644
--- a/ranger/help/invocation.py
+++ b/ranger/help/invocation.py
@@ -12,6 +12,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 """
 5. Ranger invocation
 
@@ -43,6 +44,11 @@ command line.
       This is useful when your configuration is broken, when you want
       to avoid clutter, etc.
 
+--copy-config
+      Create copies of the default configuration files in your local
+      configuration directory.  Existing ones will not be overwritten.
+      Possible values: all, apps, commands, keys, options, scope.
+
 --fail-unless-cd
       Return the exit code 1 if ranger is used to run a file, for example
       with `ranger --fail-unless-cd filename`.  This can be useful for scripts.
diff --git a/ranger/help/movement.py b/ranger/help/movement.py
index 564b226b..fce01183 100644
--- a/ranger/help/movement.py
+++ b/ranger/help/movement.py
@@ -200,6 +200,9 @@ Clicking into the preview window will usually run the file. |2?|
 	cw	Open the console with ":rename "
 	A	Open the console with ":rename <current filename>"
 	I	Same as A, put the cursor at the beginning of the filename
+	yp	Copy the path of the file (with xsel)
+	yn	Copy the base name of the file (with xsel)
+	yd	Copy the directory name of the file (with xsel)
 
 
 ==============================================================================
diff --git a/ranger/shared/__init__.py b/ranger/shared/__init__.py
deleted file mode 100644
index 048b9e7a..00000000
--- a/ranger/shared/__init__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""Shared objects contain singleton variables which can be
-inherited, essentially acting like global variables."""
-class Awareness(object):
-	pass
-
-class EnvironmentAware(Awareness):
-	env = None
-	@staticmethod
-	def _assign(instance):
-		EnvironmentAware.env = instance
-
-
-class FileManagerAware(Awareness):
-	fm = None
-	@staticmethod
-	def _assign(instance):
-		FileManagerAware.fm = instance
-
-from .mimetype import MimeTypeAware
-from .settings import SettingsAware
diff --git a/ranger/shared/mimetype.py b/ranger/shared/mimetype.py
deleted file mode 100644
index da6fcd10..00000000
--- a/ranger/shared/mimetype.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from ranger import relpath
-import mimetypes
-import os.path
-
-class MimeTypeAware(object):
-	mimetypes = {}
-	def __init__(self):
-		MimeTypeAware.__init__ = lambda _: None  # refuse multiple inits
-		mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
-		MimeTypeAware.mimetypes = mimetypes.MimeTypes()
-		MimeTypeAware.mimetypes.read(relpath('data/mime.types'))
diff --git a/setup.py b/setup.py
index 587b52c0..e63e28d2 100755
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,7 @@ if __name__ == '__main__':
 	distutils.core.setup(
 		name='ranger',
 		description='Vim-like file manager',
+		long_description=ranger.__doc__,
 		version=ranger.__version__,
 		author=ranger.__author__,
 		author_email=ranger.__email__,
@@ -39,5 +40,4 @@ if __name__ == '__main__':
 		          'ranger.fsobject',
 		          'ranger.gui',
 		          'ranger.gui.widgets',
-		          'ranger.help',
-		          'ranger.shared'))
+		          'ranger.help'))
diff --git a/test/tc_ansi.py b/test/tc_ansi.py
new file mode 100644
index 00000000..0a6ad8b1
--- /dev/null
+++ b/test/tc_ansi.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2010  David Barnett <davidbarnett2@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if __name__ == '__main__': from __init__ import init; init()
+
+import unittest
+from ranger.gui import ansi
+
+class TestDisplayable(unittest.TestCase):
+	def test_char_len(self):
+		ansi_string = "X"
+		self.assertEqual(ansi.char_len(ansi_string), 1)
+
+	def test_char_len2(self):
+		ansi_string = "XY"
+		self.assertEqual(ansi.char_len(ansi_string), 2)
+
+	def test_char_len3(self):
+		ansi_string = "XY"
+		self.assertEqual(ansi.char_len(ansi_string), 2)
+
+	def test_char_slice(self):
+		ansi_string = "XY"
+		expected = "X"
+		self.assertEqual(ansi.char_slice(ansi_string, 0, 1), expected)
+
+if __name__ == '__main__':
+	unittest.main()
diff --git a/test/tc_directory.py b/test/tc_directory.py
index 754253b3..a43ac89d 100644
--- a/test/tc_directory.py
+++ b/test/tc_directory.py
@@ -24,7 +24,7 @@ from os.path import realpath, join, dirname
 from ranger import fsobject
 from ranger.fsobject.file import File
 from ranger.fsobject.directory import Directory
-from ranger.shared.settings import SettingsAware
+from ranger.core.shared import SettingsAware
 
 SettingsAware._setup()
 
@@ -49,7 +49,7 @@ class Test1(unittest.TestCase):
 		import os
 		# Check whether the directory has the correct list of filenames.
 		dir = Directory(TESTDIR)
-		dir.load_content()
+		dir.load_content(schedule=False)
 
 		self.assertTrue(dir.exists)
 		self.assertEqual(type(dir.filenames), list)
@@ -78,8 +78,8 @@ class Test1(unittest.TestCase):
 
 	def test_nonexistant_dir(self):
 		dir = Directory(NONEXISTANT_DIR)
-		dir.load_content()
-		
+		dir.load_content(schedule=False)
+
 		self.assertTrue(dir.content_loaded)
 		self.assertFalse(dir.exists)
 		self.assertFalse(dir.accessible)
diff --git a/test/tc_loader.py b/test/tc_loader.py
index 5a2e5a68..a679a629 100644
--- a/test/tc_loader.py
+++ b/test/tc_loader.py
@@ -24,7 +24,7 @@ import os
 from os.path import realpath, join, dirname
 
 from testlib import Fake
-from ranger.shared import FileManagerAware, SettingsAware
+from ranger.core.shared import FileManagerAware, SettingsAware
 from ranger.core.loader import Loader
 from ranger.fsobject import Directory, File
 from ranger.ext.openstruct import OpenStruct
diff --git a/test/tc_signal.py b/test/tc_signal.py
index 3b1bac40..6547bbc3 100644
--- a/test/tc_signal.py
+++ b/test/tc_signal.py
@@ -21,7 +21,7 @@ if sys.path[1] != rangerpath:
 
 import unittest
 import gc
-from ranger.ext.signal_dispatcher import *
+from ranger.ext.signals import *
 
 class TestSignal(unittest.TestCase):
 	def setUp(self):