summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG27
-rw-r--r--HACKING8
-rw-r--r--Makefile3
-rw-r--r--README41
-rw-r--r--doc/colorschemes.txt4
-rwxr-xr-x[-rw-r--r--]doc/print_keys.py0
-rw-r--r--doc/ranger.111
-rwxr-xr-xranger.py6
-rw-r--r--ranger/__init__.py7
-rw-r--r--ranger/__main__.py27
-rw-r--r--ranger/api/commands.py4
-rw-r--r--ranger/api/keys.py1
-rw-r--r--ranger/container/history.py114
-rw-r--r--ranger/core/actions.py134
-rw-r--r--ranger/core/environment.py4
-rw-r--r--ranger/defaults/apps.py6
-rw-r--r--ranger/defaults/commands.py190
-rw-r--r--ranger/defaults/keys.py52
-rw-r--r--ranger/defaults/options.py4
-rw-r--r--ranger/ext/human_readable.py53
-rw-r--r--ranger/ext/relative_symlink.py39
-rw-r--r--ranger/fsobject/directory.py6
-rw-r--r--ranger/fsobject/file.py4
-rw-r--r--ranger/gui/colorscheme.py6
-rw-r--r--ranger/gui/defaultui.py4
-rw-r--r--ranger/gui/widgets/console.py449
-rw-r--r--ranger/gui/widgets/console_mode.py55
-rw-r--r--ranger/help/console.py188
-rw-r--r--ranger/help/fileop.py9
-rw-r--r--ranger/help/movement.py3
-rw-r--r--ranger/help/starting.py35
-rw-r--r--ranger/shared/mimetype.py3
-rw-r--r--ranger/shared/settings.py46
-rwxr-xr-xsetup.py47
-rw-r--r--test/__init__.py0
-rwxr-xr-xtest/all_benchmarks.py8
-rwxr-xr-xtest/all_tests.py6
-rw-r--r--test/bm_human_readable.py51
-rw-r--r--test/bm_loader.py6
l---------test/ranger1
-rw-r--r--test/tc_bookmarks.py6
-rw-r--r--test/tc_colorscheme.py6
-rw-r--r--test/tc_direction.py6
-rw-r--r--test/tc_directory.py5
-rw-r--r--test/tc_displayable.py6
-rw-r--r--test/tc_ext.py6
-rw-r--r--test/tc_history.py33
-rw-r--r--test/tc_human_readable.py51
-rw-r--r--test/tc_keyapi.py6
-rw-r--r--test/tc_loader.py6
-rw-r--r--test/tc_newkeys.py10
-rw-r--r--test/tc_relative_symlink.py47
-rw-r--r--test/tc_signal.py6
-rw-r--r--test/tc_ui.py6
-rw-r--r--test/tc_utfwidth.py8
55 files changed, 1050 insertions, 820 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 578717d4..8078305d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,30 @@
+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
+* Disabled file previews by default if ranger is used by root
+* Allow to jump to specific help sections by typing two numbers, e.g. 13?
+* Added keys: da, dr, ya, yr for adding and removing files from copy buffer
+* Added keys: gl and gL to resolve links, see 11?
+* Added key: pL to create a relative symlink
+* Added %<LETTER> and %<N><LETTER> macros for the console, see 33?
+* Fixed ansi codes for colors in the pager
+* Use the file ~/.mime.types for mime type detection
+* Several clean-ups and fixes
+
+1.1.1 -> 1.1.2:
+* Fix crash when using scrollwheel to scroll down in some cases
+* The command "ranger dir1 dir2 ..." opens multiple directories in tabs
+* Removed pydoc html documentation by default, re-create it with "make doc"
+* Minor fixes
+
 1.1.0 -> 1.1.1:
 * New install script, "setup.py"
 * New flag for running programs: "w" (waits for enter press)
diff --git a/HACKING b/HACKING
index 9c114e89..dd384758 100644
--- a/HACKING
+++ b/HACKING
@@ -56,13 +56,13 @@ assuming <self> is a "SettingsAware" object.
 
 * Changing commands, adding aliases:
 ranger/defaults/commands.py
-or ~/.ranger/commands.py
+or ~/.config/ranger/commands.py
 
 * Adding colorschemes:
 Copy ranger/colorschemes/default.py to ranger/colorschemes/myscheme.py
 and modify it according to your needs.  Alternatively, mimic the jungle
 colorscheme.  It subclasses the default scheme and just modifies a few things.
-In ranger/defaults/options.py (or ~/.ranger/options.py), change
+In ranger/defaults/options.py (or ~/.config/ranger/options.py), change
     colorscheme = 'default'
 to: colorscheme = 'myscheme'
 
@@ -91,5 +91,5 @@ Version Numbering
 X.Y.Z, where:
 
 * X: Major version, milestone
-* Y: Minor version, odd number => stable version
-* Z: Revision
+* Y: Minor version, even number => stable version
+* Z: Revision, may be omitted if zero
diff --git a/Makefile b/Makefile
index 57db7b11..fd525721 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 NAME = ranger
-VERSION = $(shell cat README | grep -m 1 -o '[0-9][0-9.]\+')
+VERSION = $(shell grep -m 1 -o '[0-9][0-9.]\+' README)
 SNAPSHOT_NAME ?= $(NAME)-$(VERSION)-$(shell git rev-parse HEAD | cut -b 1-8).tar.gz
 # Find suitable python version (need python >= 2.6 or 3.1):
 PYTHON ?= $(shell python -c 'import sys; sys.exit(sys.version < "2.6")' && \
@@ -61,7 +61,6 @@ doc: cleandoc
 		$(PYTHON) -c 'import pydoc, sys; \
 		sys.path[0] = "$(CWD)"; \
 		pydoc.writedocs("$(CWD)")'
-	rm $(DOCDIR)/test*
 	find . -name \*.html -exec sed -i 's|'$(CWD)'|../..|g' -- {} \;
 
 cleandoc:
diff --git a/README b/README
index d92543f7..2b44015e 100644
--- a/README
+++ b/README
@@ -1,13 +1,14 @@
-Ranger v.1.1.1
+Ranger v.1.2.1
 ==============
 
 Ranger is a free console file manager that gives you greater flexibility
 and a good overview of your files without having to leave your *nix console.
 It visualizes the directory tree in two dimensions: the directory hierarchy
 on one, lists of files on the other, with a preview to the right so you know
-where you'll be going.  The default keys are similar to those of Vim, Emacs
-and Midnight Commander, though Ranger is easily controllable with just the
-arrow keys or the mouse.
+where you'll be going.
+
+The default keys are similar to those of Vim, Emacs and Midnight Commander,
+though Ranger is easily controllable with just the arrow keys or the mouse.
 
 The program is written in Python (2.6 or 3.1) and uses curses for the
 text-based user interface.
@@ -19,7 +20,6 @@ About
 * Author:          Roman Zimbelmann  <romanz@lavabit.com>
 * Website:         http://savannah.nongnu.org/projects/ranger
 * License:         GNU General Public License Version 3
-* Version:         1.1.1
 
 * Download URL of the newest stable version:
 http://git.savannah.gnu.org/cgit/ranger.git/snapshot/ranger-stable.tar.gz
@@ -28,6 +28,15 @@ http://git.savannah.gnu.org/cgit/ranger.git/snapshot/ranger-stable.tar.gz
 git clone http://git.sv.gnu.org/r/ranger.git
 
 
+Design Goals
+------------
+
+* An easily maintainable file manager in a high level language
+* A quick way to switch directories and browse the file system
+* Keep it small but useful, do one thing and do it well
+* Console based, with smooth integration into the unix shell
+
+
 Features
 --------
 
@@ -72,10 +81,30 @@ parent directories and to the right there's a preview of the object you're
 pointing at.  Now use the Arrow Keys to navigate, Enter to open a file
 or type Q to quit.
 
-To customize ranger, copy the files from ranger/defaults/ to ~/.ranger/
+To customize ranger, copy the files from ranger/defaults/ to ~/.config/ranger/
 and modify them according to your wishes.
 
 
+Usage Tips
+----------
+
+The author of ranger uses this function (in ~/.bashrc) to start ranger:
+function ranger-cd {
+  before="$(pwd)"
+  python2.6 /the/path/to/ranger/ranger.py --fail-unless-cd "$@" || return 0
+  after="$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)"
+  if [[ "$before" != "$after" ]]; then
+    cd "$after"
+  fi
+}
+bind '"\C-o":"ranger-cd\C-m"'
+
+This changes the directory after you close ranger and adds the shortcut
+<CTRL-O> for starting ranger.
+
+To change back to the previous directory, you can type: cd -
+
+
 Troubleshooting, Getting Help
 -----------------------------
 
diff --git a/doc/colorschemes.txt b/doc/colorschemes.txt
index 905c7a3e..e7bc2c0a 100644
--- a/doc/colorschemes.txt
+++ b/doc/colorschemes.txt
@@ -65,7 +65,7 @@ Specify a Colorscheme
 ---------------------
 
 Colorschemes are searched for in these directories:
-~/.ranger/colorschemes/
+~/.config/ranger/colorschemes/
 /ranger/colorschemes/
 
 To specify which colorscheme to use, define the variable "colorscheme"
@@ -73,7 +73,7 @@ in your options.py:
 colorscheme = colorschemes.default
 
 This means, use the (one) colorscheme contained in
-either ~/.ranger/colorschemes/default.py or /ranger/colorschemes/default.py.
+either ~/.config/ranger/colorschemes/default.py or /ranger/colorschemes/default.py.
 
 You can define more than one colorscheme in a colorscheme file.  The
 one named "Scheme" will be chosen in that case.  If there is no colorscheme
diff --git a/doc/print_keys.py b/doc/print_keys.py
index f87a2a40..f87a2a40 100644..100755
--- a/doc/print_keys.py
+++ b/doc/print_keys.py
diff --git a/doc/ranger.1 b/doc/ranger.1
index dd343f28..03cc3d56 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.TH RANGER 1 ranger-1.1.1
+.TH RANGER 1 ranger-1.2.1
 .SH NAME
 ranger - visual file manager
 .\"-----------------------------------------
@@ -38,7 +38,8 @@ Return the exit code 1 if ranger is used to run a file, for example with
 `ranger --fail-unless-cd filename`.  This can be useful for scripts.
 .TP
 -r \fIdir\fR, --confdir=\fIdir\fR
-Define a different configuration directory.  The default is $HOME/.ranger.
+Define a different configuration directory.  The default is
+$XDG_CONFIG_HOME/ranger (which defaults to ~/.config/ranger)
 .TP
 -m \fIn\fR, --mode=\fIn\fR
 When a filename is supplied, make it run in mode \fIn\fR. Check the
@@ -177,17 +178,17 @@ of your parent shell after exiting ranger:
 
 ranger() {
     command ranger --fail-unless-cd $@ &&
-    cd "$(grep \\^\\' ~/.ranger/bookmarks | cut -b3-)"
+    cd "$(grep \\^\\' ~/.config/ranger/bookmarks | cut -b3-)"
 }
 .\"-----------------------------------------
 .SH CONFIGURATION
 The files in
 .B ranger/defaults/
 can be copied into your configuration directory (by default, this is
-$HOME/.ranger) and customized according to your wishes.  
+~/.config/ranger) and customized according to your wishes.
 Most files don't have to be copied completely though: Just define those
 settings you want to add or change and they will override the defauls.
-Colorschemes can be placed in $HOME/.ranger/colorschemes.
+Colorschemes can be placed in ~/.config/ranger/colorschemes.
 .P
 All configuration is done in Python.
 Each configuration file should contain sufficient documentation.
diff --git a/ranger.py b/ranger.py
index f290d796..5652ba69 100755
--- a/ranger.py
+++ b/ranger.py
@@ -23,7 +23,11 @@
 # after you exit ranger by starting it with: source ranger ranger
 """":
 if [ $1 ]; then
-	$@ --fail-unless-cd && cd "$(grep \^\' ~/.ranger/bookmarks | cut -b3-)"
+	if [ -z "$XDG_CONFIG_HOME" ]; then
+		"$@" --fail-unless-cd && cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)"
+	else
+		"$@" --fail-unless-cd && cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)"
+	fi
 else
 	echo "usage: source path/to/ranger.py path/to/ranger.py"
 fi
diff --git a/ranger/__init__.py b/ranger/__init__.py
index 54baa3c0..7a09dbe3 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -20,7 +20,7 @@ import sys
 from ranger.ext.openstruct import OpenStruct
 
 __license__ = 'GPL3'
-__version__ = '1.1.1'
+__version__ = '1.2.1'
 __credits__ = 'Roman Zimbelmann'
 __author__ = 'Roman Zimbelmann'
 __maintainer__ = 'Roman Zimbelmann'
@@ -31,7 +31,10 @@ Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
 """
 
 USAGE = '%prog [options] [path/filename]'
-DEFAULT_CONFDIR = '~/.ranger'
+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'
 RANGERDIR = os.path.dirname(__file__)
 LOGFILE = '/tmp/errorlog'
 arg = OpenStruct(
diff --git a/ranger/__main__.py b/ranger/__main__.py
index 048e00b9..7559b43d 100644
--- a/ranger/__main__.py
+++ b/ranger/__main__.py
@@ -30,7 +30,17 @@ def parse_arguments():
 	from optparse import OptionParser, SUPPRESS_HELP
 	from ranger import __version__, USAGE, DEFAULT_CONFDIR
 	from ranger.ext.openstruct import OpenStruct
-	parser = OptionParser(usage=USAGE, version='ranger ' + __version__)
+
+	minor_version = __version__[2:]  # assumes major version number is <10
+	if '.' in minor_version:
+		minor_version = minor_version[:minor_version.find('.')]
+	version_tag = ' (stable)' if int(minor_version) % 2 == 0 else ' (testing)'
+	if __version__.endswith('.0'):
+		version_string = 'ranger ' + __version__[:-2] + version_tag
+	else:
+		version_string = 'ranger ' + __version__ + version_tag
+
+	parser = OptionParser(usage=USAGE, version=version_string)
 
 	parser.add_option('-d', '--debug', action='store_true',
 			help="activate debug mode")
@@ -120,7 +130,7 @@ def load_settings(fm, clean):
 			pass
 		# COMPAT WARNING
 		if hasattr(keys, 'initialize_commands'):
-			print("Warning: the syntax for ~/.ranger/keys.py has changed.")
+			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)
@@ -188,8 +198,9 @@ def main():
 
 	SettingsAware._setup()
 
+	targets = arg.targets or ['.']
+	target = targets[0]
 	if arg.targets:
-		target = arg.targets[0]
 		if target.startswith('file://'):
 			target = target[7:]
 		if not os.access(target, os.F_OK):
@@ -202,10 +213,6 @@ def main():
 			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)
-		else:
-			path = target
-	else:
-		path = '.'
 
 	if not ranger.arg.clean:
 		copy_config_files()
@@ -213,9 +220,13 @@ def main():
 	crash_traceback = None
 	try:
 		# Initialize objects
-		EnvironmentAware._assign(Environment(path))
+		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()
 
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index ca3f730d..f4e2ca76 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -17,7 +17,6 @@ import os
 from collections import deque
 from ranger.api import *
 from ranger.shared import FileManagerAware
-from ranger.gui.widgets import console_mode as cmode
 from ranger.ext.command_parser import LazyParser as parse
 
 
@@ -71,9 +70,8 @@ class Command(FileManagerAware):
 	"""Abstract command class"""
 	name = None
 	allow_abbrev = True
-	def __init__(self, line, mode):
+	def __init__(self, line):
 		self.line = line
-		self.mode = mode
 
 	def execute(self):
 		"""Override this"""
diff --git a/ranger/api/keys.py b/ranger/api/keys.py
index 13a4b07f..5812de39 100644
--- a/ranger/api/keys.py
+++ b/ranger/api/keys.py
@@ -20,7 +20,6 @@ from inspect import getargspec, ismethod
 
 from ranger import RANGERDIR
 from ranger.api import *
-from ranger.gui.widgets import console_mode as cmode
 from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS
 from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections
 
diff --git a/ranger/container/history.py b/ranger/container/history.py
index ba13775d..d7a45500 100644
--- a/ranger/container/history.py
+++ b/ranger/container/history.py
@@ -13,74 +13,118 @@
 # 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 collections import deque
-
 class HistoryEmptyException(Exception):
 	pass
 
 class History(object):
-	def __init__(self, maxlen = None):
-		self.history = deque(maxlen = maxlen)
-		self.history_forward = deque(maxlen = maxlen)
+	def __init__(self, maxlen=None, unique=True):
+		self._history = []
+		self._index = 0
+		self.maxlen = maxlen
+		self.unique = unique
 
 	def add(self, item):
-		if len(self.history) == 0 or self.history[-1] != item:
-			self.history.append(item)
-			self.history_forward.clear()
+		# Remove everything after index
+		if self._index < len(self._history) - 2:
+			del self._history[:self._index+1]
+		# Remove Duplicates
+		if self.unique:
+			try:
+				self._history.remove(item)
+			except:
+				pass
+		else:
+			if self._history and self._history[-1] == item:
+				del self._history[-1]
+		# Remove first if list is too long
+		if len(self._history) > self.maxlen - 1:
+			del self._history[0]
+		# Append the item and fast forward
+		self._history.append(item)
+		self._index = len(self._history) - 1
 
-	def modify(self, item):
+	def modify(self, item, unique=False):
+		if self._history and unique:
+			try:
+				self._history.remove(item)
+				self._index -= 1
+			except:
+				pass
 		try:
-			self.history[-1] = item
+			self._history[self._index] = item
 		except IndexError:
-			raise HistoryEmptyException
+			self.add(item)
 
 	def __len__(self):
-		return len(self.history)
+		return len(self._history)
 
 	def current(self):
-		try:
-			return self.history[-1]
-		except IndexError:
-			raise HistoryEmptyException()
+		if self._history:
+			return self._history[self._index]
+		else:
+			raise HistoryEmptyException
 
 	def top(self):
 		try:
-			return self.history_forward[-1]
+			return self._history[-1]
 		except IndexError:
-			try:
-				return self.history[-1]
-			except IndexError:
-				raise HistoryEmptyException()
+			raise HistoryEmptyException()
 
 	def bottom(self):
 		try:
-			return self.history[0]
+			return self._history[0]
 		except IndexError:
 			raise HistoryEmptyException()
 
 	def back(self):
-		if len(self.history) > 1:
-			self.history_forward.appendleft( self.history.pop() )
+		self._index -= 1
+		if self._index < 0:
+			self._index = 0
 		return self.current()
 
 	def move(self, n):
-		if n > 0:
-			return self.forward()
-		if n < 0:
-			return self.back()
+		self._index += n
+		if self._index > len(self._history) - 1:
+			self._index = len(self._history) - 1
+		if self._index < 0:
+			self._index = 0
+		return self.current()
+
+	def search(self, string, n):
+		if n != 0 and string:
+			step = n > 0 and 1 or -1
+			i = self._index
+			steps_left = steps_left_at_start = int(abs(n))
+			while steps_left:
+				i += step
+				if i >= len(self._history) or i < 0:
+					break
+				if self._history[i].startswith(string):
+					steps_left -= 1
+			if steps_left != steps_left_at_start:
+				self._index = i
+		return self.current()
 
 	def __iter__(self):
-		return self.history.__iter__()
+		return self._history.__iter__()
 
 	def next(self):
-		return self.history.next()
+		return self._history.next()
 
 	def forward(self):
-		if len(self.history_forward) > 0:
-			self.history.append( self.history_forward.popleft() )
+		if self._history:
+			self._index += 1
+			if self._index > len(self._history) - 1:
+				self._index = len(self._history) - 1
+		else:
+			self._index = 0
 		return self.current()
 
 	def fast_forward(self):
-		if self.history_forward:
-			self.history.extend(self.history_forward)
-			self.history_forward.clear()
+		if self._history:
+			self._index = len(self._history) - 1
+		else:
+			self._index = 0
+
+	def _left(self):  # used for unit test
+		return self._history[0:self._index+1]
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 44061aca..b379d341 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -16,19 +16,26 @@
 import os
 import re
 import shutil
+import string
 from os.path import join, isdir
 from os import symlink, getcwd
 from inspect import cleandoc
 
 import ranger
 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.gui.widgets import console_mode as cmode
 from ranger.fsobject import File
 from ranger.ext import shutil_generatorized as shutil_g
 from ranger.core.loader import LoadableObject
 
+class _MacroTemplate(string.Template):
+	"""A template for substituting macros in commands"""
+	delimiter = '%'
+	idpattern = '\d?[a-z]'
+
 class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	search_method = 'ctime'
 	search_forward = False
@@ -69,10 +76,79 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		"""Redraw the window"""
 		self.ui.redraw_window()
 
-	def open_console(self, mode=':', string='', prompt=None):
+	def open_console(self, string='', prompt=None, position=None):
 		"""Open the console if the current UI supports that"""
 		if hasattr(self.ui, 'open_console'):
-			self.ui.open_console(mode, string, prompt=prompt)
+			self.ui.open_console(string, prompt=prompt, position=position)
+
+	def execute_console(self, string=''):
+		"""Execute a command for the console"""
+		self.open_console(string=string)
+		self.ui.console.line = string
+		self.ui.console.execute()
+
+	def substitute_macros(self, string):
+		return _MacroTemplate(string).safe_substitute(self._get_macros())
+
+	def _get_macros(self):
+		macros = {}
+
+		if self.fm.env.cf:
+			macros['f'] = shell_quote(self.fm.env.cf.basename)
+		else:
+			macros['f'] = ''
+
+		macros['s'] = ' '.join(shell_quote(fl.basename) \
+				for fl in self.fm.env.get_selection())
+
+		macros['c'] = ' '.join(shell_quote(fl.path)
+				for fl in self.fm.env.copy)
+
+		macros['t'] = ' '.join(shell_quote(fl.basename)
+				for fl in self.fm.env.cwd.files
+				if fl.realpath in self.fm.tags)
+
+		if self.fm.env.cwd:
+			macros['d'] = shell_quote(self.fm.env.cwd.path)
+		else:
+			macros['d'] = '.'
+
+		# define d/f/s macros for each tab
+		for i in range(1,10):
+			try:
+				tab_dir_path = self.fm.tabs[i]
+			except:
+				continue
+			tab_dir = self.fm.env.get_directory(tab_dir_path)
+			i = str(i)
+			macros[i + 'd'] = shell_quote(tab_dir_path)
+			macros[i + 'f'] = shell_quote(tab_dir.pointed_obj.path)
+			macros[i + 's'] = ' '.join(shell_quote(fl.path)
+				for fl in tab_dir.get_selection())
+
+		# define D/F/S for the next tab
+		found_current_tab = False
+		next_tab_path = None
+		first_tab = None
+		for tab in self.fm.tabs:
+			if not first_tab:
+				first_tab = tab
+			if found_current_tab:
+				next_tab_path = self.fm.tabs[tab]
+				break
+			if self.fm.current_tab == tab:
+				found_current_tab = True
+		if found_current_tab and not next_tab_path:
+			next_tab_path = self.fm.tabs[first_tab]
+		next_tab = self.fm.env.get_directory(next_tab_path)
+
+		macros['D'] = shell_quote(next_tab)
+		macros['F'] = shell_quote(next_tab.pointed_obj.path)
+		macros['S'] = ' '.join(shell_quote(fl.path)
+			for fl in next_tab.get_selection())
+
+		return macros
+
 
 	def execute_file(self, files, **kw):
 		"""Execute a file.
@@ -128,7 +204,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				selection = self.env.get_selection()
 				if not self.env.enter_dir(cf) and selection:
 					if self.execute_file(selection, mode=mode) is False:
-						self.open_console(cmode.OPEN_QUICK)
+						self.open_console('open_with ')
 			elif direction.vertical():
 				newpos = direction.move(
 						direction=direction.down(),
@@ -291,7 +367,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def search_file(self, text, regexp=True):
 		if isinstance(text, str) and regexp:
-			text = re.compile(text, re.L | re.U | re.I)
+			try:
+				text = re.compile(text, re.L | re.U | re.I)
+			except:
+				return False
 		self.env.last_search = text
 		self.search(order='search')
 
@@ -345,7 +424,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# --------------------------
 	# -- Tags
 	# --------------------------
-	# Tags are saved in ~/.ranger/tagged and simply mark if a
+	# Tags are saved in ~/.config/ranger/tagged and simply mark if a
 	# file is important to you in any context.
 
 	def tag_toggle(self, movedown=None):
@@ -446,15 +525,29 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 		from ranger.help import get_help, get_help_by_index
 
+		scroll_to_line = 0
 		if narg is not None:
-			help_text = get_help_by_index(narg)
+			chapter, subchapter = int(str(narg)[0]), str(narg)[1:]
+			help_text = get_help_by_index(chapter)
+			lines = help_text.split('\n')
+			if chapter:
+				chapternumber = str(chapter) + '.' + subchapter + '. '
+				skip_to_content = True
+				for line_number, line in enumerate(lines):
+					if skip_to_content:
+						if line[:10] == '==========':
+							skip_to_content = False
+					else:
+						if line.startswith(chapternumber):
+							scroll_to_line = line_number
 		else:
 			help_text = get_help(topic)
+			lines = help_text.split('\n')
 
 		pager = self.ui.open_pager()
 		pager.markup = 'help'
-		lines = help_text.split('\n')
 		pager.set_source(lines)
+		pager.move(down=scroll_to_line)
 
 	def display_log(self):
 		if not hasattr(self.ui, 'open_pager'):
@@ -531,8 +624,9 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		self.env.cut = False
 		self.ui.browser.main_column.request_redraw()
 
-	def copy(self, narg=None, dirarg=None):
-		"""Copy the selected items"""
+	def copy(self, mode='set', narg=None, dirarg=None):
+		"""Copy the selected items.  Modes are: 'set', 'add', 'remove'."""
+		assert mode in ('set', 'add', 'remove')
 		cwd = self.env.cwd
 		if not narg and not dirarg:
 			selected = (f for f in self.env.get_selection() if f in cwd.files)
@@ -548,20 +642,28 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 					pagesize=self.env.termsize[0], offset=offset)
 			cwd.pointer = pos
 			cwd.correct_pointer()
-		self.env.copy = set(selected)
+		if mode == 'set':
+			self.env.copy = set(selected)
+		elif mode == 'add':
+			self.env.copy.update(set(selected))
+		elif mode == 'remove':
+			self.env.copy.difference_update(set(selected))
 		self.env.cut = False
 		self.ui.browser.main_column.request_redraw()
 
-	def cut(self, narg=None, dirarg=None):
-		self.copy(narg=narg, dirarg=dirarg)
+	def cut(self, mode='set', narg=None, dirarg=None):
+		self.copy(mode=mode, narg=narg, dirarg=dirarg)
 		self.env.cut = True
 		self.ui.browser.main_column.request_redraw()
 
-	def paste_symlink(self):
+	def paste_symlink(self, relative=False):
 		copied_files = self.env.copy
 		for f in copied_files:
 			try:
-				symlink(f.path, join(getcwd(), f.basename))
+				if relative:
+					relative_symlink(f.path, join(getcwd(), f.basename))
+				else:
+					symlink(f.path, join(getcwd(), f.basename))
 			except Exception as x:
 				self.notify(x)
 
@@ -602,7 +704,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				for f in self.env.copy:
 					if isdir(f.path):
 						for _ in shutil_g.copytree(src=f.path,
-								dst=join(self.env.cwd.path, f.basename),
+								dst=join(original_path, f.basename),
 								symlinks=True,
 								overwrite=overwrite):
 							yield
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index fca2168b..61db8694 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -54,7 +54,7 @@ class Environment(SettingsAware, SignalDispatcher):
 		self.keybuffer = KeyBuffer(None, None)
 		self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS)
 		self.copy = set()
-		self.history = History(self.settings.max_history_size)
+		self.history = History(self.settings.max_history_size, unique=False)
 
 		try:
 			self.username = pwd.getpwuid(os.geteuid()).pw_name
@@ -166,7 +166,7 @@ class Environment(SettingsAware, SignalDispatcher):
 	def history_go(self, relative):
 		"""Move relative in history"""
 		if self.history:
-			self.history.move(relative).go()
+			self.history.move(relative).go(history=False)
 
 	def enter_dir(self, path, history = True):
 		"""Enter given path"""
diff --git a/ranger/defaults/apps.py b/ranger/defaults/apps.py
index 1f2f751c..47eff0c9 100644
--- a/ranger/defaults/apps.py
+++ b/ranger/defaults/apps.py
@@ -17,13 +17,13 @@
 This is the default ranger configuration file for filetype detection
 and application handling.
 
-You can place this file in your ~/.ranger/ directory and it will be used
+You can place this file in your ~/.config/ranger/ directory and it will be used
 instead of this one.  Though, to minimize your effort when upgrading ranger,
 you may want to subclass CustomApplications rather than making a full copy.
             
 This example modifies the behaviour of "feh" and adds a custom media player:
 
-#### start of the ~/.ranger/apps.py example
+#### start of the ~/.config/ranger/apps.py example
 	from ranger.defaults.apps import CustomApplications as DefaultApps
 	from ranger.api.apps import *
 			
@@ -94,7 +94,7 @@ class CustomApplications(Applications):
 	# ----------------------------------------- application definitions
 	# Note: Trivial applications are defined at the bottom
 	def app_pager(self, c):
-		return tup('less', *c)
+		return tup('less', '-R', *c)
 
 	def app_editor(self, c):
 		try:
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index 8728f9be..d3c05023 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -20,7 +20,7 @@ Each command is a subclass of `Command'.  Several methods are defined
 to interface with the console:
 	execute: call this method when the command is executed.
 	tab: call this method when tab is pressed.
-	quick: call this method after each keypress in the QuickCommandConsole.
+	quick: call this method after each keypress.
 
 The return values for tab() can be either:
 	None: There is no tab completion
@@ -32,7 +32,7 @@ The return value for quick() can be:
 The return value for execute() doesn't matter.
 
 If you want to add custom commands, you can create a file
-~/.ranger/commands.py, add the line:
+~/.config/ranger/commands.py, add the line:
 	from ranger.api.commands import *
 
 and write some command definitions, for example:
@@ -55,6 +55,8 @@ For a list of all actions, check /ranger/core/actions.py.
 '''
 
 from ranger.api.commands import *
+from ranger.ext.get_executables import get_executables
+from ranger.core.runner import ALLOWED_FLAGS
 
 alias('e', 'edit')
 alias('q', 'quit')
@@ -67,9 +69,6 @@ class cd(Command):
 
 	The cd command changes the directory.
 	The command 'cd -' is equivalent to typing ``.
-
-	In the quick console, the directory will be entered without the
-	need to press enter, as soon as there is one unambiguous match.
 	"""
 
 	def execute(self):
@@ -87,17 +86,153 @@ class cd(Command):
 	def tab(self):
 		return self._tab_only_directories()
 
-	def quick(self):
-		from os.path import isdir, join, normpath
+
+class search(Command):
+	def execute(self):
+		self.fm.search_file(parse(self.line).rest(1), regexp=True)
+
+
+class shell(Command):
+	def execute(self):
 		line = parse(self.line)
-		cwd = self.fm.env.cwd.path
+		if line.chunk(1) and line.chunk(1)[0] == '-':
+			flags = line.chunk(1)[1:]
+			command = line.rest(2)
+		else:
+			flags = ''
+			command = line.rest(1)
 
-		rel_dest = line.rest(1)
-		if not rel_dest:
-			return False
+		if not command and 'p' in flags: command = 'cat %f'
+		if command:
+			if '%' in command:
+				command = self.fm.substitute_macros(command)
+			self.fm.execute_command(command, flags=flags)
+
+	def tab(self):
+		line = parse(self.line)
+		if line.chunk(1) and line.chunk(1)[0] == '-':
+			flags = line.chunk(1)[1:]
+			command = line.rest(2)
+		else:
+			flags = ''
+			command = line.rest(1)
+		start = self.line[0:len(self.line) - len(command)]
+
+		try:
+			position_of_last_space = command.rindex(" ")
+		except ValueError:
+			return (start + program + ' ' for program \
+					in get_executables() if program.startswith(command))
+		if position_of_last_space == len(command) - 1:
+			return self.line + '%s '
+		else:
+			before_word, start_of_word = self.line.rsplit(' ', 1)
+			return (before_word + ' ' + file.shell_escaped_basename \
+					for file in self.fm.env.cwd.files \
+					if file.shell_escaped_basename.startswith(start_of_word))
+
+class open_with(Command):
+	def execute(self):
+		line = parse(self.line)
+		app, flags, mode = self._get_app_flags_mode(line.rest(1))
+		self.fm.execute_file(
+				files = [self.fm.env.cf],
+				app = app,
+				flags = flags,
+				mode = mode)
+
+	def _get_app_flags_mode(self, string):
+		"""
+		Extracts the application, flags and mode from a string.
 
-		abs_dest = normpath(join(cwd, rel_dest))
-		return rel_dest != '.' and isdir(abs_dest)
+		examples:
+		"mplayer d 1" => ("mplayer", "d", 1)
+		"aunpack 4" => ("aunpack", "", 4)
+		"p" => ("", "p", 0)
+		"" => None
+		"""
+
+		app = ''
+		flags = ''
+		mode = 0
+		split = string.split()
+
+		if len(split) == 0:
+			pass
+
+		elif len(split) == 1:
+			part = split[0]
+			if self._is_app(part):
+				app = part
+			elif self._is_flags(part):
+				flags = part
+			elif self._is_mode(part):
+				mode = part
+
+		elif len(split) == 2:
+			part0 = split[0]
+			part1 = split[1]
+
+			if self._is_app(part0):
+				app = part0
+				if self._is_flags(part1):
+					flags = part1
+				elif self._is_mode(part1):
+					mode = part1
+			elif self._is_flags(part0):
+				flags = part0
+				if self._is_mode(part1):
+					mode = part1
+			elif self._is_mode(part0):
+				mode = part0
+				if self._is_flags(part1):
+					flags = part1
+
+		elif len(split) >= 3:
+			part0 = split[0]
+			part1 = split[1]
+			part2 = split[2]
+
+			if self._is_app(part0):
+				app = part0
+				if self._is_flags(part1):
+					flags = part1
+					if self._is_mode(part2):
+						mode = part2
+				elif self._is_mode(part1):
+					mode = part1
+					if self._is_flags(part2):
+						flags = part2
+			elif self._is_flags(part0):
+				flags = part0
+				if self._is_mode(part1):
+					mode = part1
+			elif self._is_mode(part0):
+				mode = part0
+				if self._is_flags(part1):
+					flags = part1
+
+		return app, flags, int(mode)
+
+	def _get_tab(self):
+		line = parse(self.line)
+		data = line.rest(1)
+		if ' ' not in data:
+			all_apps = self.fm.apps.all()
+			if all_apps:
+				return (app for app in all_apps if app.startswith(data))
+
+		return None
+
+	def _is_app(self, arg):
+		return self.fm.apps.has(arg) or \
+			(not self._is_flags(arg) and arg in get_executables())
+
+	def _is_flags(self, arg):
+		return all(x in ALLOWED_FLAGS for x in arg)
+
+	def _is_mode(self, arg):
+		return all(x in '0123456789' for x in arg)
 
 
 class find(Command):
@@ -105,35 +240,21 @@ class find(Command):
 	:find <string>
 
 	The find command will attempt to find a partial, case insensitive
-	match in the filenames of the current directory.
-
-	In the quick command console, once there is one unambiguous match,
-	the file will be run automatically.
+	match in the filenames of the current directory and execute the
+	file automatically.
 	"""
 
 	count = 0
 	tab = Command._tab_directory_content
 
 	def execute(self):
-		if self.mode != cmode.COMMAND_QUICK:
-			self._search()
-
-		import re
-		search = parse(self.line).rest(1)
-		search = re.escape(search)
-		self.fm.env.last_search = re.compile(search, re.IGNORECASE)
-		self.fm.search_method = 'search'
-
 		if self.count == 1:
 			self.fm.move(right=1)
 			self.fm.block_input(0.5)
+		else:
+			self.fm.cd(parse(self.line).rest(1))
 
 	def quick(self):
-		self._search()
-		if self.count == 1:
-			return True
-
-	def _search(self):
 		self.count = 0
 		line = parse(self.line)
 		cwd = self.fm.env.cwd
@@ -142,6 +263,11 @@ class find(Command):
 		except IndexError:
 			return False
 
+		if arg == '.':
+			return False
+		if arg == '..':
+			return True
+
 		deq = deque(cwd.files)
 		deq.rotate(-cwd.pointer)
 		i = 0
@@ -281,7 +407,7 @@ class delete(Command):
 				and len(os.listdir(cf.path)) > 0):
 			# better ask for a confirmation, when attempting to
 			# delete multiple files or a non-empty directory.
-			return self.fm.open_console(self.mode, DELETE_WARNING)
+			return self.fm.open_console(DELETE_WARNING)
 
 		# no need for a confirmation, just delete
 		self.fm.delete()
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index c95baed9..9f0c78cb 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -58,7 +58,7 @@ dgg       => fm.cut(foo=bar, dirarg=Direction(to=0))
 5dgg      => fm.cut(foo=bar, narg=5, dirarg=Direction(to=0))
 5d3gg     => fm.cut(foo=bar, narg=5, dirarg=Direction(to=3))
 
-Example ~/.ranger/keys.py
+Example ~/.config/ranger/keys.py
 -------------------------
 from ranger.api.keys import *
 
@@ -130,8 +130,8 @@ map('<F3>', fm.display_file())
 map('<F4>', fm.edit_file())
 map('<F5>', fm.copy())
 map('<F6>', fm.cut())
-map('<F7>', fm.open_console(cmode.COMMAND, 'mkdir '))
-map('<F8>', fm.open_console(cmode.COMMAND, DELETE_WARNING))
+map('<F7>', fm.open_console('mkdir '))
+map('<F8>', fm.open_console(DELETE_WARNING))
 map('<F10>', fm.exit())
 
 # ===================================================================
@@ -168,12 +168,17 @@ map('u<C-V><dir>', fm.mark_in_direction(val=False))
 
 # ------------------------------------------ file system operations
 map('yy', 'y<dir>', fm.copy())
+map('ya', fm.copy(mode='add'))
+map('yr', fm.copy(mode='remove'))
 map('dd', 'd<dir>', fm.cut())
+map('da', fm.cut(mode='add'))
+map('dr', fm.cut(mode='remove'))
 map('pp', fm.paste())
 map('po', fm.paste(overwrite=True))
-map('pl', fm.paste_symlink())
+map('pl', fm.paste_symlink(relative=False))
+map('pL', fm.paste_symlink(relative=True))
 map('p<bg>', fm.hint('press *p* to confirm pasting' \
-		', *o* to overwrite or *l* to create symlinks'))
+		', *o*verwrite, create sym*l*inks, relative sym*L*inks'))
 
 map('u<bg>', fm.hint("un*y*ank, unbook*m*ark, unselect:*v*"))
 map('ud', 'uy', fm.uncut())
@@ -181,12 +186,12 @@ map('ud', 'uy', fm.uncut())
 # ---------------------------------------------------- run programs
 map('S', fm.execute_command(os.environ['SHELL']))
 map('E', fm.edit_file())
-map('du', fm.execute_command('du --max-depth=1 -h | less'))
+map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size'))
 
 # -------------------------------------------------- toggle options
 map('z<bg>', fm.hint("[*cdfhimpPs*] show_*h*idden *p*review_files "\
 		"*P*review_dirs *f*ilter flush*i*nput *m*ouse"))
-map('zh', '<C-h>', fm.toggle_boolean_option('show_hidden'))
+map('zh', '<C-h>', '<backspace>', fm.toggle_boolean_option('show_hidden'))
 map('zp', fm.toggle_boolean_option('preview_files'))
 map('zP', fm.toggle_boolean_option('preview_directories'))
 map('zi', fm.toggle_boolean_option('flushinput'))
@@ -194,7 +199,7 @@ map('zd', fm.toggle_boolean_option('sort_directories_first'))
 map('zc', fm.toggle_boolean_option('collapse_preview'))
 map('zs', fm.toggle_boolean_option('sort_case_insensitive'))
 map('zm', fm.toggle_boolean_option('mouse_enabled'))
-map('zf', fm.open_console(cmode.COMMAND, 'filter '))
+map('zf', fm.open_console('filter '))
 
 # ------------------------------------------------------------ sort
 map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*ase*n*ame *m*time" \
@@ -220,26 +225,28 @@ map('or', 'Or', 'oR', 'OR', lambda arg: \
 @map("A")
 def append_to_filename(arg):
 	command = 'rename ' + arg.fm.env.cf.basename
-	arg.fm.open_console(cmode.COMMAND, command)
+	arg.fm.open_console(command)
 
 @map("I")
 def insert_before_filename(arg):
-	append_to_filename(arg)
-	arg.fm.ui.console.move(right=len('rename '), absolute=True)
+	command = 'rename ' + arg.fm.env.cf.basename
+	arg.fm.open_console(command, position=len('rename '))
 
-map('cw', fm.open_console(cmode.COMMAND, 'rename '))
-map('cd', fm.open_console(cmode.COMMAND, 'cd '))
-map('f', fm.open_console(cmode.COMMAND_QUICK, 'find '))
+map('cw', fm.open_console('rename '))
+map('cd', fm.open_console('cd '))
+map('f', fm.open_console('find '))
 map('d<bg>', fm.hint('d*u* (disk usage) d*d* (cut)'))
-map('@', fm.open_console(cmode.OPEN, '@'))
-map('#', fm.open_console(cmode.OPEN, 'p!'))
+map('@', fm.open_console('shell  %s', position=len('shell ')))
+map('#', fm.open_console('shell -p '))
 
 # --------------------------------------------- jump to directories
 map('gh', fm.cd('~'))
 map('ge', fm.cd('/etc'))
 map('gu', fm.cd('/usr'))
 map('gd', fm.cd('/dev'))
-map('gl', fm.cd('/lib'))
+map('gl', lambda arg: arg.fm.cd(os.path.realpath(arg.fm.env.cwd.path)))
+map('gL', lambda arg: arg.fm.cd(
+		os.path.dirname(os.path.realpath(arg.fm.env.cf.path))))
 map('go', fm.cd('/opt'))
 map('gv', fm.cd('/var'))
 map('gr', 'g/', fm.cd('/'))
@@ -261,7 +268,7 @@ for n in range(1, 10):
 	map('<A-' + str(n) + '>', fm.tab_open(n))
 
 # ------------------------------------------------------- searching
-map('/', fm.open_console(cmode.SEARCH))
+map('/', fm.open_console('search '))
 
 map('n', fm.search())
 map('N', fm.search(forward=False))
@@ -300,11 +307,10 @@ def ctrl_c(arg):
 		arg.fm.notify("Aborting: " + item.get_description())
 		arg.fm.loader.remove(index=0)
 
-map(':', ';', fm.open_console(cmode.COMMAND))
-map('>', fm.open_console(cmode.COMMAND_QUICK))
-map('!', fm.open_console(cmode.OPEN, prompt='!'))
-map('s', fm.open_console(cmode.OPEN, prompt='$'))
-map('r', fm.open_console(cmode.OPEN_QUICK))
+map(':', ';', fm.open_console(''))
+map('!', fm.open_console('shell '))
+map('s', fm.open_console('shell '))
+map('r', fm.open_console('open_with '))
 
 
 # ===================================================================
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index 8f8ee830..39046443 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -17,7 +17,7 @@
 This is the default configuration file of ranger.
 
 There are two ways of customizing ranger.  The first and recommended
-method is creating a file at ~/.ranger/options.py and adding
+method is creating a file at ~/.config/ranger/options.py and adding
 those lines you want to change.  It might look like this:
 
 from ranger.api.options import *
@@ -94,7 +94,7 @@ tilde_in_titlebar = True
 
 # How many directory-changes or console-commands should be kept in history?
 max_history_size = 20
-max_console_history_size = 20
+max_console_history_size = 50
 
 # Try to keep so much space between the top/bottom border when scrolling:
 scroll_offset = 8
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index beeaf6d3..40111b6d 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -13,24 +13,39 @@
 # 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 math
-
-ONE_KB = 1024
-UNITS = 'BKMGTP'
-MAX_EXPONENT = len(UNITS) - 1
-
 def human_readable(byte, seperator=' '):
-	if not byte:
-		return '0'
-
-	exponent = int(math.log(byte, 2) / 10)
-	flt = round(float(byte) / (1 << (10 * exponent)), 2)
+	"""
+	Convert a large number of bytes to an easily readable format.
 
-	if exponent > MAX_EXPONENT:
-		return '>9000' # off scale
-
-	if int(flt) == flt:
-		return '%.0f%s%s' % (flt, seperator, UNITS[exponent])
-
-	else:
-		return '%.2f%s%s' % (flt, seperator, UNITS[exponent])
+	>>> human_readable(54)
+	"54 B"
+	>>> human_readable(1500)
+	"1.46 K"
+	>>> human_readable(2 ** 20 * 1023)
+	"1023 M"
+	"""
+	if byte <= 0:
+		return '0'
+	if byte < 2**10:
+		return '%d%sB'   % (byte, seperator)
+	if byte < 2**10 * 999:
+		return '%.3g%sK' % (byte / 2**10.0, seperator)
+	if byte < 2**20:
+		return '%.4g%sK' % (byte / 2**10.0, seperator)
+	if byte < 2**20 * 999:
+		return '%.3g%sM' % (byte / 2**20.0, seperator)
+	if byte < 2**30:
+		return '%.4g%sM' % (byte / 2**20.0, seperator)
+	if byte < 2**30 * 999:
+		return '%.3g%sG' % (byte / 2**30.0, seperator)
+	if byte < 2**40:
+		return '%.4g%sG' % (byte / 2**30.0, seperator)
+	if byte < 2**40 * 999:
+		return '%.3g%sT' % (byte / 2**40.0, seperator)
+	if byte < 2**50:
+		return '%.4g%sT' % (byte / 2**40.0, seperator)
+	if byte < 2**50 * 999:
+		return '%.3g%sP' % (byte / 2**50.0, seperator)
+	if byte < 2**60:
+		return '%.4g%sP' % (byte / 2**50.0, seperator)
+	return '>9000'
diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py
new file mode 100644
index 00000000..bba00e39
--- /dev/null
+++ b/ranger/ext/relative_symlink.py
@@ -0,0 +1,39 @@
+# 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 os import symlink, sep
+from os.path import dirname, join
+
+def relative_symlink(src, dst):
+	common_base = get_common_base(src, dst)
+	symlink(get_relative_source_file(src, dst, common_base), dst)
+
+def get_relative_source_file(src, dst, common_base=None):
+	if common_base is None:
+		common_base = get_common_base(src, dst)
+	return '../' * dst.count('/', len(common_base)) + src[len(common_base):]
+
+def get_common_base(src, dst):
+	if not src or not dst:
+		return '/'
+	i = 0
+	while True:
+		new_i = src.find(sep, i + 1)
+		if new_i == -1:
+			break
+		if not dst.startswith(src[:new_i + 1]):
+			break
+		i = new_i
+	return src[:i + 1]
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index f1bcd9a3..9bdb4caa 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -63,7 +63,6 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 	filter = None
 	marked_items = None
 	scroll_begin = 0
-	scroll_offset = 0
 
 	mount_path = '/'
 	disk_usage = 0
@@ -213,7 +212,6 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 					yield
 				self.disk_usage = disk_usage
 
-				self.scroll_offset = 0
 				self.filenames = filenames
 				self.files = files
 
@@ -420,10 +418,10 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 			return True
 		return self.last_used + seconds < time()
 
-	def go(self):
+	def go(self, history=True):
 		"""enter the directory if the filemanager is running"""
 		if self.fm:
-			return self.fm.enter_dir(self.path)
+			return self.fm.enter_dir(self.path, history=history)
 		return False
 
 	def empty(self):
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 4acad100..fad22a1f 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -76,7 +76,9 @@ class File(FileSystemObject):
 	def has_preview(self):
 		if not self.fm.settings.preview_files:
 			return False
-		if not self.accessible or self.is_fifo or self.is_device:
+		if self.is_socket or self.is_fifo or self.is_device:
+			return False
+		if not self.accessible:
 			return False
 		if self.image or self.container:
 			return True
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index 501b8788..5b317acb 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -24,7 +24,7 @@ The values are specified in ranger.gui.color.
 A colorscheme must...
 
 1. be inside either of these directories:
-~/.ranger/colorschemes/
+~/.config/ranger/colorschemes/
 path/to/ranger/colorschemes/
 
 2. be a subclass of ranger.gui.colorscheme.ColorScheme
@@ -121,7 +121,7 @@ class ColorScheme(SettingsAware):
 		return fg, -1, attr
 
 def _colorscheme_name_to_class(signal):
-	# Find the colorscheme.  First look for it at ~/.ranger/colorschemes,
+	# Find the colorscheme.  First look for it at ~/.config/ranger/colorschemes,
 	# then at RANGERDIR/colorschemes.  If the file contains a class
 	# named Scheme, it is used.  Otherwise, an arbitrary other class
 	# is picked.
@@ -139,7 +139,7 @@ def _colorscheme_name_to_class(signal):
 		except:
 			return False
 
-	# create ~/.ranger/colorschemes/__init__.py if it doesn't exist
+	# 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')
diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py
index 4baea756..434e6d45 100644
--- a/ranger/gui/defaultui.py
+++ b/ranger/gui/defaultui.py
@@ -92,8 +92,8 @@ class DefaultUI(UI):
 	def close_embedded_pager(self):
 		self.browser.close_pager()
 
-	def open_console(self, mode, string='', prompt=None):
-		if self.console.open(mode, string, prompt=prompt):
+	def open_console(self, string='', prompt=None, position=None):
+		if self.console.open(string, prompt=prompt, position=position):
 			self.status.msg = None
 			self.console.on_close = self.close_console
 			self.console.visible = True
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 1ee7ebc0..57264292 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -18,89 +18,63 @@ The Console widget implements a vim-like console for entering
 commands, searching and executing files.
 """
 
-import string
 import curses
+import re
 from collections import deque
 
 from . import Widget
-from ranger.gui.widgets.console_mode import is_valid_mode, mode_to_class
 from ranger import log, relpath_conf
-from ranger.core.runner import ALLOWED_FLAGS
-from ranger.ext.shell_escape import shell_quote
-from ranger.ext.utfwidth import uwid
 from ranger.container.keymap import CommandArgs
-from ranger.ext.get_executables import get_executables
 from ranger.ext.direction import Direction
 from ranger.ext.utfwidth import uwid, uchars
 from ranger.container import History
 from ranger.container.history import HistoryEmptyException
 import ranger
 
-DEFAULT_HISTORY = 0
-SEARCH_HISTORY = 1
-QUICKOPEN_HISTORY = 2
-OPEN_HISTORY = 3
-
-class _CustomTemplate(string.Template):
-	"""A string.Template subclass for use in the OpenConsole"""
-	delimiter = '%'
-	idpattern = '[a-z]'
-
-
 class Console(Widget):
-	mode = None
 	visible = False
 	last_cursor_mode = None
+	history_search_pattern = None
 	prompt = ':'
 	copy = ''
 	tab_deque = None
 	original_line = None
 	history = None
-	histories = None
 	override = None
 	allow_close = False
-	historypaths = []
+	historypath = None
 
 	def __init__(self, win):
 		Widget.__init__(self, win)
 		self.clear()
-		self.histories = []
-		# load histories from files
-		if ranger.arg.clean:
-			for i in range(4):
-				self.histories.append(
-						History(self.settings.max_console_history_size))
-		else:
-			self.historypaths = [relpath_conf(x) for x in \
-				('history', 'history_search', 'history_qopen', 'history_open')]
-			for i, path in enumerate(self.historypaths):
-				hist = History(self.settings.max_console_history_size)
-				self.histories.append(hist)
-				if ranger.arg.clean: continue
-				try: f = open(path, 'r')
-				except: continue
+		self.history = History(self.settings.max_console_history_size)
+		# load history from files
+		if not ranger.arg.clean:
+			self.historypath = relpath_conf('history')
+			try:
+				f = open(self.historypath, 'r')
+			except:
+				pass
+			else:
 				for line in f:
-					hist.add(line[:-1])
+					self.history.add(line[:-1])
 				f.close()
 
 	def destroy(self):
-		# save histories from files
+		# save history to files
 		if ranger.arg.clean or not self.settings.save_console_history:
 			return
-		for i, path in enumerate(self.historypaths):
-			try: f = open(path, 'w')
-			except: continue
-			for entry in self.histories[i]:
-				f.write(entry + '\n')
-			f.close()
-
-	def init(self):
-		"""override this. Called directly after class change"""
+		if self.historypath:
+			try:
+				f = open(self.historypath, 'w')
+			except:
+				pass
+			else:
+				for entry in self.history:
+					f.write(entry + '\n')
+				f.close()
 
 	def draw(self):
-		if self.mode is None:
-			return
-
 		self.win.erase()
 		self.addstr(0, 0, self.prompt)
 		overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1
@@ -117,32 +91,28 @@ class Console(Widget):
 		except:
 			pass
 
-	def open(self, mode, string='', prompt=None):
-		if not is_valid_mode(mode):
-			return False
+	def open(self, string='', prompt=None, position=None):
 		if prompt is not None:
 			assert isinstance(prompt, str)
 			self.prompt = prompt
 		elif 'prompt' in self.__dict__:
 			del self.prompt
 
-		cls = mode_to_class(mode)
-
 		if self.last_cursor_mode is None:
 			try:
 				self.last_cursor_mode = curses.curs_set(1)
 			except:
 				pass
-		self.mode = mode
-		self.__class__ = cls
-		self.history = self.histories[DEFAULT_HISTORY]
-		self.init()
 		self.allow_close = False
 		self.tab_deque = None
 		self.focused = True
 		self.visible = True
 		self.line = string
+		self.history_search_pattern = self.line
 		self.pos = len(string)
+		if position is not None:
+			self.pos = min(self.pos, position)
+		self.history.fast_forward()
 		self.history.add('')
 		return True
 
@@ -215,7 +185,10 @@ class Console(Widget):
 		else:
 			if self.line != current and self.line != self.history.top():
 				self.history.modify(self.line)
-			self.history.move(n)
+			if self.history_search_pattern:
+				self.history.search(self.history_search_pattern, n)
+			else:
+				self.history.move(n)
 			current = self.history.current()
 			if self.line != current:
 				self.line = self.history.current()
@@ -223,7 +196,7 @@ class Console(Widget):
 
 	def add_to_history(self):
 		self.history.fast_forward()
-		self.history.modify(self.line)
+		self.history.modify(self.line, unique=True)
 
 	def move(self, **keywords):
 		direction = Direction(keywords)
@@ -258,15 +231,15 @@ class Console(Widget):
 		self.on_line_change()
 
 	def delete_word(self):
-		self.tab_deque = None
-		try:
-			i = self.line.rindex(' ', 0, self.pos - 1) + 1
-			self.line = self.line[:i] + self.line[self.pos:]
+		if self.line:
+			self.tab_deque = None
+			i = len(self.line) - 2
+			while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
+				i -= 1
+			self.copy = self.line[i + 1:]
+			self.line = self.line[:i + 1]
 			self.pos = len(self.line)
-		except ValueError:
-			self.line = ''
-			self.pos = 0
-		self.on_line_change()
+			self.on_line_change()
 
 	def delete(self, mod):
 		self.tab_deque = None
@@ -282,56 +255,6 @@ class Console(Widget):
 		self.line = left_part + ''.join(uc[upos+1:])
 		self.on_line_change()
 
-	def execute(self):
-		pass
-
-	def tab(self):
-		pass
-
-	def on_line_change(self):
-		pass
-
-
-class ConsoleWithTab(Console):
-	def tab(self, n=1):
-		if self.tab_deque is None:
-			tab_result = self._get_tab()
-
-			if isinstance(tab_result, str):
-				self.line = tab_result
-				self.pos = len(tab_result)
-				self.on_line_change()
-
-			elif tab_result == None:
-				pass
-
-			elif hasattr(tab_result, '__iter__'):
-				self.tab_deque = deque(tab_result)
-				self.tab_deque.appendleft(self.line)
-
-		if self.tab_deque is not None:
-			self.tab_deque.rotate(-n)
-			self.line = self.tab_deque[0]
-			self.pos = len(self.line)
-			self.on_line_change()
-
-	def _get_tab(self):
-		"""
-		Override this function in the subclass!
-
-		It should return either a string, an iterable or None.
-		If a string is returned, tabbing will result in the line turning
-		into that string.
-		If another iterable is returned, each tabbing will cycle through
-		the elements of the iterable (which have to be strings).
-		If None is returned, nothing will happen.
-		"""
-
-		return None
-
-
-class CommandConsole(ConsoleWithTab):
-	prompt = ':'
 
 	def execute(self, cmd=None):
 		self.allow_close = True
@@ -355,7 +278,7 @@ class CommandConsole(ConsoleWithTab):
 		except:
 			return None
 		else:
-			return command_class(self.line, self.mode)
+			return command_class(self.line)
 
 	def _get_cmd_class(self):
 		return self.fm.commands.get_command(self.line.split()[0])
@@ -370,277 +293,35 @@ class CommandConsole(ConsoleWithTab):
 
 		return self.fm.commands.command_generator(self.line)
 
+	def tab(self, n=1):
+		if self.tab_deque is None:
+			tab_result = self._get_tab()
+
+			if isinstance(tab_result, str):
+				self.line = tab_result
+				self.pos = len(tab_result)
+				self.on_line_change()
+
+			elif tab_result == None:
+				pass
+
+			elif hasattr(tab_result, '__iter__'):
+				self.tab_deque = deque(tab_result)
+				self.tab_deque.appendleft(self.line)
+
+		if self.tab_deque is not None:
+			self.tab_deque.rotate(-n)
+			self.line = self.tab_deque[0]
+			self.pos = len(self.line)
+			self.on_line_change()
 
-class QuickCommandConsole(CommandConsole):
-	"""
-	The QuickCommandConsole is essentially the same as the
-	CommandConsole, and includes one additional feature:
-	After each letter you type, it checks whether the command as it
-	stands there could be executed in a meaningful way, and if it does,
-	run it right away.
-
-	Example:
-	>cd ..
-	As you type the last dot, The console will recognize what you mean
-	and enter the parent directory saving you the time of pressing enter.
-	"""
-	prompt = '>'
 	def on_line_change(self):
+		self.history_search_pattern = self.line
 		try:
 			cls = self._get_cmd_class()
 		except (KeyError, ValueError, IndexError):
 			pass
 		else:
-			cmd = cls(self.line, self.mode)
+			cmd = cls(self.line)
 			if cmd and cmd.quick():
 				self.execute(cmd)
-
-
-class SearchConsole(Console):
-	prompt = '/'
-
-	def init(self):
-		self.history = self.histories[SEARCH_HISTORY]
-
-	def execute(self):
-		self.fm.search_file(self.line, regexp=True)
-		self.close()
-
-
-class OpenConsole(ConsoleWithTab):
-	"""
-	The Open Console allows you to execute shell commands:
-	!vim *         will run vim and open all files in the directory.
-
-	%f will be replaced with the basename of the highlighted file
-	%s will be selected with all files in the selection
-
-	There is a special syntax for more control:
-
-	!d! mplayer    will run mplayer with flags (d means detached)
-	!@ mplayer     will open the selected files with mplayer
-			   (equivalent to !mplayer %s)
-
-	Those two can be combinated:
-
-	!d!@mplayer    will open the selection with a detached mplayer
-				   (again, this is equivalent to !d!mplayer %s)
-
-	For a list of other flags than "d", check chapter 2.5 of the documentation
-	"""
-	prompt = '!'
-
-	def init(self):
-		self.history = self.histories[OPEN_HISTORY]
-
-	def execute(self):
-		command, flags = self._parse()
-		if not command and 'p' in flags:
-			command = 'cat %f'
-		if command:
-			if _CustomTemplate.delimiter in command:
-				command = self._substitute_metachars(command)
-			self.fm.execute_command(command, flags=flags)
-		self.close()
-
-	def _get_tab(self):
-		try:
-			i = self.line.index('!')+1
-		except ValueError:
-			line = self.line
-			start = ''
-		else:
-			line = self.line[i:]
-			start = self.line[:i]
-
-		try:
-			position_of_last_space = line.rindex(" ")
-		except ValueError:
-			return (start + program + ' ' for program \
-					in get_executables() if program.startswith(line))
-		if position_of_last_space == len(line) - 1:
-			return self.line + '%s '
-		else:
-			before_word, start_of_word = self.line.rsplit(' ', 1)
-			return (before_word + ' ' + file.shell_escaped_basename \
-					for file in self.fm.env.cwd.files \
-					if file.shell_escaped_basename.startswith(start_of_word))
-
-	def _substitute_metachars(self, command):
-		macros = {}
-
-		if self.fm.env.cf:
-			macros['f'] = shell_quote(self.fm.env.cf.basename)
-		else:
-			macros['f'] = ''
-
-		macros['s'] = ' '.join(shell_quote(fl.basename) \
-				for fl in self.fm.env.get_selection())
-
-		macros['c'] = ' '.join(shell_quote(fl.path)
-				for fl in self.fm.env.copy)
-
-		macros['t'] = ' '.join(shell_quote(fl.basename)
-				for fl in self.fm.env.cwd.files
-				if fl.realpath in self.fm.tags)
-
-		if self.fm.env.cwd:
-			macros['d'] = shell_quote(self.fm.env.cwd.path)
-		else:
-			macros['d'] = '.'
-
-		return _CustomTemplate(command).safe_substitute(macros)
-
-	def _parse(self):
-		if '!' in self.line:
-			flags, cmd = self.line.split('!', 1)
-		else:
-			flags, cmd = '', self.line
-
-		add_selection = False
-		if cmd.startswith('@'):
-			cmd = cmd[1:]
-			add_selection = True
-		elif flags.startswith('@'):
-			flags = flags[1:]
-			add_selection = True
-
-		if add_selection:
-			cmd += ' ' + ' '.join(shell_quote(fl.basename) \
-					for fl in self.env.get_selection())
-
-		return (cmd, flags)
-
-
-class QuickOpenConsole(ConsoleWithTab):
-	"""
-	The Quick Open Console allows you to open files with predefined programs
-	and modes very quickly.  By adding flags to the command, you can specify
-	precisely how the program is run, e.g. the d-flag will run it detached
-	from the file manager.
-
-	For a list of other flags than "d", check chapter 2.5 of the documentation
-
-	The syntax is "open with: <application> <mode> <flags>".
-	The parsing of the arguments is very flexible.  You can leave out one or
-	more arguments (or even all of them) and it will fall back to default
-	values.  You can switch the order as well.
-	There is just one rule:
-
-	If you supply the <application>, it has to be the first argument.
-
-	Examples:
-
-	open with: mplayer D     open the selection in mplayer, but not detached
-	open with: 1             open it with the default handler in mode 1
-	open with: d             open it detached with the default handler
-	open with: p             open it as usual, but pipe the output to "less"
-	open with: totem 1 Ds    open in totem in mode 1, will not detach the
-							 process (flag D) but discard the output (flag s)
-	"""
-
-	prompt = 'open with: '
-
-	def init(self):
-		self.history = self.histories[QUICKOPEN_HISTORY]
-
-	def execute(self):
-		split = self.line.split()
-		app, flags, mode = self._get_app_flags_mode()
-		self.fm.execute_file(
-				files = [self.env.cf],
-				app = app,
-				flags = flags,
-				mode = mode )
-		self.close()
-
-	def _get_app_flags_mode(self):
-		"""
-		Extracts the application, flags and mode from
-		a string entered into the "openwith_quick" console.
-		"""
-		# examples:
-		# "mplayer d 1" => ("mplayer", "d", 1)
-		# "aunpack 4" => ("aunpack", "", 4)
-		# "p" => ("", "p", 0)
-		# "" => None
-
-		app = ''
-		flags = ''
-		mode = 0
-		split = self.line.split()
-
-		if len(split) == 0:
-			pass
-
-		elif len(split) == 1:
-			part = split[0]
-			if self._is_app(part):
-				app = part
-			elif self._is_flags(part):
-				flags = part
-			elif self._is_mode(part):
-				mode = part
-
-		elif len(split) == 2:
-			part0 = split[0]
-			part1 = split[1]
-
-			if self._is_app(part0):
-				app = part0
-				if self._is_flags(part1):
-					flags = part1
-				elif self._is_mode(part1):
-					mode = part1
-			elif self._is_flags(part0):
-				flags = part0
-				if self._is_mode(part1):
-					mode = part1
-			elif self._is_mode(part0):
-				mode = part0
-				if self._is_flags(part1):
-					flags = part1
-
-		elif len(split) >= 3:
-			part0 = split[0]
-			part1 = split[1]
-			part2 = split[2]
-
-			if self._is_app(part0):
-				app = part0
-				if self._is_flags(part1):
-					flags = part1
-					if self._is_mode(part2):
-						mode = part2
-				elif self._is_mode(part1):
-					mode = part1
-					if self._is_flags(part2):
-						flags = part2
-			elif self._is_flags(part0):
-				flags = part0
-				if self._is_mode(part1):
-					mode = part1
-			elif self._is_mode(part0):
-				mode = part0
-				if self._is_flags(part1):
-					flags = part1
-
-		return app, flags, int(mode)
-
-	def _get_tab(self):
-		if ' ' not in self.line:
-			all_apps = self.fm.apps.all()
-			if all_apps:
-				return (app for app in all_apps if app.startswith(self.line))
-
-		return None
-
-	def _is_app(self, arg):
-		return self.fm.apps.has(arg) or \
-			(not self._is_flags(arg) and arg in get_executables())
-
-	def _is_flags(self, arg):
-		return all(x in ALLOWED_FLAGS for x in arg)
-
-	def _is_mode(self, arg):
-		return all(x in '0123456789' for x in arg)
diff --git a/ranger/gui/widgets/console_mode.py b/ranger/gui/widgets/console_mode.py
deleted file mode 100644
index c29f9959..00000000
--- a/ranger/gui/widgets/console_mode.py
+++ /dev/null
@@ -1,55 +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/>.
-
-DEFAULT = 0
-COMMAND = 1
-COMMAND_QUICK = 2
-OPEN = 3
-OPEN_QUICK = 4
-SEARCH = 5
-
-def is_valid_mode(mode):
-	"""
-	Returns True or False depending on whether the mode is valid or not.
-	"""
-	return isinstance(mode, int) and mode >= 0 and mode <= 5
-
-def all_modes(mode):
-	"""
-	Returns a generator containing all valid modes.
-	"""
-	return range(6)
-
-def mode_to_class(mode):
-	"""
-	Associates modes with the actual classes
-	from ranger.gui.widgets.console.
-	"""
-	from .console import Console, CommandConsole, OpenConsole, \
-			QuickOpenConsole, QuickCommandConsole, SearchConsole
-
-	if mode == DEFAULT:
-		return Console
-	if mode == COMMAND:
-		return CommandConsole
-	if mode == OPEN:
-		return OpenConsole
-	if mode == OPEN_QUICK:
-		return QuickOpenConsole
-	if mode == COMMAND_QUICK:
-		return QuickCommandConsole
-	if mode == SEARCH:
-		return SearchConsole
-	raise ValueError
diff --git a/ranger/help/console.py b/ranger/help/console.py
index d1764841..f03491db 100644
--- a/ranger/help/console.py
+++ b/ranger/help/console.py
@@ -13,59 +13,33 @@
 # 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. Basic movement and browsing
+3. The Console
 
-3.1. Overview of all the Console Modes
+3.1. General Information
 3.2. List of Commands
-3.3. The (Quick) Command Console
-3.4. The Open Console
-3.5. The Quick Open Console
-
+3.3. Macros
+3.4. The more complicated Commands in Detail
 
 ==============================================================================
-3.1. Overview of all the Console Modes:
-
-There are, as of now, five different types of consoles:
-
-3.1.1. The Command Console, opened by pressing ":"
-      Usable for entering commands like you know it from vim.
-      Use the tab key to cycle through all available commands
-      and press F1 to view the docstring of the current command.
-
-3.1.2. The Quick Command Console, opened with ">"
-      Works just like 3.1.1, but it saves you pressing <RETURN> by checking
-      whether the current input can be executed in a meaningful way
-      and doing so automatically.  (works with commands like cd, find.)
-
-3.1.3. The Search Console, opened with "/"
-      Very much like the console you get in vim after pressing "/".
-      The current directory will be searched for files whose names
-      match the given regular expression.
+3.1. General Information
 
-3.1.4. The Open Console, activated with "!"
-      Similar to ":!..." in vim.  The given command will be executed,
-      "%s" will be replaced with all the files in the selection,
-      "%f" is replaced with only the current highlighted file.
-      There are further options to tweak how the command is executed.
+The console is opened by pressing ":".  Press <TAB> to cycle through all
+available commands and press <F1> to view help about the current command.
 
-3.1.5. The Quick Open Console, opened by pressing "r"
-      Rather than having to enter the full command, this console gives
-      you a higher level interface to running files.
-      You can define the application, the mode and the flags separated
-      with spaces.
+All commands are defined in the file ranger/defaults/commands.py, which
+also contains a detailed specification.
 
 
 ==============================================================================
 3.2. List of Commands
 
-This is a list of the few commands which are implemented by default.
-All commands except for ":delete" can be abbreviated to the shortest
+All commands except for ":delete" can be abbreviated with the shortest
 unambiguous name, e.g. ":chmod" can be written as ":ch" but not as ":c" since
 it conflicts with ":cd".
 
 
 :cd <dirname>
-      Changes the directory to <dirname> *
+      Changes the directory to <dirname>
 
 :chmod <octal_number>
       Sets the permissions of the selection to the octal number.
@@ -86,10 +60,9 @@ it conflicts with ":cd".
 :filter <string>
       Displays only files which contain <string> in their basename.
 
-:find <string>
-      Look for a partial, case insensitive match in the filenames
-      of the current directory and execute it if there is only
-      one match. *
+:find <regexp>
+      Quickly find files that match the regexp and execute the first
+	  unambiguous match.
 
 :grep <string>
       Looks for a string in all marked files or directory.
@@ -104,65 +77,34 @@ it conflicts with ":cd".
 :mkdir <dirname>
       Creates a directory with the name <dirname>
 
+:open_with [<program>] [<flags>] [<mode>]
+      Open the current file with the program, flags and mode. |24?| |25?|
+      All arguments are optional.  If none is given, its equivalent to
+      pressing <Enter>
+
 :quit
       Exits ranger
 
 :rename <newname>
       Changes the name of the currently highlighted file to <newname>
 
+:search <regexp>
+      Search for a regexp in all file names, like the / key in vim.
+
+:shell [-<flags>] <command>
+      Run the command, optionally with some flags.  |25?|
+      Example: shell -d firefox -safe-mode %s
+      opens (detached from ranger) the selection in firefox' safe-mode
+
 :terminal
       Spawns "x-terminal-emulator" starting in the current directory.
 
 :touch <filename>
       Creates a file with the name <filename>
 
-* implements handler for the Quick Command Console.
-
 
 ==============================================================================
-3.3. The (Quick) Command Console
-
-Open these consoles by pressing ":" or ">"
-
-The Command Console and the "Quick" Command Console are mostly identical
-since they share the commands.  As explained in 3.1.2, the point in using
-the Quick Command Console is that the command is executed without the
-need to press <RETURN> as soon as ranger thinks you want it executed.
-
-Take the "find" command, for example.  It will attempt to find a file
-or directory which matches the given input, and if there is one unambiguous
-match, that file will be executed or that directory will be entered.
-If you use the "find" command in the quick console, as soon as there is
-one unambiguous match, <RETURN> will be pressed for you, giving you a
-very fast way to browse your files.
-
-
-All commands are defined in ranger/defaults/commands.py.  You can refer to this
-file for a list of commands.  Implementing new commands should be intuitive:
-Create a new class, a subclass of Command, and define the execute method
-is usually enough.  For parsing command input, the command parser in
-ranger/ext/command_parser.py is used.  The tab method should return None,
-a string or an iterable sequence containing the strings which should be
-cycled through by pressing tab.
-
-Only those commands which implement the quick() method will be specially
-treated by the Quick Command Console.  For the rest, both consoles are equal.
-quick() is called after each key press and if it returns True, the
-command will be executed immediately.
-
-
-Pressing F1 inside the console displays the docstring of the current command
-in the pager if docstrings are available (i.e.  unless python is run with
-the flag "-OO" which removes all docstrings.)
-
-
-==============================================================================
-3.4. The Open Console
-
-Open this console by pressing "!" or "s"
-
-The Open Console allows you to execute shell commands:
-!vim *         will run vim and open all files in the directory.
+3.3. Macros
 
 Like in similar filemanagers there are some macros.  Use them in
 commands and they will be replaced with a list of files.
@@ -173,61 +115,41 @@ commands and they will be replaced with a list of files.
 	%t	all tagged files in the current directory
 	%c	the full paths of the currently copied/cut files
 
+The macros %f, %d and %s also have upper case variants, %F, %D and %S,
+which refer to the next tab.  To refer to specific tabs, add a number in
+between. Examples:
+	%D	The path of the directory in the next tab
+	%7s	The selection of the seventh tab
+
 %c is the only macro which ranges out of the current directory. So you may
 "abuse" the copying function for other purposes, like diffing two files which
 are in different directories:
 
 	Yank the file A (type yy), move to the file B and use:
-	!p!diff %c %f
-
-There is a special syntax for more control:
-
-!d! mplayer    will run mplayer with flags (d means detached)
-!@ mplayer     will open the selected files with mplayer
-	       (equivalent to !mplayer %s)
-
-Those two can be combinated:
-
-!d!@mplayer    will open the selection with a detached mplayer
-	       (again, this is equivalent to !d!mplayer %s)
-
-These keys open the console with a predefined text:
-	@	"!@"	Suffixes %s.  Good for things like "@mount"
-	#	"!p!"	Pipes output through a pager.  For commands with output.
-			Note: A plain "!p!" will be translated to "!p!cat %f"
-
-For a list of other flags than "d", check chapter 2.5 of the documentation
+	:shell -p diff %c %f
 
 
 ==============================================================================
-3.5. The Quick Open Console
-
-Open this console by pressing "r"
-
-The Quick Open Console allows you to open files with predefined programs
-and modes very quickly.  By adding flags to the command, you can specify
-precisely how the program is run, e.g. the d-flag will run it detached
-from the file manager.
-
-For a list of other flags than "d", check chapter 2.5 of the documentation
-
-The syntax is "open with: <application> <mode> <flags>".
-The parsing of the arguments is very flexible.  You can leave out one or
-more arguments (or even all of them) and it will fall back to default
-values.  You can switch the order as well.
-There is just one rule:
-
-If you supply the <application>, it has to be the first argument.
-
-Examples:
-
-open with: mplayer D     open the selection in mplayer, but not detached
-open with: 1             open it with the default handler in mode 1
-open with: d             open it detached with the default handler
-open with: p             open it as usual, but pipe the output to "less"
-open with: totem 1 Ds    open in totem in mode 1, will not detach the
-			 process (flag D) but discard the output (flag s)
-
+3.4. The more complicated Commands in Detail
+
+3.3.1. "find"
+The find command is different than others: it doesn't require you to
+press <RETURN>.  To speed things up, it tries to guess when you're
+done typing and executes the command right away.
+The key "f" opens the console with ":find "
+
+3.3.2. "shell"
+The shell command accepts flags |25?| as the first argument. This example
+will use the "p"-flag, which pipes the output to the pager:
+	:shell -p cat somefile.txt
+
+There are some shortcuts which open the console with the shell command:
+	"!" opens ":shell "
+	"@" opens ":shell  %s"
+	"#" opens ":shell -p "
+
+3.3.3. "open_with"
+The open_with command is explained in detail in chapter 2.2. |22?|
 
 ==============================================================================
 """
diff --git a/ranger/help/fileop.py b/ranger/help/fileop.py
index 53ce9ff8..ac23c6d4 100644
--- a/ranger/help/fileop.py
+++ b/ranger/help/fileop.py
@@ -31,7 +31,7 @@ harm your files:
 :chmod <number>    Change the rights of the selection
 :delete            DELETES ALL FILES IN THE SELECTION
 :rename <newname>  Change the name of the current file
-pp, pl, po         Pastes the copied files in different ways
+pp, pl, pL, po     Pastes the copied files in different ways
 
 Think twice before using these commands or key combinations.
 
@@ -60,10 +60,14 @@ The "highlighted file", or the "current file", is the one below the cursor.
 	yy	copy the selection
 	dd	cut the selection
 
+	ya, da	add the selection to the copied/cut files
+	yr, dr	remove the selection from the copied/cut files
+
 	pp	paste the copied/cut files. No file will be overwritten.
 		Instead, a "_" character will be appended to the new filename.
 	po	paste the copied/cut files. Existing files are overwritten.
 	pl	create symbolic links to the copied/cut files.
+	pL	create relative symbolic links to the copied/cut files.
 
 The difference between copying and cutting should be intuitive:
 
@@ -75,6 +79,9 @@ If renaming is not possible because the source and the destination are
 on separate devices, it will be copied and eventually the source is deleted.
 This implies that a file can only be cut + pasted once.
 
+The files are either copied or cut, never mixed even if you mix "da" and "ya"
+keys (in which case the last command is decisive about whether they are copied
+or cut.)
 
 ==============================================================================
 4.4. Task View
diff --git a/ranger/help/movement.py b/ranger/help/movement.py
index 3abec359..564b226b 100644
--- a/ranger/help/movement.py
+++ b/ranger/help/movement.py
@@ -74,6 +74,9 @@ This keys can be used to make movements beyond the current directory
 	{	traverse in the other direction. (not implemented yet,
 		currently this only moves back in history)
 
+	gl	move to the real path of the current directory (resolving symlinks)
+	gL	move to the real path of the selected file or directory
+
 
 ==============================================================================
 1.2. Browser control
diff --git a/ranger/help/starting.py b/ranger/help/starting.py
index 99cfc45e..1796f83d 100644
--- a/ranger/help/starting.py
+++ b/ranger/help/starting.py
@@ -17,7 +17,7 @@
 2. Running Files
 
 2.1. How to run files
-2.2. The "open with" prompt
+2.2. The "open_with" command
 2.2. Programs
 2.4. Modes
 2.5. Flags
@@ -30,40 +30,47 @@ While highlighting a file, press the "l" key to fire up the automatic
 filetype detection mechanism and attempt to start the file.
 
 	l	run the selection
-	r	open the "open with" prompt
+	r	open the console with ":open_with"
 
 Note: The selection means, if there are marked files in this directory,
 use them.  Otherwise use the file under the cursor.
 
 
 ==============================================================================
-2.2. The "open with" prompt
+2.2. The "open_with" command
 
 If the automatic filetype detection fails or starts the file in a wrong
 way, you can press "r" to manually tell ranger how to run it.
 
-Syntax: open with: <program> <flags> <mode>
+The programs and modes can be defined in the apps.py, giving you a
+high level interface for running files.
+
+Syntax: :open_with <program> <flags> <mode>
+You can leave out parameters or change the order.
 
 Examples:
 Open this file with vim:
-	open with: vim
+	:open_with vim
 Run this file like with "./file":
-	open with: self
+	:open_with self
+Open this file as usual but pipe the output to "less"
+	:open_with p
 Open this file with mplayer with the "detached" flag:
-	open with: mplayer d
+	:open_with mplayer d
+Open this file with totem in mode 1, will not detach the process (flag D)
+but discard the output (flag s).
+	:open_with totem 1 Ds
 
 The parameters <program>, <flags> and <mode> are explained in the
 following paragraphs
 
-Note: The "open with" console is named QuickOpenConsole in the source code.
-
 
 ==============================================================================
 2.3. Programs
 
 Programs have to be defined in ranger/defaults/apps.py.  Each function
 in the class CustomApplications which starts with "app_" can be used
-as a program in the "open with" prompt.
+as a program in the "open_with" command.
 
 You're encouraged to add your own program definitions to the list.  Refer to
 the existing examples in the apps.py, it should be easy to adapt it for your
@@ -81,8 +88,8 @@ gives you 2 ways of opening a video (by default):
 
 By specifying a mode, you can select one of those.  The "l" key will
 start a file in mode 0. "4l" will start the file in mode 4 etc.
-You can specify a mode in the "open with" console by simply adding
-the number.  Eg: "open with: mplayer 1" or "open with: 1"
+You can specify a mode in the "open_with" command by simply adding
+the number.  Eg: ":open_with mplayer 1" or ":open_with 1"
 
 For a list of all programs and modes, see ranger/defaults/apps.py
 
@@ -97,11 +104,11 @@ Flags give you a way to modify the behaviour of the spawned process.
 	p	Redirect output to the pager
 	w	Wait for an enter-press when the process is done
 
-For example, "open with: p" will pipe the output of that process into
+For example, ":open_with p" will pipe the output of that process into
 the pager.
 
 An uppercase flag has the opposite effect.  If a program will be detached by
-default, use "open with: D" to not detach it.
+default, use ":open_with D" to not detach it.
 
 Note: Some combinations don't make sense, eg: "vim d" would open the file in
 vim and detach it.  Since vim is a console application, you loose grip
diff --git a/ranger/shared/mimetype.py b/ranger/shared/mimetype.py
index c6577056..da6fcd10 100644
--- a/ranger/shared/mimetype.py
+++ b/ranger/shared/mimetype.py
@@ -15,9 +15,12 @@
 
 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/ranger/shared/settings.py b/ranger/shared/settings.py
index 24aea39c..41334ada 100644
--- a/ranger/shared/settings.py
+++ b/ranger/shared/settings.py
@@ -20,36 +20,36 @@ from ranger.ext.signal_dispatcher import SignalDispatcher
 from ranger.ext.openstruct import OpenStruct
 
 ALLOWED_SETTINGS = {
-	'show_hidden': bool,
-	'show_hidden_bookmarks': bool,
-	'show_cursor': bool,
 	'autosave_bookmarks': bool,
-	'save_console_history': bool,
 	'collapse_preview': bool,
+	'colorscheme_overlay': (type(None), type(lambda:0)),
+	'colorscheme': str,
 	'column_ratios': (tuple, list, set),
+	'dirname_in_tabs': bool,
 	'display_size_in_main_column': bool,
 	'display_size_in_status_bar': bool,
-	'draw_borders': bool,
 	'draw_bookmark_borders': bool,
-	'dirname_in_tabs': bool,
-	'sort': str,
-	'sort_reverse': bool,
-	'sort_case_insensitive': bool,
-	'sort_directories_first': bool,
-	'update_title': bool,
-	'shorten_title': int,  # Note: False is an instance of int
-	'tilde_in_titlebar': bool,
-	'max_history_size': (int, type(None)),
+	'draw_borders': bool,
+	'flushinput': bool,
+	'hidden_filter': lambda x: isinstance(x, str) or hasattr(x, 'match'),
 	'max_console_history_size': (int, type(None)),
+	'max_history_size': (int, type(None)),
+	'mouse_enabled': bool,
+	'preview_directories': bool,
+	'preview_files': bool,
 	'preview_script': (str, type(None)),
+	'save_console_history': bool,
 	'scroll_offset': int,
-	'preview_files': bool,
-	'preview_directories': bool,
-	'mouse_enabled': bool,
-	'flushinput': bool,
-	'colorscheme': str,
-	'colorscheme_overlay': (type(None), type(lambda:0)),
-	'hidden_filter': lambda x: isinstance(x, str) or hasattr(x, 'match'),
+	'shorten_title': int,  # Note: False is an instance of int
+	'show_cursor': bool,
+	'show_hidden_bookmarks': bool,
+	'show_hidden': bool,
+	'sort_case_insensitive': bool,
+	'sort_directories_first': bool,
+	'sort_reverse': bool,
+	'sort': str,
+	'tilde_in_titlebar': bool,
+	'update_title': bool,
 	'xterm_alt_key': bool,
 }
 
@@ -67,7 +67,9 @@ class SettingObject(SignalDispatcher):
 		if name[0] == '_':
 			self.__dict__[name] = value
 		else:
-			assert name in self._settings, "No such setting: {0}!".format(name)
+			assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name)
+			if name not in self._settings:
+				getattr(self, name)
 			assert self._check_type(name, value)
 			kws = dict(setting=name, value=value,
 					previous=self._settings[name])
diff --git a/setup.py b/setup.py
index 17cfc660..587b52c0 100755
--- a/setup.py
+++ b/setup.py
@@ -17,26 +17,27 @@
 import distutils.core
 import ranger
 
-distutils.core.setup(
-	name='ranger',
-	description='Vim-like file manager',
-	version=ranger.__version__,
-	author=ranger.__author__,
-	author_email=ranger.__email__,
-	license=ranger.__license__,
-	url='http://savannah.nongnu.org/projects/ranger',
-	scripts=['scripts/ranger'],
-	data_files=[('share/man/man1', ['doc/ranger.1'])],
-	package_data={'ranger': ['data/*']},
-	packages=('ranger',
-	          'ranger.api',
-	          'ranger.colorschemes',
-	          'ranger.container',
-	          'ranger.core',
-	          'ranger.defaults',
-	          'ranger.ext',
-	          'ranger.fsobject',
-	          'ranger.gui',
-	          'ranger.gui.widgets',
-	          'ranger.help',
-	          'ranger.shared'))
+if __name__ == '__main__':
+	distutils.core.setup(
+		name='ranger',
+		description='Vim-like file manager',
+		version=ranger.__version__,
+		author=ranger.__author__,
+		author_email=ranger.__email__,
+		license=ranger.__license__,
+		url='http://savannah.nongnu.org/projects/ranger',
+		scripts=['scripts/ranger'],
+		data_files=[('share/man/man1', ['doc/ranger.1'])],
+		package_data={'ranger': ['data/*']},
+		packages=('ranger',
+		          'ranger.api',
+		          'ranger.colorschemes',
+		          'ranger.container',
+		          'ranger.core',
+		          'ranger.defaults',
+		          'ranger.ext',
+		          'ranger.fsobject',
+		          'ranger.gui',
+		          'ranger.gui.widgets',
+		          'ranger.help',
+		          'ranger.shared'))
diff --git a/test/__init__.py b/test/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/test/__init__.py
+++ /dev/null
diff --git a/test/all_benchmarks.py b/test/all_benchmarks.py
index 20f11ad8..a3612701 100755
--- a/test/all_benchmarks.py
+++ b/test/all_benchmarks.py
@@ -19,9 +19,13 @@ Run all the benchmarks inside this directory.
 Usage: ./all_benchmarks.py [count] [regexp-filters...]
 """
 
-import os
-import re
+import os.path
 import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
+import re
 import time
 
 if __name__ == '__main__':
diff --git a/test/all_tests.py b/test/all_tests.py
index 7cfc855f..0c184df5 100755
--- a/test/all_tests.py
+++ b/test/all_tests.py
@@ -19,8 +19,12 @@ Run all the tests inside this directory as a test suite.
 Usage: ./all_tests.py [verbosity]
 """
 
-import os
+import os.path
 import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 
 if __name__ == '__main__':
diff --git a/test/bm_human_readable.py b/test/bm_human_readable.py
new file mode 100644
index 00000000..ef400774
--- /dev/null
+++ b/test/bm_human_readable.py
@@ -0,0 +1,51 @@
+# 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/>.
+
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
+from ranger.ext.human_readable import *
+
+# The version before 2010/06/24:
+import math
+UNITS = 'BKMGTP'
+MAX_EXPONENT = len(UNITS) - 1
+def human_readable_old(byte, seperator=' '):
+	if not byte:
+		return '0'
+
+	exponent = int(math.log(byte, 2) / 10)
+	flt = round(float(byte) / (1 << (10 * exponent)), 2)
+
+	if exponent > MAX_EXPONENT:
+		return '>9000' # off scale
+
+	if int(flt) == flt:
+		return '%.0f%s%s' % (flt, seperator, UNITS[exponent])
+
+	else:
+		return '%.2f%s%s' % (flt, seperator, UNITS[exponent])
+
+class benchmark_human_readable(object):
+	def bm_current(self, n):
+		for i in range(n):
+			human_readable((128 * i) % 2**50)
+
+	def bm_old(self, n):
+		for i in range(n):
+			human_readable_old((128 * i) % 2**50)
diff --git a/test/bm_loader.py b/test/bm_loader.py
index 968640a5..552954a7 100644
--- a/test/bm_loader.py
+++ b/test/bm_loader.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from ranger.core.loader import Loader
 from ranger.fsobject import Directory, File
 from ranger.ext.openstruct import OpenStruct
diff --git a/test/ranger b/test/ranger
deleted file mode 120000
index 5459458c..00000000
--- a/test/ranger
+++ /dev/null
@@ -1 +0,0 @@
-../ranger
\ No newline at end of file
diff --git a/test/tc_bookmarks.py b/test/tc_bookmarks.py
index 9b41f1c6..59435f06 100644
--- a/test/tc_bookmarks.py
+++ b/test/tc_bookmarks.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from os.path import realpath, join, dirname
 import unittest
 import os
diff --git a/test/tc_colorscheme.py b/test/tc_colorscheme.py
index 8d6adee6..eefb1e4f 100644
--- a/test/tc_colorscheme.py
+++ b/test/tc_colorscheme.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from unittest import TestCase, main
 import random
 import ranger.colorschemes
diff --git a/test/tc_direction.py b/test/tc_direction.py
index 5c1776cf..16c26dab 100644
--- a/test/tc_direction.py
+++ b/test/tc_direction.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 from ranger.ext.direction import Direction
 from ranger.ext.openstruct import OpenStruct
diff --git a/test/tc_directory.py b/test/tc_directory.py
index a702c4db..754253b3 100644
--- a/test/tc_directory.py
+++ b/test/tc_directory.py
@@ -13,7 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
 import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from os.path import realpath, join, dirname
 
 from ranger import fsobject
diff --git a/test/tc_displayable.py b/test/tc_displayable.py
index 1c66a40e..72e0507d 100644
--- a/test/tc_displayable.py
+++ b/test/tc_displayable.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 import curses
 from random import randint
diff --git a/test/tc_ext.py b/test/tc_ext.py
index 919f35a2..495591a1 100644
--- a/test/tc_ext.py
+++ b/test/tc_ext.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 from collections import deque
 
diff --git a/test/tc_history.py b/test/tc_history.py
index 33784e14..02a8bb9f 100644
--- a/test/tc_history.py
+++ b/test/tc_history.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from ranger.container import History
 from unittest import TestCase, main
 import unittest
@@ -27,13 +33,13 @@ class Test(TestCase):
 		hist.back()
 
 		self.assertEqual(4, hist.current())
-		self.assertEqual([3,4], list(hist))
+		self.assertEqual([3,4], list(hist._left()))
 
 		self.assertEqual(5, hist.top())
 
 		hist.back()
 		self.assertEqual(3, hist.current())
-		self.assertEqual([3], list(hist))
+		self.assertEqual([3], list(hist._left()))
 
 		# no change if current == bottom
 		self.assertEqual(hist.current(), hist.bottom())
@@ -46,12 +52,31 @@ class Test(TestCase):
 		hist.forward()
 		hist.forward()
 		self.assertEqual(5, hist.current())
-		self.assertEqual([3,4,5], list(hist))
+		self.assertEqual([3,4,5], list(hist._left()))
 
 
 		self.assertEqual(3, hist.bottom())
 		hist.add(6)
 		self.assertEqual(4, hist.bottom())
-		self.assertEqual([4,5,6], list(hist))
+		self.assertEqual([4,5,6], list(hist._left()))
+
+		hist.back()
+		hist.fast_forward()
+		self.assertEqual([4,5,6], list(hist._left()))
+		hist.back()
+		hist.back()
+		hist.fast_forward()
+		self.assertEqual([4,5,6], list(hist._left()))
+		hist.back()
+		hist.back()
+		hist.back()
+		hist.fast_forward()
+		self.assertEqual([4,5,6], list(hist._left()))
+		hist.back()
+		hist.back()
+		hist.back()
+		hist.back()
+		hist.fast_forward()
+		self.assertEqual([4,5,6], list(hist._left()))
 
 if __name__ == '__main__': main()
diff --git a/test/tc_human_readable.py b/test/tc_human_readable.py
new file mode 100644
index 00000000..493e6d3a
--- /dev/null
+++ b/test/tc_human_readable.py
@@ -0,0 +1,51 @@
+# 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/>.
+
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
+import unittest
+from ranger.ext.human_readable import human_readable as hr
+
+class HumanReadableTest(unittest.TestCase):
+	def test_basic(self):
+		self.assertEqual("0", hr(0))
+		self.assertEqual("1 B", hr(1))
+		self.assertEqual("1 K", hr(2 ** 10))
+		self.assertEqual("1 M", hr(2 ** 20))
+		self.assertEqual("1 G", hr(2 ** 30))
+		self.assertEqual(">9000", hr(2 ** 100))
+
+	def test_big(self):
+		self.assertEqual("1023 G", hr(2 ** 30 * 1023))
+		self.assertEqual("1024 G", hr(2 ** 40 - 1))
+		self.assertEqual("1 T",    hr(2 ** 40))
+
+	def test_small(self):
+		self.assertEqual("1000 B", hr(1000))
+		self.assertEqual("1.66 M", hr(1.66 * 2 ** 20))
+		self.assertEqual("1.46 K", hr(1500))
+		self.assertEqual("1.5 K",  hr(2 ** 10 + 2 ** 9))
+		self.assertEqual("1.5 K",  hr(2 ** 10 + 2 ** 9 - 1))
+
+	def test_no_exponent(self):
+		for i in range(2 ** 10, 2 ** 20, 512):
+			self.assertTrue('e' not in hr(i), "%d => %s" % (i, hr(i)))
+
+if __name__ == '__main__':
+	unittest.main()
diff --git a/test/tc_keyapi.py b/test/tc_keyapi.py
index 48282a7d..79d89fa5 100644
--- a/test/tc_keyapi.py
+++ b/test/tc_keyapi.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 from unittest import TestCase, main
 
 class Test(TestCase):
diff --git a/test/tc_loader.py b/test/tc_loader.py
index 9f7f7322..5a2e5a68 100644
--- a/test/tc_loader.py
+++ b/test/tc_loader.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 import os
 from os.path import realpath, join, dirname
diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py
index fd856f17..c9597201 100644
--- a/test/tc_newkeys.py
+++ b/test/tc_newkeys.py
@@ -12,7 +12,13 @@
 # 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/>.
+
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+sys.path[1:1] = ['..']
 
 from unittest import TestCase, main
 
@@ -22,8 +28,6 @@ from ranger.container.keymap import *
 from ranger.container.keybuffer import KeyBuffer
 from ranger.ext.keybinding_parser import parse_keybinding
 
-import sys
-
 def simulate_press(self, string):
 	for char in parse_keybinding(string):
 		self.add(char)
diff --git a/test/tc_relative_symlink.py b/test/tc_relative_symlink.py
new file mode 100644
index 00000000..a202513d
--- /dev/null
+++ b/test/tc_relative_symlink.py
@@ -0,0 +1,47 @@
+# 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/>.
+
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
+import unittest
+from ranger.ext.relative_symlink import *
+rel = get_relative_source_file
+
+class Test(unittest.TestCase):
+	def test_foo(self):
+		self.assertEqual('../foo', rel('/foo', '/x/bar'))
+		self.assertEqual('../../foo', rel('/foo', '/x/y/bar'))
+		self.assertEqual('../../a/b/foo', rel('/a/b/foo', '/x/y/bar'))
+		self.assertEqual('../../x/b/foo', rel('/x/b/foo', '/x/y/bar',
+			common_base='/'))
+		self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar'))
+		self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar'))
+
+	def test_get_common_base(self):
+		self.assertEqual('/', get_common_base('', ''))
+		self.assertEqual('/', get_common_base('', '/'))
+		self.assertEqual('/', get_common_base('/', ''))
+		self.assertEqual('/', get_common_base('/', '/'))
+		self.assertEqual('/', get_common_base('/bla/bar/x', '/foo/bar/a'))
+		self.assertEqual('/foo/bar/', get_common_base('/foo/bar/x', '/foo/bar/a'))
+		self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a'))
+		self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a'))
+		self.assertEqual('/', get_common_base('//foo/bar/x', '/foo/baz/a'))
+
+if __name__ == '__main__': unittest.main()
diff --git a/test/tc_signal.py b/test/tc_signal.py
index f31681f4..3b1bac40 100644
--- a/test/tc_signal.py
+++ b/test/tc_signal.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 import gc
 from ranger.ext.signal_dispatcher import *
diff --git a/test/tc_ui.py b/test/tc_ui.py
index dc8d669d..fa2bdcac 100644
--- a/test/tc_ui.py
+++ b/test/tc_ui.py
@@ -13,6 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
 import unittest
 import curses
 
diff --git a/test/tc_utfwidth.py b/test/tc_utfwidth.py
index 2aa5fa6d..0288c17b 100644
--- a/test/tc_utfwidth.py
+++ b/test/tc_utfwidth.py
@@ -12,7 +12,13 @@
 # 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/>.
+
+import os.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+sys.path[1:1] = ['..']
 
 from unittest import TestCase, main
 from ranger.ext.utfwidth import *