summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--doc/ranger.12
-rwxr-xr-xranger.py43
-rw-r--r--ranger/__init__.py74
-rw-r--r--ranger/api/apps.py2
-rw-r--r--ranger/api/commands.py5
-rw-r--r--ranger/api/keys.py10
-rw-r--r--ranger/api/options.py1
-rw-r--r--ranger/colorschemes/jungle.py2
-rw-r--r--ranger/colorschemes/snow.py5
-rw-r--r--ranger/container/settingobject.py (renamed from ranger/shared/settings.py)40
-rw-r--r--ranger/core/actions.py8
-rw-r--r--ranger/core/environment.py4
-rw-r--r--ranger/core/fm.py34
-rw-r--r--ranger/core/helper.py (renamed from ranger/__main__.py)191
-rw-r--r--ranger/core/loader.py17
-rw-r--r--ranger/core/main.py99
-rw-r--r--ranger/core/shared.py79
-rw-r--r--ranger/defaults/apps.py14
-rw-r--r--ranger/defaults/commands.py46
-rw-r--r--ranger/defaults/keys.py6
-rw-r--r--ranger/defaults/options.py2
-rw-r--r--ranger/fsobject/directory.py13
-rw-r--r--ranger/fsobject/file.py1
-rw-r--r--ranger/fsobject/fsobject.py21
-rw-r--r--ranger/gui/bar.py24
-rw-r--r--ranger/gui/color.py2
-rw-r--r--ranger/gui/colorscheme.py12
-rw-r--r--ranger/gui/context.py2
-rw-r--r--ranger/gui/curses_shortcuts.py2
-rw-r--r--ranger/gui/displayable.py2
-rw-r--r--ranger/gui/ui.py4
-rw-r--r--ranger/gui/widgets/browsercolumn.py30
-rw-r--r--ranger/gui/widgets/browserview.py1
-rw-r--r--ranger/gui/widgets/console.py3
-rw-r--r--ranger/gui/widgets/statusbar.py23
-rw-r--r--ranger/gui/widgets/titlebar.py18
-rw-r--r--ranger/help/console.py1
-rw-r--r--ranger/help/invocation.py1
-rw-r--r--ranger/shared/__init__.py35
-rw-r--r--ranger/shared/mimetype.py26
-rwxr-xr-xsetup.py4
-rw-r--r--test/tc_directory.py8
-rw-r--r--test/tc_loader.py2
44 files changed, 503 insertions, 418 deletions
diff --git a/README b/README
index 2b44015e..44c03513 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ranger v.1.2.1
+Ranger v.1.3.0
 ==============
 
 Ranger is a free console file manager that gives you greater flexibility
diff --git a/doc/ranger.1 b/doc/ranger.1
index 03cc3d56..968e601b 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.TH RANGER 1 ranger-1.2.1
+.TH RANGER 1 ranger-1.3.0
 .SH NAME
 ranger - visual file manager
 .\"-----------------------------------------
diff --git a/ranger.py b/ranger.py
index 5652ba69..fbceab23 100755
--- a/ranger.py
+++ b/ranger.py
@@ -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
+	$@ --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.
+# 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 7a09dbe3..324cea55 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -13,69 +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.1'
-__credits__ = 'Roman Zimbelmann'
-__author__ = 'Roman Zimbelmann'
-__maintainer__ = 'Roman Zimbelmann'
+__version__ = '1.3.0'
+__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_script(*paths):
-	"""
-	Returns the path relative to where scripts are stored.
-
-	It's relpath('data', *paths) with the --clean flag and
-	relpath_conf(*paths) without --clean.
-	"""
-	if arg.clean:
-		return relpath('data', *paths)
-	else:
-		return relpath_conf(*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 f4e2ca76..a491c927 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -16,9 +16,12 @@
 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
+def alias(*_):
+	pass
 
 class CommandContainer(object):
 	def __init__(self):
diff --git a/ranger/api/keys.py b/ranger/api/keys.py
index 5812de39..7ba05c73 100644
--- a/ranger/api/keys.py
+++ b/ranger/api/keys.py
@@ -23,6 +23,16 @@ from ranger.api import *
 from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS
 from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections
 
+# A dummy that allows the generation of docstrings in ranger.defaults.keys
+class DummyKeyManager(object):
+	def get_context(self, _):
+		class Dummy(object):
+			def __getattr__(self, *_, **__):
+				return Dummy()
+			__call__ = __getattr__
+		return Dummy()
+keymanager = DummyKeyManager()
+
 class Wrapper(object):
 	def __init__(self, firstattr):
 		self.__firstattr__ = firstattr
diff --git a/ranger/api/options.py b/ranger/api/options.py
index 93da4df1..ee947b39 100644
--- a/ranger/api/options.py
+++ b/ranger/api/options.py
@@ -17,4 +17,3 @@ import re
 from re import compile as regexp
 from ranger.api import *
 from ranger.gui import color
-from ranger import relpath, relpath_conf, relpath_script
diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py
index af10a404..f5e03c06 100644
--- a/ranger/colorschemes/jungle.py
+++ b/ranger/colorschemes/jungle.py
@@ -20,7 +20,7 @@ class Scheme(Default):
 	def use(self, context):
 		fg, bg, attr = Default.use(self, context)
 
-		if context.directory and not context.marked:
+		if context.directory and not context.marked and not context.link:
 			fg = green
 
 		if context.in_titlebar and context.hostname:
diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py
index e89359c4..a449db87 100644
--- a/ranger/colorschemes/snow.py
+++ b/ranger/colorschemes/snow.py
@@ -32,7 +32,10 @@ class Snow(ColorScheme):
 			if context.directory:
 				attr |= bold
 
-		if context.highlight:
+		elif context.highlight:
+			attr |= reverse
+
+		elif context.in_titlebar and context.tab and context.good:
 			attr |= reverse
 
 		return fg, bg, attr
diff --git a/ranger/shared/settings.py b/ranger/container/settingobject.py
index 41334ada..c8bd8b49 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.core.shared import FileManagerAware
 
 ALLOWED_SETTINGS = {
 	'autosave_bookmarks': bool,
@@ -54,7 +52,7 @@ ALLOWED_SETTINGS = {
 }
 
 
-class SettingObject(SignalDispatcher):
+class SettingObject(SignalDispatcher, FileManagerAware):
 	def __init__(self):
 		SignalDispatcher.__init__(self)
 		self.__dict__['_settings'] = dict()
@@ -72,7 +70,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)
 
@@ -129,35 +127,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 b379d341..4b6a4ff4 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -26,7 +26,8 @@ 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
@@ -216,6 +217,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def move_parent(self, n):
 		parent = self.env.at_level(-1)
+		if parent.pointer + n < 0:
+			n = 0 - parent.pointer
 		try:
 			self.env.enter_dir(parent.files[parent.pointer+n])
 		except IndexError:
@@ -469,6 +472,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:
@@ -479,10 +483,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):
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index 61db8694..655054d7 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -22,7 +22,7 @@ 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.core.shared import SettingsAware
 
 ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview',
 		'console')
@@ -124,7 +124,7 @@ 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 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 dfad3425..e468e58c 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -19,6 +19,7 @@ The File Manager, putting the pieces together
 
 from time import time
 from collections import deque
+import mimetypes
 import os
 import sys
 
@@ -28,14 +29,12 @@ 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 import __version__
 from ranger.core.loader import Loader
 
-CTRL_C = 3
 TICKS_BEFORE_COLLECTING_GARBAGE = 100
 TIME_BEFORE_FILE_BECOMES_GARBAGE = 1200
 
@@ -70,7 +69,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,
@@ -81,7 +80,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()
@@ -94,6 +93,10 @@ 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 block_input(self, sec=0):
 		self.input_blocked = sec != 0
 		self.input_blocked_until = time() + sec
@@ -103,6 +106,23 @@ class FM(Actions, SignalDispatcher):
 			self.input_blocked = False
 		return self.input_blocked
 
+	def copy_config_files(self):
+		if not (ranger.arg.clean or os.path.exists(self.confpath('scope.sh'))):
+			import shutil
+			shutil.copy(self.relpath('data/scope.sh'),
+					self.confpath('scope.sh'))
+
+	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:
@@ -120,14 +140,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():
@@ -152,5 +170,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 7559b43d..0ef0fc27 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,20 +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)
-# convenient exception handling in ranger.py (ImportError)
+"""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:
@@ -40,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")
@@ -52,7 +55,7 @@ def parse_arguments():
 			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")
@@ -62,7 +65,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']
@@ -70,33 +73,8 @@ def parse_arguments():
 	return arg
 
 
-def copy_config_files():
-	import shutil
-	from ranger import relpath, relpath_conf
-	if not os.path.exists(relpath_conf('scope.sh')):
-		shutil.copy(relpath('data', 'scope.sh'), relpath_conf('scope.sh'))
-
-
-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:
@@ -121,24 +99,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)
@@ -162,99 +135,41 @@ 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'
-
-	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)
-
-	if not ranger.arg.clean:
-		copy_config_files()
-
-	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:
+def allow_access_to_confdir(confdir, allow):
+	if allow:
 		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
+			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]
 
 
-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.
+	"""
+	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():
+	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..7b074d60 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -15,17 +15,9 @@
 
 from collections import deque
 from time import time
-from ranger.shared import FileManagerAware
+from ranger.core.shared import FileManagerAware
 import math
 
-def status_generator():
-	"""Generate a rotating line which can be used as a throbber"""
-	while True:
-		yield '/'
-		yield '-'
-		yield '\\'
-		yield '|'
-
 class LoadableObject(object):
 	def __init__(self, gen, descr):
 		self.load_generator = gen
@@ -37,19 +29,22 @@ class LoadableObject(object):
 
 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):
 		"""
diff --git a/ranger/core/main.py b/ranger/core/main.py
new file mode 100644
index 00000000..ed555a8d
--- /dev/null
+++ b/ranger/core/main.py
@@ -0,0 +1,99 @@
+# 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.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.core.shared import (EnvironmentAware, FileManagerAware,
+			SettingsAware)
+
+	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()
+	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)
+			sys.exit(1)
+		elif os.path.isfile(target):
+			def print_function(string):
+				print(string)
+			runner = Runner(logfunc=print_function)
+			load_apps(runner, 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.env = Environment(target)
+		fm = FM()
+		fm.copy_config_files()
+		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
+		FileManagerAware.fm = fm
+		fm.ui = UI()
+		if not arg.debug:
+			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:
+		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
diff --git a/ranger/core/shared.py b/ranger/core/shared.py
new file mode 100644
index 00000000..175395ec
--- /dev/null
+++ b/ranger/core/shared.py
@@ -0,0 +1,79 @@
+# 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
+
+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 postprocess_paths(signal):
+			import os
+			signal.value = os.path.expanduser(signal.value)
+		settings.signal_bind('setopt.preview_script',
+				postprocess_paths, 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/defaults/apps.py b/ranger/defaults/apps.py
index 47eff0c9..85abb2dc 100644
--- a/ranger/defaults/apps.py
+++ b/ranger/defaults/apps.py
@@ -87,9 +87,13 @@ class CustomApplications(Applications):
 		if f.image:
 			return self.either(c, 'feh', 'eog', 'mirage')
 
-		if f.document or f.filetype.startswith('text'):
+		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 d3c05023..157b148c 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -73,9 +73,8 @@ class cd(Command):
 
 	def execute(self):
 		line = parse(self.line)
-		try:
-			destination = line.rest(1)
-		except IndexError:
+		destination = line.rest(1)
+		if not destination:
 			destination = '~'
 
 		if destination == '-':
@@ -289,12 +288,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)
@@ -308,8 +308,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:
@@ -437,6 +435,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 9f0c78cb..e62ae9ae 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -202,12 +202,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',
 }
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index 5ce617bc..3c20c6fb 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -44,7 +44,7 @@ show_hidden = False
 
 # Ranger ships with scope.sh, a script that calls external programs (see
 # README for dependencies) to preview images, archives, etc.
-preview_script = relpath_script('scope.sh')
+preview_script = '~/.config/ranger/scope.sh'
 
 # Show dotfiles in the bookmark preview box?
 show_hidden_bookmarks = True
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index 9bdb4caa..e52b84d7 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -23,7 +23,7 @@ from time import time
 
 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 +39,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:
@@ -76,6 +82,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,
@@ -295,6 +302,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:
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index fad22a1f..57d82b31 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -17,7 +17,6 @@ import re
 from ranger.fsobject import FileSystemObject
 from subprocess import Popen, PIPE
 from ranger.core.runner import devnull
-from ranger import relpath
 
 N_FIRST_BYTES = 20
 control_characters = set(chr(n) for n in
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index 4ca5a6c8..fd886275 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -17,17 +17,20 @@ 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')
 
+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 [int(c) if c.isdigit() else c or 0 \
+			for c in _extract_number_re.split(self.basename)]
+
+	@lazy_property
+	def basename_natural_lower(self):
+		return [int(c) if c.isdigit() else c or 0 \
+			for c in _extract_number_re.split(self.basename_lower)]
+
 	def __str__(self):
 		"""returns a string containing the absolute path"""
 		return str(self.path)
@@ -110,7 +121,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 = ''
 
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index 03ed2f78..41cc8133 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
+		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 69f67eba..58f0b38a 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -62,7 +62,7 @@ invisible  = curses.A_INVIS
 
 default_colors = (default, default, normal)
 
-def remove_attr(integer, attr):
+def remove_attr(integer, attribute):
 	"""Remove an attribute from an integer"""
 	if integer & attribute:
 		return integer ^ attribute
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index 5b317acb..ae6aac0b 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -47,8 +47,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
@@ -141,15 +141,15 @@ 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:
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 42f9dada..bae03adc 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -19,7 +19,7 @@ import _curses
 
 from ranger.ext.iter_tools import flatten
 from ranger.gui.color import get_color
-from ranger.shared import SettingsAware
+from ranger.core.shared import SettingsAware
 
 def ascii_only(string):
 	# Some python versions have problems with invalid unicode strings.
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/ui.py b/ranger/gui/ui.py
index b0c1a352..2f27f11e 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -123,8 +123,8 @@ class UI(DisplayableContainer):
 			event = MouseEvent(curses.getmouse())
 		except _curses.error:
 			return
-
-		DisplayableContainer.click(self, event)
+		if not self.console.visible:
+			DisplayableContainer.click(self, event)
 
 	def handle_key(self, key):
 		"""Handles key input"""
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index 5585bccc..63323f65 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
@@ -198,6 +199,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 +210,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 +257,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 +273,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..142e7985 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -117,6 +117,7 @@ class BrowserView(Widget, DisplayableContainer):
 				pass
 
 	def _draw_bookmarks(self):
+		self.fm.bookmarks.update_if_outdated()
 		self.color_reset()
 		self.need_clear = True
 
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 57264292..9f7d2405 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -23,7 +23,6 @@ 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
@@ -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:
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 3019930b..fb3e6b21 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -217,6 +217,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=''))
@@ -224,20 +229,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')
@@ -245,7 +254,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 35e2e3d9..d87a0803 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-The titlebar is the widget at the top, giving you broad orientation.
+The titlebar is the widget at the top, giving you broad overview.
 
 It displays the current path among other things.
 """
@@ -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..27ab5a67 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
 
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_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