summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rwxr-xr-xranger.py43
-rw-r--r--ranger/__init__.py66
-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/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.py28
-rw-r--r--ranger/core/helper.py (renamed from ranger/__main__.py)183
-rw-r--r--ranger/core/loader.py18
-rw-r--r--ranger/core/main.py98
-rw-r--r--ranger/core/shared.py73
-rw-r--r--ranger/defaults/apps.py14
-rw-r--r--ranger/defaults/commands.py44
-rw-r--r--ranger/defaults/keys.py6
-rw-r--r--ranger/fsobject/directory.py13
-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.py7
-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
40 files changed, 480 insertions, 390 deletions
diff --git a/CHANGELOG b/CHANGELOG
index f1417fee..8078305d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,9 @@ This log only documents changes between stable versions.
 From 1.2 on, odd minor version numbers (1.3, 1.5, 1.7,..) are assigned to the
 fresh git snapshots while stable versions will have even minor numbers.
 
+1.2 -> 1.2.1:
+* Fixed yy/pp bug when yanking multiple directories
+
 1.1.2 -> 1.2:
 * !!! Changed the default configuration directory to ~/.config/ranger !!!
 * Removed "Console Modes", each old mode is now a simple command
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 87376500..324cea55 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -13,11 +13,19 @@
 # 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.
 
-from os import path, environ
-from ranger.ext.openstruct import OpenStruct
-from sys import argv
+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
+from ranger.core.main import main
 
 # Information
 __license__ = 'GPL3'
@@ -26,52 +34,4 @@ __author__ = __maintainer__ = 'Roman Zimbelmann'
 __email__ = 'romanz@lavabit.com'
 
 # Constants
-USAGE = '%prog [options] [path/filename]'
-RANGERDIR = path.dirname(__file__)
-LOGFILE = '/tmp/errorlog'
-if 'XDG_CONFIG_HOME' in environ and environ['XDG_CONFIG_HOME']:
-	DEFAULT_CONFDIR = environ['XDG_CONFIG_HOME'] + '/ranger'
-else:
-	DEFAULT_CONFDIR = '~/.config/ranger'
-DEBUG = ('-d' in argv or '--debug' in argv) and ('--' not in argv or
-	(('-d' in argv and argv.index('-d') < argv.index('--')) or
-	('--debug' in argv and argv.index('--debug') < argv.index('--'))))
-
-# Get some valid arguments before actually parsing them in main()
-arg = OpenStruct(debug=DEBUG, clean=False, confdir=DEFAULT_CONFDIR,
-		mode=0, flags='', targets=[])
-
-# Debugging features.  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 not DEBUG: 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 not DEBUG: return
-	import traceback
-	traceback.print_stack(file=open(LOGFILE, 'a'))
-
-# Handy functions
-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 path.join(arg.confdir, *paths)
-
-def relpath(*paths):
-	"""returns the path relative to rangers library directory"""
-	return path.join(RANGERDIR, *paths)
-
-# Clean up
-del path, environ, OpenStruct, argv
+RANGERDIR = os.path.dirname(__file__)
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/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 7604af12..008b846d 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,
@@ -53,7 +51,7 @@ ALLOWED_SETTINGS = {
 }
 
 
-class SettingObject(SignalDispatcher):
+class SettingObject(SignalDispatcher, FileManagerAware):
 	def __init__(self):
 		SignalDispatcher.__init__(self)
 		self.__dict__['_settings'] = dict()
@@ -71,7 +69,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 +126,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 1493d84d..2e775848 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.core.loader import CommandLoader
 
@@ -215,6 +216,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:
@@ -468,6 +471,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:
@@ -478,10 +482,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 d2aa00ec..cd1c73b6 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 destroy(self):
 		debug = ranger.arg.debug
 		if self.ui:
@@ -118,6 +121,17 @@ class FM(Actions, SignalDispatcher):
 			self.input_blocked = False
 		return self.input_blocked
 
+	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:
@@ -135,14 +149,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():
@@ -167,5 +179,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 a9a18537..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,26 +73,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:
@@ -114,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)
@@ -155,96 +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'
+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.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.
+	"""
+	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 e33c2e9e..8272d0e0 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -16,20 +16,12 @@
 from collections import deque
 from time import time, sleep
 from subprocess import Popen, PIPE
+from time import time
+from ranger.shared import FileManagerAware
 import math
 import os
 import select
 
-from ranger.shared import FileManagerAware
-
-def status_generator():
-	"""Generate a rotating line which can be used as a throbber"""
-	while True:
-		yield '/'
-		yield '-'
-		yield '\\'
-		yield '|'
-
 
 class Loadable(object):
 	paused = False
@@ -106,11 +98,13 @@ class CommandLoader(Loadable, FileManagerAware):
 
 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.throbber_status = 0
 		self.status_generator = status_generator()
 		self.rotate()
 		self.old_item = None
@@ -118,7 +112,9 @@ class Loader(FileManagerAware):
 	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..42118516
--- /dev/null
+++ b/ranger/core/main.py
@@ -0,0 +1,98 @@
+# 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.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..b91445a3
--- /dev/null
+++ b/ranger/core/shared.py
@@ -0,0 +1,73 @@
+# 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)
+
+		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 b61d5a5a..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)
@@ -435,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/fsobject/directory.py b/ranger/fsobject/directory.py
index 2ac56120..7578ad5f 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -24,7 +24,7 @@ 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
 
@@ -40,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:
@@ -77,6 +83,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, 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,
@@ -296,6 +303,10 @@ class Directory(FileSystemObject, Accumulator, Loadable, 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/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 3df45700..4ed348fd 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -16,7 +16,7 @@
 import _curses
 
 from ranger.ext.iter_tools import flatten
-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 d617e64e..dacc3920 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 2a92f313..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=''))
@@ -241,7 +246,7 @@ class StatusBar(Widget):
 					+ str(len(target.files)) + '  ', base)
 			if max_pos == 0:
 				right.add('All', base, 'all')
-			if pos == 0:
+			elif pos == 0:
 				right.add('Top', base, 'top')
 			elif pos >= max_pos:
 				right.add('Bot', base, 'bot')
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