about summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2010-09-16 18:19:46 +0200
committerhut <hut@lavabit.com>2010-09-16 18:19:46 +0200
commit4341846741b6623a1ead43485cfd5cf633453bb6 (patch)
treefdfdd0b13329ad457ad9a6a0c0091019396ae64a /ranger
parent37a60686b340f030a2fc37e7ac9d19a701de9e6b (diff)
parent0c0849c3d8bf57a8b0d0bd9d6113639c58a28fd2 (diff)
downloadranger-4341846741b6623a1ead43485cfd5cf633453bb6.tar.gz
Merge branch 'master' into cp
Conflicts:
	ranger/__main__.py
	ranger/core/actions.py
Diffstat (limited to 'ranger')
-rw-r--r--ranger/__init__.py7
-rw-r--r--ranger/__main__.py108
-rw-r--r--ranger/api/apps.py3
-rw-r--r--ranger/api/commands.py4
-rw-r--r--ranger/api/keys.py1
-rw-r--r--ranger/colorschemes/default88.py2
-rw-r--r--ranger/colorschemes/jungle.py1
-rw-r--r--ranger/colorschemes/texas.py2
-rw-r--r--ranger/container/bookmarks.py5
-rw-r--r--ranger/container/history.py114
-rw-r--r--ranger/core/actions.py154
-rw-r--r--ranger/core/environment.py8
-rw-r--r--ranger/core/runner.py19
-rw-r--r--ranger/defaults/apps.py51
-rw-r--r--ranger/defaults/commands.py190
-rw-r--r--ranger/defaults/keys.py70
-rw-r--r--ranger/defaults/options.py6
-rw-r--r--ranger/ext/direction.py2
-rw-r--r--ranger/ext/human_readable.py53
-rw-r--r--ranger/ext/lazy_property.py25
-rw-r--r--ranger/ext/relative_symlink.py39
-rw-r--r--ranger/ext/shell_escape.py2
-rw-r--r--ranger/ext/utfwidth.py109
-rw-r--r--ranger/fsobject/directory.py54
-rw-r--r--ranger/fsobject/file.py58
-rw-r--r--ranger/fsobject/fsobject.py135
-rw-r--r--ranger/gui/bar.py4
-rw-r--r--ranger/gui/colorscheme.py6
-rw-r--r--ranger/gui/curses_shortcuts.py37
-rw-r--r--ranger/gui/defaultui.py4
-rw-r--r--ranger/gui/ui.py6
-rw-r--r--ranger/gui/widgets/browsercolumn.py107
-rw-r--r--ranger/gui/widgets/browserview.py13
-rw-r--r--ranger/gui/widgets/console.py476
-rw-r--r--ranger/gui/widgets/console_mode.py55
-rw-r--r--ranger/gui/widgets/statusbar.py16
-rw-r--r--ranger/gui/widgets/taskview.py3
-rw-r--r--ranger/gui/widgets/titlebar.py2
-rw-r--r--ranger/help/console.py188
-rw-r--r--ranger/help/fileop.py9
-rw-r--r--ranger/help/index.py4
-rw-r--r--ranger/help/invocation.py9
-rw-r--r--ranger/help/movement.py8
-rw-r--r--ranger/help/starting.py36
-rw-r--r--ranger/shared/mimetype.py3
-rw-r--r--ranger/shared/settings.py44
46 files changed, 1195 insertions, 1057 deletions
diff --git a/ranger/__init__.py b/ranger/__init__.py
index f46a1e76..1f8cc324 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.0.4'
+__version__ = '1.3.0'
 __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 c232b489..a9a18537 100644
--- a/ranger/__main__.py
+++ b/ranger/__main__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 # coding=utf-8
 #
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
@@ -21,7 +21,8 @@
 # (ImportError will imply that this module can't be found)
 # convenient exception handling in ranger.py (ImportError)
 
-import os
+import locale
+import os.path
 import sys
 
 def parse_arguments():
@@ -29,13 +30,25 @@ 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")
 	parser.add_option('-c', '--clean', action='store_true',
 			help="don't touch/require any config files. ")
-	parser.add_option('--fail-if-run', action='store_true',
+	parser.add_option('--fail-if-run', action='store_true', # COMPAT
+			help=SUPPRESS_HELP)
+	parser.add_option('--fail-unless-cd', action='store_true',
 			help="experimental: return the exit code 1 if ranger is" \
 					"used to run a file (with `ranger filename`)")
 	parser.add_option('-r', '--confdir', type='string',
@@ -50,6 +63,9 @@ def parse_arguments():
 	options, positional = parser.parse_args()
 	arg = OpenStruct(options.__dict__, targets=positional)
 	arg.confdir = os.path.expanduser(arg.confdir)
+	if arg.fail_if_run:
+		arg.fail_unless_cd = arg.fail_if_run
+		del arg['fail_if_run']
 
 	return arg
 
@@ -107,7 +123,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)
@@ -147,7 +163,18 @@ def main():
 		print(errormessage)
 		print('ranger requires the python curses module. Aborting.')
 		sys.exit(1)
-	from locale import getdefaultlocale, setlocale, LC_ALL
+
+	try: locale.setlocale(locale.LC_ALL, '')
+	except: print("Warning: Unable to set locale.  Expect encoding problems.")
+
+	if not 'SHELL' in os.environ:
+		os.environ['SHELL'] = 'bash'
+
+	arg = parse_arguments()
+	if arg.clean:
+		sys.dont_write_bytecode = True
+
+	# Need to decide whether to write bytecode or not before importing.
 	import ranger
 	from ranger.ext import curses_interrupt_handler
 	from ranger.core.runner import Runner
@@ -158,28 +185,15 @@ def main():
 	from ranger.shared import (EnvironmentAware, FileManagerAware,
 			SettingsAware)
 
-	# Ensure that a utf8 locale is set.
-	try:
-		if getdefaultlocale()[1] not in ('utf8', 'UTF-8'):
-			for locale in ('en_US.utf8', 'en_US.UTF-8'):
-				try: setlocale(LC_ALL, locale)
-				except: pass
-				else: break
-			else: setlocale(LC_ALL, '')
-		else: setlocale(LC_ALL, '')
-	except:
-		print("Warning: Unable to set locale.  Expect encoding problems.")
-
-	arg = parse_arguments()
-	ranger.arg = arg
-
-	if not ranger.arg.debug:
+	if not arg.debug:
 		curses_interrupt_handler.install_interrupt_handler()
+	ranger.arg = arg
 
 	SettingsAware._setup()
 
+	targets = arg.targets or ['.']
+	target = targets[0]
 	if arg.targets:
-		target = arg.targets[0]
 		if target.startswith('file://'):
 			target = target[7:]
 		if not os.access(target, os.F_OK):
@@ -191,18 +205,18 @@ def main():
 			runner = Runner(logfunc=print_function)
 			load_apps(runner, ranger.arg.clean)
 			runner(files=[File(target)], mode=arg.mode, flags=arg.flags)
-			sys.exit(1 if arg.fail_if_run else 0)
-		else:
-			path = target
-	else:
-		path = '.'
+			sys.exit(1 if arg.fail_unless_cd else 0)
 
-	# Initialize objects
-	EnvironmentAware._assign(Environment(path))
-	fm = FM()
-	crash_exception = None
+	crash_traceback = None
 	try:
+		# Initialize objects
+		EnvironmentAware._assign(Environment(target))
+		fm = FM()
+		fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \
+				in enumerate(targets[:9]))
 		load_settings(fm, ranger.arg.clean)
+		if fm.env.username == 'root':
+			fm.settings.preview_files = False
 		FileManagerAware._assign(fm)
 		fm.ui = UI()
 
@@ -210,21 +224,23 @@ def main():
 		fm.initialize()
 		fm.ui.initialize()
 		fm.loop()
-	except Exception as e:
-		crash_exception = e
-		if not (arg.debug or arg.clean):
-			import traceback
-			dumpname = ranger.relpath_conf('traceback')
-			traceback.print_exc(file=open(dumpname, 'w'))
+	except Exception:
+		import traceback
+		crash_traceback = traceback.format_exc()
+	except SystemExit as error:
+		return error.args[0]
 	finally:
-		fm.destroy()
-		if crash_exception:
-			print("Fatal: " + str(crash_exception))
-			if arg.debug or arg.clean:
-				raise crash_exception
-			else:
-				print("A traceback has been saved to " + dumpname)
-				print("Please include it in a bugreport.")
+		try:
+			fm.destroy()
+		except (AttributeError, NameError):
+			pass
+		if crash_traceback:
+			print(crash_traceback)
+			print("Ranger crashed.  " \
+					"Please report this (including the traceback) at:")
+			print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem")
+			return 1
+		return 0
 
 
 if __name__ == '__main__':
diff --git a/ranger/api/apps.py b/ranger/api/apps.py
index d2e9ac4f..91aae357 100644
--- a/ranger/api/apps.py
+++ b/ranger/api/apps.py
@@ -121,7 +121,8 @@ class Applications(FileManagerAware):
 		flags = 'flags' in keywords and keywords['flags'] or ""
 		for name in args:
 			assert isinstance(name, str)
-			setattr(cls, "app_" + name, _generic_wrapper(name, flags=flags))
+			if not hasattr(cls, "app_" + name):
+				setattr(cls, "app_" + name, _generic_wrapper(name, flags=flags))
 
 
 def tup(*args):
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/colorschemes/default88.py b/ranger/colorschemes/default88.py
index 9af6dca7..9c00d9ff 100644
--- a/ranger/colorschemes/default88.py
+++ b/ranger/colorschemes/default88.py
@@ -20,9 +20,7 @@ For now, just map each of the 8 base colors to new ones
 for brighter blue, etc. and do some minor modifications.
 """
 
-from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import *
-
 from ranger.colorschemes.default import Default
 import curses
 
diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py
index 376091c0..af10a404 100644
--- a/ranger/colorschemes/jungle.py
+++ b/ranger/colorschemes/jungle.py
@@ -13,7 +13,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import *
 from ranger.colorschemes.default import Default
 
diff --git a/ranger/colorschemes/texas.py b/ranger/colorschemes/texas.py
index 93fd4791..26e8916d 100644
--- a/ranger/colorschemes/texas.py
+++ b/ranger/colorschemes/texas.py
@@ -17,9 +17,7 @@
 Some experimental colorscheme.
 """
 
-from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import *
-
 from ranger.colorschemes.default import Default
 import curses
 
diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py
index f06b7498..1e801638 100644
--- a/ranger/container/bookmarks.py
+++ b/ranger/container/bookmarks.py
@@ -156,7 +156,10 @@ class Bookmarks(object):
 			for key, value in self.dct.items():
 				if type(key) == str\
 						and key in ALLOWED_KEYS:
-					f.write("{0}:{1}\n".format(str(key), str(value)))
+					try:
+						f.write("{0}:{1}\n".format(str(key), str(value)))
+					except:
+						pass
 
 			f.close()
 		self._update_mtime()
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 16978591..1493d84d 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -16,18 +16,25 @@
 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.core.loader import CommandLoader
 
+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
@@ -68,10 +75,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.
@@ -127,7 +203,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(),
@@ -138,9 +214,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				cwd.move(to=newpos)
 
 	def move_parent(self, n):
-		self.enter_dir('..')
-		self.move(down=n)
-		self.move(right=0)
+		parent = self.env.at_level(-1)
+		try:
+			self.env.enter_dir(parent.files[parent.pointer+n])
+		except IndexError:
+			pass
 
 	def history_go(self, relative):
 		"""Move back and forth in the history"""
@@ -272,13 +350,26 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if hasattr(self.ui, 'status'):
 			self.ui.status.need_redraw = True
 
+	def mark_in_direction(self, val=True, dirarg=None):
+		cwd = self.env.cwd
+		direction = Direction(dirarg)
+		pos, selected = direction.select(lst=cwd.files, current=cwd.pointer,
+				pagesize=self.env.termsize[0])
+		cwd.pointer = pos
+		cwd.correct_pointer()
+		for item in selected:
+			cwd.mark_item(item, val)
+
 	# --------------------------
 	# -- Searching
 	# --------------------------
 
 	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')
 
@@ -332,7 +423,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):
@@ -433,15 +524,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'):
@@ -523,8 +628,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)
@@ -538,22 +644,30 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			pos, selected = direction.select(
 					override=narg, lst=cwd.files, current=cwd.pointer,
 					pagesize=self.env.termsize[0], offset=offset)
-			self.env.cwd.pointer = pos
-			self.env.cwd.correct_pointer()
-		self.env.copy = set(selected)
+			cwd.pointer = pos
+			cwd.correct_pointer()
+		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)
 
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index bb6abb36..61db8694 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -28,8 +28,8 @@ ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview',
 		'console')
 
 class Environment(SettingsAware, SignalDispatcher):
-	"""A collection of data which is relevant for more than
-	one class.
+	"""
+	A collection of data which is relevant for more than one class.
 	"""
 
 	cwd = None  # current directory
@@ -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/core/runner.py b/ranger/core/runner.py
index 26424881..f35f99b7 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -37,10 +37,21 @@ from subprocess import Popen, PIPE
 from ranger.ext.waitpid_no_intr import waitpid_no_intr
 
 
-ALLOWED_FLAGS = 'sdpSDP'
+ALLOWED_FLAGS = 'sdpwSDPW'
 devnull = open(os.devnull, 'a')
 
 
+def press_enter():
+	"""Wait for an ENTER-press"""
+	sys.stdout.write("Press ENTER to continue")
+	try:
+		waitfnc = raw_input
+	except NameError:
+		# "raw_input" not available in python3
+		waitfnc = input
+	waitfnc()
+
+
 class Context(object):
 	"""
 	A context object contains data on how to run a process.
@@ -144,6 +155,7 @@ class Runner(object):
 
 		toggle_ui = True
 		pipe_output = False
+		wait_for_enter = False
 
 		popen_kws['args'] = action
 		if 'shell' not in popen_kws:
@@ -168,6 +180,9 @@ class Runner(object):
 		if 'd' in context.flags:
 			toggle_ui = False
 			context.wait = False
+		if 'w' in context.flags:
+			if not pipe_output and context.wait: # <-- sanity check
+				wait_for_enter = True
 
 		# Finally, run it
 
@@ -182,6 +197,8 @@ class Runner(object):
 			else:
 				if context.wait:
 					waitpid_no_intr(process.pid)
+				if wait_for_enter:
+					press_enter()
 		finally:
 			if toggle_ui:
 				self._activate_ui(True)
diff --git a/ranger/defaults/apps.py b/ranger/defaults/apps.py
index 4dd3bde5..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 *
 			
@@ -55,29 +55,29 @@ class CustomApplications(Applications):
 		f = c.file
 
 		if f.basename.lower() == 'makefile':
-			return self.app_make(c)
+			return self.either(c, 'make')
 
 		if f.extension is not None:
 			if f.extension in ('pdf', ):
 				c.flags += 'd'
 				return self.either(c, 'evince', 'zathura', 'apvlv')
 			if f.extension in ('xml', ):
-				return self.app_editor(c)
+				return self.either(c, 'editor')
 			if f.extension in ('html', 'htm', 'xhtml'):
 				return self.either(c, 'firefox', 'opera', 'elinks')
 			if f.extension in ('swf', ):
 				return self.either(c, 'firefox', 'opera')
 			if f.extension == 'nes':
-				return self.app_fceux(c)
+				return self.either(c, 'fceux')
 			if f.extension in ('swc', 'smc'):
-				return self.app_zsnes(c)
+				return self.either(c, 'zsnes')
 
 		if f.mimetype is not None:
 			if INTERPRETED_LANGUAGES.match(f.mimetype):
-				return self.app_edit_or_run(c)
+				return self.either(c, 'edit_or_run')
 
 		if f.container:
-			return self.app_aunpack(c)
+			return self.either(c, 'aunpack', 'file_roller')
 
 		if f.video or f.audio:
 			if f.video:
@@ -85,16 +85,16 @@ class CustomApplications(Applications):
 			return self.either(c, 'mplayer', 'totem')
 
 		if f.image:
-			return self.either(c, 'feh', 'eye_of_gnome', 'mirage')
+			return self.either(c, 'feh', 'eog', 'mirage')
 
 		if f.document or f.filetype.startswith('text'):
-			return self.app_editor(c)
+			return self.either(c, 'editor')
 
 
 	# ----------------------------------------- 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:
@@ -109,7 +109,6 @@ class CustomApplications(Applications):
 
 		return self.either(c, 'vim', 'emacs', 'nano')
 
-	@depends_on(app_editor, Applications.app_self)
 	def app_edit_or_run(self, c):
 		if c.mode is 1:
 			return self.app_self(c)
@@ -138,25 +137,12 @@ class CustomApplications(Applications):
 
 		c.flags += 'd'
 
-		if c.mode in arg:
+		if c.mode in arg: # mode 1, 2 and 3 will set the image as the background
 			return tup('feh', arg[c.mode], c.file.path)
-		if c.mode is 4:
-			return self.app_gimp(c)
-		if len(c.files) > 1:
-			return tup('feh', *c)
-
-		try:
-			from collections import deque
-
-			directory = self.fm.env.get_directory(c.file.dirname)
-			images = [f.path for f in directory.files if f.image]
-			position = images.index(c.file.path)
-			deq = deque(images)
-			deq.rotate(-position)
-
-			return tup('feh', *deq)
-		except:
-			return tup('feh', *c)
+		if c.mode is 11 and len(c.files) is 1: # view all files in the cwd
+			images = (f.basename for f in self.fm.env.cwd.files if f.image)
+			return tup('feh', '--start-at', c.file.basename, *images)
+		return tup('feh', *c)
 
 	@depends_on('aunpack')
 	def app_aunpack(self, c):
@@ -165,6 +151,11 @@ class CustomApplications(Applications):
 			return tup('aunpack', '-l', c.file.path)
 		return tup('aunpack', c.file.path)
 
+	@depends_on('file-roller')
+	def app_file_roller(self, c):
+		c.flags += 'd'
+		return tup('file-roller', c.file.path)
+
 	@depends_on('make')
 	def app_make(self, c):
 		if c.mode is 0:
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 979d77e5..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())
 
 # ===================================================================
@@ -163,15 +163,22 @@ map('T', fm.tag_remove())
 map(' ', fm.mark(toggle=True))
 map('v', fm.mark(all=True, toggle=True))
 map('V', 'uv', fm.mark(all=True, val=False))
+map('<C-V><dir>', fm.mark_in_direction(val=True))
+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('p<bg>', fm.hint('press *p* once again to confirm pasting' \
-		', or *l* to create symlinks'))
+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*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())
@@ -179,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'))
@@ -192,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" \
@@ -218,31 +225,33 @@ 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('/'))
 map('gm', fm.cd('/media'))
-map('gn', fm.cd('/mnt'))
+map('gM', fm.cd('/mnt'))
 map('gs', fm.cd('/srv'))
 map('gR', fm.cd(RANGERDIR))
 
@@ -259,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))
@@ -298,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 '))
 
 
 # ===================================================================
@@ -313,8 +321,8 @@ map.merge(global_keys)
 map.merge(vim_aliases)
 
 # -------------------------------------------------------- movement
-map('<left>', wdg.move(left=4))
-map('<right>', wdg.move(right=4))
+map('<left>', 'h', wdg.move(left=4))
+map('<right>', 'l', wdg.move(right=4))
 map('<C-D>', 'd', wdg.move(down=0.5, pages=True))
 map('<C-U>', 'u', wdg.move(up=0.5, pages=True))
 map('<C-F>', 'f', '<pagedown>', wdg.move(down=1, pages=True))
@@ -363,16 +371,16 @@ map.merge(readline_aliases)
 
 map('<up>', '<C-P>', wdg.history_move(-1))
 map('<down>', '<C-N>', wdg.history_move(1))
-map('<home>', wdg.move(right=0, absolute=True))
-map('<end>', wdg.move(right=-1, absolute=True))
+map('<home>', '<C-A>', wdg.move(right=0, absolute=True))
+map('<end>', '<C-E>', wdg.move(right=-1, absolute=True))
 map('<tab>', wdg.tab())
 map('<s-tab>', wdg.tab(-1))
 map('<C-C>', '<C-D>', '<ESC>', wdg.close())
 map('<CR>', '<c-j>', wdg.execute())
 map('<F1>', lambda arg: arg.fm.display_command_help(arg.wdg))
 
-map('<backspace>', wdg.delete(-1))
-map('<delete>', wdg.delete(0))
+map('<backspace>', '<C-H>', wdg.delete(-1))
+map('<delete>', '<C-D>', wdg.delete(0))
 map('<C-W>', wdg.delete_word())
 map('<C-K>', wdg.delete_rest(1))
 map('<C-U>', wdg.delete_rest(-1))
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index d93d7685..126d4bad 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 *
@@ -36,7 +36,7 @@ from ranger.api.options import *
 # Which files should be hidden?  Toggle this by typing `zh' or
 # changing the setting `show_hidden'
 hidden_filter = regexp(
-	r'lost\+found|^\.|~$|\.(:?pyc|pyo|bak|swp)$')
+	r'^\.|\.(?:pyc|pyo|bak|swp)$|~$|lost\+found')
 show_hidden = False
 
 # Show dotfiles in the bookmark preview box?
@@ -85,7 +85,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/direction.py b/ranger/ext/direction.py
index b9fbcac9..f36e22a6 100644
--- a/ranger/ext/direction.py
+++ b/ranger/ext/direction.py
@@ -134,7 +134,7 @@ class Direction(dict):
 			pos += current
 		return int(max(min(pos, maximum + offset - 1), minimum))
 
-	def select(self, lst, override, current, pagesize, offset=1):
+	def select(self, lst, current, pagesize, override=None, offset=1):
 		dest = self.move(direction=self.down(), override=override,
 			current=current, pagesize=pagesize, minimum=0, maximum=len(lst))
 		selection = lst[min(current, dest):max(current, dest) + offset]
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/lazy_property.py b/ranger/ext/lazy_property.py
new file mode 100644
index 00000000..320a1993
--- /dev/null
+++ b/ranger/ext/lazy_property.py
@@ -0,0 +1,25 @@
+# From http://blog.pythonisito.com/2008/08/lazy-descriptors.html
+
+class lazy_property(object):
+	"""
+	A @property-like decorator with lazy evaluation
+
+	Example:
+	class Foo:
+		@lazy_property
+		def bar(self):
+			result = [...]
+			return result
+	"""
+
+	def __init__(self, method):
+		self._method = method
+		self.__name__ = method.__name__
+		self.__doc__ = method.__doc__
+
+	def __get__(self, obj, cls=None):
+		if obj is None:  # to fix issues with pydoc
+			return None
+		result = self._method(obj)
+		obj.__dict__[self.__name__] = result
+		return result
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/ext/shell_escape.py b/ranger/ext/shell_escape.py
index f8393f2a..ec9e4b12 100644
--- a/ranger/ext/shell_escape.py
+++ b/ranger/ext/shell_escape.py
@@ -14,7 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-A function to escape metacharacters of arguments for shell commands.
+Functions to escape metacharacters of arguments for shell commands.
 """
 
 META_CHARS = (' ', "'", '"', '`', '&', '|', ';',
diff --git a/ranger/ext/utfwidth.py b/ranger/ext/utfwidth.py
new file mode 100644
index 00000000..a506c676
--- /dev/null
+++ b/ranger/ext/utfwidth.py
@@ -0,0 +1,109 @@
+# -*- encoding: utf8 -*-
+# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2004, 2005  Timo Hirvonen
+#
+# 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/>.
+#
+# ----
+# This file contains portions of code from cmus (uchar.c).
+
+NARROW = 1
+WIDE = 2
+
+def uwid(string):
+	"""Return the width of a string"""
+	end = len(string)
+	i = 0
+	width = 0
+	while i < end:
+		bytelen = utf_byte_length(string[i:])
+		width += utf_char_width(string[i:i+bytelen])
+		i += bytelen
+	return width
+
+def uchars(string):
+	"""Return a list with one string for each character"""
+	end = len(string)
+	i = 0
+	result = []
+	while i < end:
+		bytelen = utf_byte_length(string[i:])
+		result.append(string[i:i+bytelen])
+		i += bytelen
+	return result
+
+def utf_byte_length(string):
+	"""Return the byte length of one utf character"""
+	firstord = ord(string[0])
+	if firstord < 0b01111111:
+		return 1
+	if firstord < 0b10111111:
+		return 1  # invalid
+	if firstord < 0b11011111:
+		return 2
+	if firstord < 0b11101111:
+		return 3
+	if firstord < 0b11110100:
+		return 4
+	return 1  # invalid
+
+def utf_char_width(string):
+	"""Return the width of a single character"""
+	u = _utf_char_to_int(string)
+	if u < 0x1100:
+		return NARROW
+	# Hangul Jamo init. constonants
+	if u <= 0x115F:
+		return WIDE
+	# Angle Brackets
+	if u == 0x2329 or u == 0x232A:
+		return WIDE
+	if u < 0x2e80:
+		return NARROW
+	# CJK ... Yi
+	if u < 0x302A:
+		return WIDE
+	if u <= 0x302F:
+		return NARROW
+	if u == 0x303F or u == 0x3099 or u == 0x309a:
+		return NARROW
+	# CJK ... Yi
+	if u <= 0xA4CF:
+		return WIDE
+	# Hangul Syllables
+	if u >= 0xAC00 and u <= 0xD7A3:
+		return WIDE
+	# CJK Compatibility Ideographs
+	if u >= 0xF900 and u <= 0xFAFF:
+		return WIDE
+	# CJK Compatibility Forms
+	if u >= 0xFE30 and u <= 0xFE6F:
+		return WIDE
+	# Fullwidth Forms
+	if u >= 0xFF00 and u <= 0xFF60 or u >= 0xFFE0 and u <= 0xFFE6:
+		return WIDE
+	# CJK Extra Stuff
+	if u >= 0x20000 and u <= 0x2FFFD:
+		return WIDE
+	# ?
+	if u >= 0x30000 and u <= 0x3FFFD:
+		return WIDE
+	return NARROW  # invalid (?)
+
+def _utf_char_to_int(string):
+	# Squash the last 6 bits of each byte together to an integer
+	u = 0
+	for c in string:
+		u = (u << 6) | (ord(c) & 0b00111111)
+	return u
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index bab650c6..2ac56120 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -16,6 +16,7 @@
 import os.path
 import stat
 from stat import S_ISLNK, S_ISDIR
+from os import stat as os_stat, lstat as os_lstat
 from os.path import join, isdir, basename
 from collections import deque
 from time import time
@@ -63,7 +64,6 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 	filter = None
 	marked_items = None
 	scroll_begin = 0
-	scroll_offset = 0
 
 	mount_path = '/'
 	disk_usage = 0
@@ -98,6 +98,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 		for opt in ('hidden_filter', 'show_hidden'):
 			self.settings.signal_bind('setopt.' + opt,
 				self.request_reload, weak=True)
+		self.use()
 
 	def request_resort(self):
 		self.order_outdated = True
@@ -165,49 +166,53 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 		self.load_if_outdated()
 
 		try:
-			if self.exists and self.runnable:
+			if self.runnable:
 				yield
-				self.mount_path = mount_path(self.path)
+				mypath = self.path
+
+				self.mount_path = mount_path(mypath)
 
 				hidden_filter = not self.settings.show_hidden \
 						and self.settings.hidden_filter
-				filenames = [join(self.path, fname) \
-						for fname in os.listdir(self.path) \
+				filenames = [mypath + (mypath == '/' and fname or '/' + fname)\
+						for fname in os.listdir(mypath) \
 						if accept_file(fname, hidden_filter, self.filter)]
 				yield
 
-				self.load_content_mtime = os.stat(self.path).st_mtime
+				self.load_content_mtime = os.stat(mypath).st_mtime
 
 				marked_paths = [obj.path for obj in self.marked_items]
 
 				files = []
+				disk_usage = 0
 				for name in filenames:
 					try:
-						file_lstat = os.lstat(name)
-						if S_ISLNK(file_lstat.st_mode):
-							file_stat = os.stat(name)
+						file_lstat = os_lstat(name)
+						if file_lstat.st_mode & 0o170000 == 0o120000:
+							file_stat = os_stat(name)
 						else:
 							file_stat = file_lstat
 						stats = (file_stat, file_lstat)
-						is_a_dir = S_ISDIR(file_stat.st_mode)
+						is_a_dir = file_stat.st_mode & 0o170000 == 0o040000
 					except:
 						stats = None
 						is_a_dir = False
 					if is_a_dir:
 						try:
 							item = self.fm.env.get_directory(name)
+							item.load_if_outdated()
 						except:
 							item = Directory(name, preload=stats,
 									path_is_abs=True)
+							item.load()
 					else:
 						item = File(name, preload=stats, path_is_abs=True)
-					item.load_if_outdated()
+						item.load()
+						disk_usage += item.size
 					files.append(item)
 					yield
+				self.disk_usage = disk_usage
 
-				self.disk_usage = sum(f.size for f in files if f.is_file)
-
-				self.scroll_offset = 0
 				self.filenames = filenames
 				self.files = files
 
@@ -221,7 +226,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 
 				self.sort()
 
-				if len(self.files) > 0:
+				if files:
 					if self.pointed_obj is not None:
 						self.sync_index()
 					else:
@@ -401,6 +406,25 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 			return True
 		return False
 
+	def get_description(self):
+		return "Loading " + str(self)
+
+	def use(self):
+		"""mark the filesystem-object as used at the current time"""
+		self.last_used = time()
+
+	def is_older_than(self, seconds):
+		"""returns whether this object wasn't use()d in the last n seconds"""
+		if seconds < 0:
+			return True
+		return self.last_used + seconds < time()
+
+	def go(self, history=True):
+		"""enter the directory if the filemanager is running"""
+		if self.fm:
+			return self.fm.enter_dir(self.path, history=history)
+		return False
+
 	def empty(self):
 		"""Is the directory empty?"""
 		return self.files is None or len(self.files) == 0
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 4618df33..5e79c2d1 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -13,11 +13,45 @@
 # 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 re
+import zipfile
+from ranger.fsobject import FileSystemObject
+
 N_FIRST_BYTES = 20
 control_characters = set(chr(n) for n in
 		set(range(0, 9)) | set(range(14, 32)))
 
-from ranger.fsobject import FileSystemObject
+# Don't even try to preview files which mach this regular expression:
+PREVIEW_BLACKLIST = re.compile(r"""
+		# look at the extension:
+		\.(
+			# one character extensions:
+				[oa]
+			# media formats:
+				| avi | [mj]pe?g | mp\d | og[gmv] | wm[av] | mkv | flv
+				| png | bmp | vob | wav | mpc | flac | divx? | xcf | pdf
+			# binary files:
+				| torrent | class | so | img | py[co] | dmg
+			# containers:
+				| iso | rar | 7z | tar | gz | bz2 | tgz
+		)
+		# ignore filetype-independent suffixes:
+			(\.part|\.bak|~)?
+		# ignore fully numerical file extensions:
+			(\.\d+)*?
+		$
+""", re.VERBOSE | re.IGNORECASE)
+
+# Preview these files (almost) always:
+PREVIEW_WHITELIST = re.compile(r"""
+		\.(
+			txt | py | c
+		)
+		# ignore filetype-independent suffixes:
+			(\.part|\.bak|~)?
+		$
+""", re.VERBOSE | re.IGNORECASE)
+
 class File(FileSystemObject):
 	is_file = True
 
@@ -38,3 +72,25 @@ class File(FileSystemObject):
 		if self.firstbytes and control_characters & set(self.firstbytes):
 			return True
 		return False
+
+	def has_preview(self):
+		if not self.fm.settings.preview_files:
+			return False
+		if self.is_socket or self.is_fifo or self.is_device:
+			return False
+		if not self.accessible:
+			return False
+		if PREVIEW_WHITELIST.search(self.basename):
+			return True
+		if PREVIEW_BLACKLIST.search(self.basename):
+			return False
+		if self.path == '/dev/core' or self.path == '/proc/kcore':
+			return False
+		if self.extension not in ('zip',) and self.is_binary():
+			return False
+		return True
+
+	def get_preview_source(self):
+		if self.extension == 'zip':
+			return '\n'.join(zipfile.ZipFile(self.path).namelist())
+		return open(self.path, 'r')
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index afef48e4..4ca5a6c8 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -19,25 +19,23 @@ CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio',
 
 from os import access, listdir, lstat, readlink, stat
 from time import time
-from os.path import abspath, basename, dirname, realpath
+from os.path import abspath, basename, dirname, realpath, splitext, extsep
 from . import BAD_INFO
 from ranger.shared import MimeTypeAware, FileManagerAware
 from ranger.ext.shell_escape import shell_escape
 from ranger.ext.spawn import spawn
+from ranger.ext.lazy_property import lazy_property
 from ranger.ext.human_readable import human_readable
 
 class FileSystemObject(MimeTypeAware, FileManagerAware):
-	(_filetype,
-	_shell_escaped_basename,
-	basename,
+	(basename,
 	basename_lower,
 	dirname,
 	extension,
 	infostring,
-	last_used,
 	path,
 	permissions,
-	stat) = (None,) * 11
+	stat) = (None,) * 8
 
 	(content_loaded,
 	force_load,
@@ -50,7 +48,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 	is_socket,
 
 	accessible,
-	exists,
+	exists,       # "exists" currently means "link_target_exists"
 	loaded,
 	marked,
 	runnable,
@@ -76,8 +74,8 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		self.path = path
 		self.basename = basename(path)
 		self.basename_lower = self.basename.lower()
+		self.extension = splitext(self.basename)[1].lstrip(extsep) or None
 		self.dirname = dirname(path)
-		self.realpath = self.path
 		self.preload = preload
 
 		try:
@@ -86,48 +84,33 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		except ValueError:
 			self.extension = None
 
-		self.use()
-
 	def __repr__(self):
 		return "<{0} {1}>".format(self.__class__.__name__, self.path)
 
-	@property
+	@lazy_property
 	def shell_escaped_basename(self):
-		if self._shell_escaped_basename is None:
-			self._shell_escaped_basename = shell_escape(self.basename)
-		return self._shell_escaped_basename
+		return shell_escape(self.basename)
 
-	@property
+	@lazy_property
 	def filetype(self):
-		if self._filetype is None:
-			try:
-				got = spawn(["file", '-Lb', '--mime-type', self.path])
-			except OSError:
-				self._filetype = ''
-			else:
-				self._filetype = got
-		return self._filetype
-
-	def get_description(self):
-		return "Loading " + str(self)
+		try:
+			return spawn(["file", '-Lb', '--mime-type', self.path])
+		except OSError:
+			return ""
 
 	def __str__(self):
 		"""returns a string containing the absolute path"""
 		return str(self.path)
 
 	def use(self):
-		"""mark the filesystem-object as used at the current time"""
-		self.last_used = time()
-
-	def is_older_than(self, seconds):
-		"""returns whether this object wasn't use()d in the last n seconds"""
-		if seconds < 0:
-			return True
-		return self.last_used + seconds < time()
+		"""Used in garbage-collecting.  Override in Directory"""
 
 	def set_mimetype(self):
 		"""assign attributes such as self.video according to the mimetype"""
-		self._mimetype = self.mimetypes.guess_type(self.basename, False)[0]
+		basename = self.basename
+		if self.extension == 'part':
+			basename = basename[0:-5]
+		self._mimetype = self.mimetypes.guess_type(basename, False)[0]
 		if self._mimetype is None:
 			self._mimetype = ''
 
@@ -168,6 +151,15 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		"""Called by directory.mark_item() and similar functions"""
 		self.marked = bool(boolean)
 
+	@lazy_property
+	def realpath(self):
+		if self.is_link:
+			try:
+				return realpath(self.path)
+			except:
+				return None  # it is impossible to get the link destination
+		return self.path
+
 	def load(self):
 		"""
 		reads useful information about the filesystem-object from the
@@ -179,46 +171,43 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		# Get the stat object, either from preload or from [l]stat
 		new_stat = None
 		path = self.path
+		is_link = False
 		if self.preload:
 			new_stat = self.preload[1]
-			is_link = (new_stat.st_mode & 0o120000) == 0o120000
+			is_link = new_stat.st_mode & 0o170000 == 0o120000
 			if is_link:
 				new_stat = self.preload[0]
 			self.preload = None
+			self.exists = True if new_stat else False
 		else:
 			try:
 				new_stat = lstat(path)
-				is_link = (new_stat.st_mode & 0o120000) == 0o120000
+				is_link = new_stat.st_mode & 0o170000 == 0o120000
 				if is_link:
 					new_stat = stat(path)
+				self.exists = True
 			except:
-				pass
+				self.exists = False
 
 		# Set some attributes
-		if new_stat:
-			mode = new_stat.st_mode
-			self.accessible = True
-			self.is_device = (mode & 0o060000) == 0o060000
-			self.is_fifo = (mode & 0o010000) == 0o010000
-			self.is_link = is_link
-			self.is_socket = (mode & 0o140000) == 0o140000
-			if access(path, 0):
-				self.exists = True
-				if self.is_directory:
-					self.runnable = (mode & 0o0100) == 0o0100
-			else:
-				self.exists = False
-				self.runnable = False
-			if is_link:
-				try:
-					self.realpath = realpath(path)
-				except OSError:
-					pass  # it is impossible to get the link destination
-		else:
-			self.accessible = False
 
-		# Determine infostring
-		if self.is_file:
+		self.accessible = True if new_stat else False
+		mode = new_stat.st_mode if new_stat else 0
+
+		format = mode & 0o170000
+		if format == 0o020000 or format == 0o060000:  # stat.S_IFCHR/BLK
+			self.is_device = True
+			self.size = 0
+			self.infostring = 'dev'
+		elif format == 0o010000:  # stat.S_IFIFO
+			self.is_fifo = True
+			self.size = 0
+			self.infostring = 'fifo'
+		elif format == 0o140000:  # stat.S_IFSOCK
+			self.is_socket = True
+			self.size = 0
+			self.infostring = 'sock'
+		elif self.is_file:
 			if new_stat:
 				self.size = new_stat.st_size
 				self.infostring = ' ' + human_readable(self.size)
@@ -236,17 +225,9 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 				self.infostring = ' %d' % self.size
 				self.accessible = True
 				self.runnable = True
-		elif self.is_device:
-			self.size = 0
-			self.infostring = 'dev'
-		elif self.is_fifo:
-			self.size = 0
-			self.infostring = 'fifo'
-		elif self.is_socket:
-			self.size = 0
-			self.infostring = 'sock'
-		if self.is_link:
+		if is_link:
 			self.infostring = '->' + self.infostring
+			self.is_link = True
 
 		self.stat = new_stat
 
@@ -274,12 +255,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		self.permissions = ''.join(perms)
 		return self.permissions
 
-	def go(self):
-		"""enter the directory if the filemanager is running"""
-		if self.fm:
-			return self.fm.enter_dir(self.path)
-		return False
-
 	def load_if_outdated(self):
 		"""
 		Calls load() if the currently cached information is outdated
@@ -292,11 +267,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 			real_mtime = lstat(self.path).st_mtime
 		except OSError:
 			real_mtime = None
-		if self.stat:
-			cached_mtime = self.stat.st_mtime
-		else:
-			cached_mtime = 0
-		if real_mtime != cached_mtime:
+		if not self.stat or self.stat.st_mtime != real_mtime:
 			self.load()
 			return True
 		return False
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index f5e34eb1..03ed2f78 100644
--- a/ranger/gui/bar.py
+++ b/ranger/gui/bar.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from ranger.ext.utfwidth import uwid
+
 class Bar(object):
 	left = None
 	right = None
@@ -132,7 +134,7 @@ class ColoredString(object):
 		self.string = self.string[:n]
 
 	def __len__(self):
-		return len(self.string)
+		return uwid(self.string)
 
 	def __str__(self):
 		return self.string
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/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index 82d8c787..3df45700 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -18,6 +18,22 @@ import _curses
 from ranger.ext.iter_tools import flatten
 from ranger.shared import SettingsAware
 
+def ascii_only(string):
+	# Some python versions have problems with invalid unicode strings.
+	# I think this exception is rare enough that this naive hack is enough.
+	# It simply removes all non-ascii chars from a string.
+	def validate_char(char):
+		try:
+			if ord(char) > 127:
+				return '?'
+		except:
+			return '?'
+		return char
+	if isinstance(string, str):
+		return ''.join(validate_char(c) for c in string)
+	return string
+
+
 class CursesShortcuts(SettingsAware):
 	"""
 	This class defines shortcuts to faciliate operations with curses.
@@ -33,12 +49,33 @@ class CursesShortcuts(SettingsAware):
 			self.win.addstr(*args)
 		except (_curses.error, TypeError):
 			pass
+		except UnicodeEncodeError:
+			try:
+				self.win.addstr(*(ascii_only(obj) for obj in args))
+			except (_curses.error, TypeError):
+				pass
 
 	def addnstr(self, *args):
 		try:
 			self.win.addnstr(*args)
 		except (_curses.error, TypeError):
 			pass
+		except UnicodeEncodeError:
+			try:
+				self.win.addnstr(*(ascii_only(obj) for obj in args))
+			except (_curses.error, TypeError):
+				pass
+
+	def addch(self, *args):
+		try:
+			self.win.addch(*args)
+		except (_curses.error, TypeError):
+			pass
+		except UnicodeEncodeError:
+			try:
+				self.win.addch(*(ascii_only(obj) for obj in args))
+			except (_curses.error, TypeError):
+				pass
 
 	def color(self, *keys):
 		"""Change the colors from now on."""
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/ui.py b/ranger/gui/ui.py
index e9c20395..b0c1a352 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -19,6 +19,7 @@ import curses
 import _curses
 
 from .displayable import DisplayableContainer
+from ranger.gui.curses_shortcuts import ascii_only
 from ranger.container.keymap import CommandArgs
 from .mouse_event import MouseEvent
 
@@ -239,7 +240,10 @@ class UI(DisplayableContainer):
 				split = cwd.rsplit(os.sep, self.settings.shorten_title)
 				if os.sep in split[0]:
 					cwd = os.sep.join(split[1:])
-			sys.stdout.write("\033]2;ranger:" + cwd + "\007")
+			try:
+				sys.stdout.write("\033]2;ranger:" + cwd + "\007")
+			except UnicodeEncodeError:
+				sys.stdout.write("\033]2;ranger:" + ascii_only(cwd) + "\007")
 		self.win.refresh()
 
 	def finalize(self):
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index fbacbccd..d617e64e 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -14,7 +14,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """The BrowserColumn widget displays the contents of a directory or file."""
-import re
 import stat
 from time import time
 
@@ -22,36 +21,6 @@ from . import Widget
 from .pager import Pager
 from ranger.fsobject import BAD_INFO
 
-# Don't even try to preview files which mach this regular expression:
-PREVIEW_BLACKLIST = re.compile(r"""
-		# look at the extension:
-		\.(
-			# one character extensions:
-				[oa]
-			# media formats:
-				| avi | [mj]pe?g | mp\d | og[gmv] | wm[av] | mkv | flv
-				| png | bmp | vob | wav | mpc | flac | divx? | xcf | pdf
-			# binary files:
-				| torrent | class | so | img | py[co] | dmg
-			# containers:
-				| iso | rar | zip | 7z | tar | gz | bz2 | tgz
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		# ignore fully numerical file extensions:
-			(\.\d+)*?
-		$
-""", re.VERBOSE | re.IGNORECASE)
-
-PREVIEW_WHITELIST = re.compile(r"""
-		\.(
-			txt | py | c
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		$
-""", re.VERBOSE | re.IGNORECASE)
-
 class BrowserColumn(Pager):
 	main_column = False
 	display_infostring = False
@@ -127,7 +96,7 @@ class BrowserColumn(Pager):
 			return False
 
 		if self.target.is_file:
-			if not self._preview_this_file(self.target):
+			if not self.target.has_preview():
 				return False
 
 		if self.target.is_directory:
@@ -173,43 +142,28 @@ class BrowserColumn(Pager):
 			self.need_redraw = False
 			self.last_redraw_time = time()
 
-	def _preview_this_file(self, target):
-		if not (target \
-				and self.settings.preview_files \
-				and target.is_file \
-				and target.accessible \
-				and target.stat \
-				and not target.is_device \
-				and not target.stat.st_mode & stat.S_IFIFO):
-			return False
-
-		if PREVIEW_WHITELIST.search(target.basename):
-			return True
-		if PREVIEW_BLACKLIST.search(target.basename):
-			return False
-		if target.is_binary():
-			return False
-		return True
-
 	def _draw_file(self):
 		"""Draw a preview of the file, if the settings allow it"""
 		self.win.move(0, 0)
 		if not self.target.accessible:
-			self.win.addnstr("not accessible", self.wid)
+			self.addnstr("not accessible", self.wid)
 			Pager.close(self)
 			return
 
-		if not self._preview_this_file(self.target):
+		if self.target is None or not self.target.has_preview():
 			Pager.close(self)
 			return
 
 		try:
-			f = open(self.target.path, 'r')
+			f = self.target.get_preview_source()
 		except:
 			Pager.close(self)
 		else:
-			self.set_source(f)
-			Pager.draw(self)
+			if f is None:
+				Pager.close(self)
+			else:
+				self.set_source(f)
+				Pager.draw(self)
 
 	def _draw_directory(self):
 		"""Draw the contents of a directory"""
@@ -223,7 +177,7 @@ class BrowserColumn(Pager):
 
 		if not self.target.content_loaded:
 			self.color(base_color)
-			self.win.addnstr("...", self.wid)
+			self.addnstr("...", self.wid)
 			self.color_reset()
 			return
 
@@ -232,13 +186,13 @@ class BrowserColumn(Pager):
 
 		if not self.target.accessible:
 			self.color(base_color, 'error')
-			self.win.addnstr("not accessible", self.wid)
+			self.addnstr("not accessible", self.wid)
 			self.color_reset()
 			return
 
 		if self.target.empty():
 			self.color(base_color, 'empty')
-			self.win.addnstr("empty", self.wid)
+			self.addnstr("empty", self.wid)
 			self.color_reset()
 			return
 
@@ -295,27 +249,22 @@ class BrowserColumn(Pager):
 				this_color.append(drawn.exists and 'good' or 'bad')
 
 			string = drawn.basename
-			try:
-				if self.main_column:
-					if tagged:
-						self.win.addnstr(line, 0, text, self.wid - 2)
-					elif self.wid > 1:
-						self.win.addnstr(line, 1, text, self.wid - 2)
-				else:
-					self.win.addnstr(line, 0, text, self.wid)
-
-				if self.display_infostring and drawn.infostring \
-						and self.settings.display_size_in_main_column:
-					info = drawn.infostring
-					x = self.wid - 1 - len(info)
-					if info is BAD_INFO:
-						bad_info_color = (x, len(str(info)))
-					if x > 0:
-						self.win.addstr(line, x, str(info) + ' ')
-			except:
-				# the drawing of the last string will cause an error
-				# because ncurses tries to move out of the bounds
-				pass
+			if self.main_column:
+				if tagged:
+					self.addnstr(line, 0, text, self.wid - 2)
+				elif self.wid > 1:
+					self.addnstr(line, 1, text, self.wid - 2)
+			else:
+				self.addnstr(line, 0, text, self.wid)
+
+			if self.display_infostring and drawn.infostring \
+					and self.settings.display_size_in_main_column:
+				info = drawn.infostring
+				x = self.wid - 1 - len(info)
+				if info is BAD_INFO:
+					bad_info_color = (x, len(str(info)))
+				if x > 0:
+					self.addstr(line, x, str(info) + ' ')
 
 			self.color_at(line, 0, self.wid, this_color)
 			if bad_info_color:
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index a90231f2..c80e4885 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -144,7 +144,7 @@ class BrowserView(Widget, DisplayableContainer):
 
 			if maxlen < self.wid:
 				self.win.vline(0, maxlen, curses.ACS_VLINE, line+1)
-				self.win.addch(line+1, maxlen, curses.ACS_LRCORNER)
+				self.addch(line+1, maxlen, curses.ACS_LRCORNER)
 
 	def _draw_borders(self):
 		win = self.win
@@ -188,13 +188,10 @@ class BrowserView(Widget, DisplayableContainer):
 				# in case it's off the boundaries
 				pass
 
-		win.addch(0, left_start, curses.ACS_ULCORNER)
-		win.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
-		win.addch(0, right_end, curses.ACS_URCORNER)
-		try:
-			win.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
-		except:
-			pass
+		self.addch(0, left_start, curses.ACS_ULCORNER)
+		self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
+		self.addch(0, right_end, curses.ACS_URCORNER)
+		self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
 
 	def _collapse(self):
 		# Should the last column be cut off? (Because there is no preview)
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index fa9e438e..57264292 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -18,124 +18,101 @@ 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.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
+		self.history = History(self.settings.max_console_history_size)
+		# load history from files
 		if not ranger.arg.clean:
-			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.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) + len(self.line) + 1
+		overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1
 		if overflow > 0: 
+			#XXX: cut uft-char-wise, consider width
 			self.addstr(self.line[overflow:])
 		else:
 			self.addstr(self.line)
 
 	def finalize(self):
 		try:
-			self.fm.ui.win.move(self.y,
-					self.x + min(self.wid-1, self.pos + len(self.prompt)))
+			xpos = uwid(self.line[0:self.pos]) + len(self.prompt)
+			self.fm.ui.win.move(self.y, self.x + min(self.wid-1, xpos))
 		except:
 			pass
 
-	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
 
@@ -208,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()
@@ -216,16 +196,20 @@ 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)
 		if direction.horizontal():
-			self.pos = direction.move(
+			# Ensure that the pointer is moved utf-char-wise
+			uc = uchars(self.line)
+			upos = len(uchars(self.line[:self.pos]))
+			newupos = direction.move(
 					direction=direction.right(),
 					minimum=0,
-					maximum=len(self.line) + 1,
-					current=self.pos)
+					maximum=len(uc) + 1,
+					current=upos)
+			self.pos = len(''.join(uc[:newupos]))
 
 	def delete_rest(self, direction):
 		self.tab_deque = None
@@ -247,76 +231,30 @@ 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
-		if mod == -1 and len(self.line) == 0:
-			self.close()
-		pos = self.pos + mod
-
-		self.line = self.line[0:pos] + self.line[pos+1:]
-		self.move(right=mod)
+		if mod == -1 and self.pos == 0:
+			if not self.line:
+				self.close()
+			return
+		# Delete utf-char-wise
+		uc = uchars(self.line)
+		upos = len(uchars(self.line[:self.pos])) + mod
+		left_part = ''.join(uc[:upos])
+		self.pos = len(left_part)
+		self.line = left_part + ''.join(uc[upos+1:])
 		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
@@ -340,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])
@@ -355,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/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 3019930b..2a92f313 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -224,19 +224,23 @@ class StatusBar(Widget):
 				right.add(human_readable(sum(f.size \
 					for f in target.marked_items \
 					if f.is_file), seperator=''))
-			right.add(" / " + str(len(target.marked_items)))
+			right.add("/" + str(len(target.marked_items)))
 		else:
-			right.add(human_readable(target.disk_usage, seperator=''))
-			right.add(", ", "space")
+			right.add(human_readable(target.disk_usage, seperator='') +
+					" sum, ")
 			right.add(human_readable(self.env.get_free_space( \
-					target.mount_path), seperator=''))
+					target.mount_path), seperator='') + " free")
 		right.add("  ", "space")
 
 		if target.marked_items:
 			# Indicate that there are marked files. Useful if you scroll
 			# away and don't see them anymore.
 			right.add('Mrk', base, 'marked')
-		elif max_pos > 0:
+		elif len(target.files):
+			right.add(str(target.pointer + 1) + '/'
+					+ str(len(target.files)) + '  ', base)
+			if max_pos == 0:
+				right.add('All', base, 'all')
 			if pos == 0:
 				right.add('Top', base, 'top')
 			elif pos >= max_pos:
@@ -245,7 +249,7 @@ class StatusBar(Widget):
 				right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos),
 						base, 'percentage')
 		else:
-			right.add('All', base, 'all')
+			right.add('0/0  All', base, 'all')
 
 	def _print_result(self, result):
 		self.win.move(0, 0)
diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py
index 475b903c..e988b08c 100644
--- a/ranger/gui/widgets/taskview.py
+++ b/ranger/gui/widgets/taskview.py
@@ -86,7 +86,8 @@ class TaskView(Widget, Accumulator):
 		if i is None:
 			i = self.pointer
 
-		self.fm.loader.remove(index=i)
+		if self.fm.loader.queue:
+			self.fm.loader.remove(index=i)
 
 	def task_move(self, to, i=None):
 		if i is None:
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index 17da7748..35e2e3d9 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -52,7 +52,7 @@ class TitleBar(Widget):
 		self._print_result(self.result)
 		if self.wid > 2:
 			self.color('in_titlebar', 'throbber')
-			self.win.addnstr(self.y, self.wid - 2 - self.tab_width,
+			self.addnstr(self.y, self.wid - 2 - self.tab_width,
 					self.throbber, 1)
 
 	def click(self, event):
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/index.py b/ranger/help/index.py
index 316e975f..1245da64 100644
--- a/ranger/help/index.py
+++ b/ranger/help/index.py
@@ -18,8 +18,8 @@
                                                                      k
     Move around:  Use the cursor keys, or "h" to go left,          h   l
                   "j" to go down, "k" to go up, "l" to go right.     j
-   Close Ranger:  Use ":q<Enter>" or "Q".
-  Specific help:  Type the help key "?" prepended with a number:
+   Close Ranger:  Type "Q"
+  Specific help:  Type "?", prepended with a number:
 
 	|0?|	This index
 	|1?|	Basic movement and browsing
diff --git a/ranger/help/invocation.py b/ranger/help/invocation.py
index 3de574cc..26cffd4a 100644
--- a/ranger/help/invocation.py
+++ b/ranger/help/invocation.py
@@ -43,9 +43,10 @@ command line.
       This is useful when your configuration is broken, when you want
       to avoid clutter, etc.
 
---fail-if-run
+--fail-unless-cd
       Return the exit code 1 if ranger is used to run a file, for example
-      with `ranger --fail-if-run filename`.  This can be useful for scripts.
+      with `ranger --fail-unless-cd filename`.  This can be useful for scripts.
+      (This option used to be called --fail-if-run)
 
 -r <dir>, --confdir=<dir>
       Define a different configuration directory.  The default is
@@ -69,7 +70,7 @@ command line.
 Examples:
       ranger episode1.avi
       ranger --debug /usr/bin
-      ranger --confdir=~/.config/ranger --fail-if-run
+      ranger --confdir=~/.config/ranger --fail-unless-cd
 
 
 ==============================================================================
@@ -95,7 +96,7 @@ docstrings.  Use this option if you don't need the documentation.
 Examples:
       PYTHONOPTIMIZE=1 ranger episode1.avi
       PYTHONOPTIMIZE=2 ranger --debug /usr/bin
-      python -OO `which ranger` --confdir=~/.config/ranger --fail-if-run
+      python -OO `which ranger` --confdir=~/.config/ranger --fail-unless-cd
 
 Note: The author expected "-OO" to reduce the memory usage, but that
 doesn't seem to happen.
diff --git a/ranger/help/movement.py b/ranger/help/movement.py
index 3287e9bb..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
@@ -96,7 +99,10 @@ of the file you're pointing at.
 
 	<Space> mark a file
 	v	toggle all marks
-	V	remove all marks
+	V, uv	remove all marks
+	^V	mark files in a specific direction
+		e.g. ^Vgg marks all files from the current to the top
+	u^V	unmark files in a specific direction
 
 By "tagging" files, you can highlight them and mark them to be
 special in whatever context you want.  Tags are persistent across sessions.
diff --git a/ranger/help/starting.py b/ranger/help/starting.py
index 069d6a04..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
 
@@ -95,12 +102,13 @@ Flags give you a way to modify the behaviour of the spawned process.
 	s	Silent mode.  Output will be discarded.
 	d	Detach the process.  (Run in background)
 	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 f5d8614f..7604af12 100644
--- a/ranger/shared/settings.py
+++ b/ranger/shared/settings.py
@@ -20,35 +20,35 @@ 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,
+	'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,
+	'save_console_history': bool,
+	'scroll_offset': int,
+	'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,
-	'update_title': bool,
-	'shorten_title': int,  # Note: False is an instance of int
+	'sort_reverse': bool,
+	'sort': str,
 	'tilde_in_titlebar': bool,
-	'max_history_size': (int, type(None)),
-	'max_console_history_size': (int, type(None)),
-	'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'),
+	'update_title': bool,
 	'xterm_alt_key': bool,
 }
 
@@ -66,7 +66,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])