summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2010-05-14 17:27:04 +0200
committerhut <hut@lavabit.com>2010-05-14 17:27:04 +0200
commit420d3d1af8d2991ae6a8cbe061adaab878c4b3ff (patch)
treead1cdc33cc12984c141618439b646424d8a76c25 /ranger
parent792d6b23711c428a4a2b98537f44e2fbaed97fdd (diff)
parent2f3326a41430364cb34a227d311691cada605327 (diff)
downloadranger-420d3d1af8d2991ae6a8cbe061adaab878c4b3ff.tar.gz
Merge branch 'master' into cp
Conflicts:
	ranger/__main__.py
Diffstat (limited to 'ranger')
-rw-r--r--ranger/__main__.py75
-rw-r--r--ranger/api/commands.py9
-rw-r--r--ranger/core/environment.py8
-rw-r--r--ranger/core/fm.py2
-rw-r--r--ranger/defaults/commands.py26
-rw-r--r--ranger/defaults/keys.py33
-rw-r--r--ranger/defaults/options.py2
-rw-r--r--ranger/ext/accumulator.py1
-rw-r--r--ranger/ext/human_readable.py2
-rw-r--r--ranger/ext/spawn.py27
-rw-r--r--ranger/fsobject/__init__.py11
-rw-r--r--ranger/fsobject/directory.py83
-rw-r--r--ranger/fsobject/file.py8
-rw-r--r--ranger/fsobject/fsobject.py141
-rw-r--r--ranger/gui/mouse_event.py9
-rw-r--r--ranger/gui/ui.py4
-rw-r--r--ranger/gui/widgets/browsercolumn.py10
-rw-r--r--ranger/gui/widgets/browserview.py26
-rw-r--r--ranger/gui/widgets/console.py22
-rw-r--r--ranger/help/__init__.py3
-rw-r--r--ranger/help/console.py31
-rw-r--r--ranger/help/index.py1
-rw-r--r--ranger/help/invocation.py106
-rw-r--r--ranger/help/movement.py37
-rw-r--r--ranger/help/starting.py2
-rw-r--r--ranger/shared/mimetype.py8
26 files changed, 475 insertions, 212 deletions
diff --git a/ranger/__main__.py b/ranger/__main__.py
index d25065a1..6d574693 100644
--- a/ranger/__main__.py
+++ b/ranger/__main__.py
@@ -16,29 +16,19 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+# Most import statements in this module are inside the functions.
+# This enables more convenient exception handling in ranger.py
+# (ImportError will imply that this module can't be found)
+# convenient exception handling in ranger.py (ImportError)
+
 import os
 import sys
-import ranger
-
-from optparse import OptionParser, SUPPRESS_HELP
-from ranger.ext.openstruct import OpenStruct
-from ranger import __version__, USAGE, DEFAULT_CONFDIR
-import ranger.api.commands
-
-from signal import signal, SIGINT
-from locale import getdefaultlocale, setlocale, LC_ALL
-
-from ranger.ext import curses_interrupt_handler
-from ranger.core.runner import Runner
-from ranger.core.fm import FM
-from ranger.core.environment import Environment
-from ranger.shared import (EnvironmentAware, FileManagerAware,
-		SettingsAware)
-from ranger.gui.defaultui import DefaultUI as UI
-from ranger.fsobject.file import File
 
 def parse_arguments():
 	"""Parse the program 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__)
 
 	parser.add_option('-d', '--debug', action='store_true',
@@ -83,6 +73,8 @@ def allow_access_to_confdir(confdir, allow):
 
 
 def load_settings(fm, clean):
+	import ranger
+	import ranger.api.commands
 	if not clean:
 		allow_access_to_confdir(ranger.arg.confdir, True)
 
@@ -132,6 +124,7 @@ def load_settings(fm, clean):
 
 
 def load_apps(fm, clean):
+	import ranger
 	if not clean:
 		allow_access_to_confdir(ranger.arg.confdir, True)
 		try:
@@ -152,15 +145,28 @@ def main():
 		print(errormessage)
 		print('ranger requires the python curses module. Aborting.')
 		sys.exit(1)
+	from locale import getdefaultlocale, setlocale, LC_ALL
+	import ranger
+	from ranger.ext import curses_interrupt_handler
+	from ranger.core.runner import Runner
+	from ranger.core.fm import FM
+	from ranger.core.environment import Environment
+	from ranger.gui.defaultui import DefaultUI as UI
+	from ranger.fsobject import File
+	from ranger.shared import (EnvironmentAware, FileManagerAware,
+			SettingsAware)
 
 	# Ensure that a utf8 locale is set.
-	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
+	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, '')
-	else: setlocale(LC_ALL, '')
+	except:
+		print("Warning: Unable to set locale.  Expect encoding problems.")
 
 	arg = parse_arguments()
 	ranger.arg = arg
@@ -172,6 +178,8 @@ def main():
 
 	if arg.targets:
 		target = arg.targets[0]
+		if target.startswith('file://'):
+			target = target[7:]
 		if not os.access(target, os.F_OK):
 			print("File or directory doesn't exist: %s" % target)
 			sys.exit(1)
@@ -190,6 +198,7 @@ def main():
 	# Initialize objects
 	EnvironmentAware._assign(Environment(path))
 	fm = FM()
+	crash_exception = None
 	try:
 		load_settings(fm, ranger.arg.clean)
 		FileManagerAware._assign(fm)
@@ -199,11 +208,25 @@ 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'))
 	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.")
 
 
 if __name__ == '__main__':
-	top_dir = os.path.dirname(sys.path[0])
-	sys.path.insert(0, top_dir)
+	# The ranger directory can be executed directly, for example by typing
+	# python /usr/lib/python2.6/site-packages/ranger
+	sys.path.insert(0, os.path.dirname(sys.path[0]))
 	main()
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 55bb5b54..ca3f730d 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -48,13 +48,14 @@ class CommandContainer(object):
 	def get_command(self, name, abbrev=True):
 		if abbrev:
 			lst = [cls for cmd, cls in self.commands.items() \
-					if cmd.startswith(name) \
-					and cls.allow_abbrev \
+					if cls.allow_abbrev and cmd.startswith(name) \
 					or cmd == name]
 			if len(lst) == 0:
 				raise KeyError
-			if len(lst) == 1 or self.commands[name] in lst:
+			if len(lst) == 1:
 				return lst[0]
+			if self.commands[name] in lst:
+				return self.commands[name]
 			raise ValueError("Ambiguous command")
 		else:
 			try:
@@ -80,7 +81,7 @@ class Command(FileManagerAware):
 	def tab(self):
 		"""Override this"""
 
-	def quick_open(self):
+	def quick(self):
 		"""Override this"""
 
 	def _tab_only_directories(self):
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index a485e277..296ba108 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -19,7 +19,7 @@ import pwd
 import socket
 from os.path import abspath, normpath, join, expanduser, isdir
 
-from ranger.fsobject.directory import Directory, NoDirectoryGiven
+from ranger.fsobject import Directory
 from ranger.container import KeyBuffer, KeyManager, History
 from ranger.ext.signal_dispatcher import SignalDispatcher
 from ranger.shared import SettingsAware
@@ -179,12 +179,8 @@ class Environment(SettingsAware, SignalDispatcher):
 		path = normpath(join(self.path, expanduser(path)))
 
 		if not isdir(path):
-			return
-
-		try:
-			new_cwd = self.get_directory(path)
-		except NoDirectoryGiven:
 			return False
+		new_cwd = self.get_directory(path)
 
 		try:
 			os.chdir(path)
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 7a55afa7..d2aa00ec 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -30,7 +30,7 @@ from ranger.container import Bookmarks
 from ranger.core.runner import Runner
 from ranger import relpath_conf
 from ranger.ext.get_executables import get_executables
-from ranger.fsobject.directory import Directory
+from ranger.fsobject import Directory
 from ranger.ext.signal_dispatcher import SignalDispatcher
 from ranger import __version__
 from ranger.core.loader import Loader
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index bfe038c5..8728f9be 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -58,8 +58,8 @@ from ranger.api.commands import *
 
 alias('e', 'edit')
 alias('q', 'quit')
-alias('q!', 'quit!')
-alias('qall', 'quit!')
+alias('q!', 'quitall')
+alias('qall', 'quitall')
 
 class cd(Command):
 	"""
@@ -213,18 +213,27 @@ class quit(Command):
 		self.fm.tab_close()
 
 
-class quit_now(Command):
+class quitall(Command):
 	"""
-	:quit!
+	:quitall
 
 	Quits the program immediately.
 	"""
-	name = 'quit!'
 
 	def execute(self):
 		self.fm.exit()
 
 
+class quit_bang(quitall):
+	"""
+	:quit!
+
+	Quits the program immediately.
+	"""
+	name = 'quit!'
+	allow_abbrev = False
+
+
 class terminal(Command):
 	"""
 	:terminal
@@ -358,7 +367,10 @@ class edit(Command):
 
 	def execute(self):
 		line = parse(self.line)
-		self.fm.edit_file(line.rest(1))
+		if not line.chunk(1):
+			self.fm.edit_file(self.fm.env.cf.path)
+		else:
+			self.fm.edit_file(line.rest(1))
 
 	def tab(self):
 		return self._tab_directory_content()
@@ -403,7 +415,7 @@ class rename(Command):
 	"""
 
 	def execute(self):
-		from ranger.fsobject.file import File
+		from ranger.fsobject import File
 		line = parse(self.line)
 		if not line.rest(1):
 			return self.fm.notify('Syntax: rename <newname>', bad=True)
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 4dd7f280..b17e5e8f 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -162,26 +162,28 @@ map('T', fm.tag_remove())
 
 map(' ', fm.mark(toggle=True))
 map('v', fm.mark(all=True, toggle=True))
-map('V', fm.mark(all=True, val=False))
+map('V', 'uv', fm.mark(all=True, val=False))
 
 # ------------------------------------------ file system operations
 map('yy', 'y<dir>', fm.copy())
 map('dd', 'd<dir>', fm.cut())
-map('ud', fm.uncut())
 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('u<bg>', fm.hint("un*y*ank, unbook*m*ark, unselect:*v*"))
+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'))
 
 # -------------------------------------------------- toggle options
-map('z<bg>', fm.hint("show_*h*idden *p*review_files *P*review_dirs " \
-	"*d*irs_first flush*i*nput *m*ouse"))
+map('z<bg>', fm.hint("[*cdfhimpPs*] show_*h*idden *p*review_files "\
+		"*P*review_dirs *f*ilter flush*i*nput *m*ouse"))
 map('zh', fm.toggle_boolean_option('show_hidden'))
 map('zp', fm.toggle_boolean_option('preview_files'))
 map('zP', fm.toggle_boolean_option('preview_directories'))
@@ -190,6 +192,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 '))
 
 # ------------------------------------------------------------ sort
 map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*ase*n*ame *m*time" \
@@ -217,10 +220,14 @@ def append_to_filename(arg):
 	command = 'rename ' + arg.fm.env.cf.basename
 	arg.fm.open_console(cmode.COMMAND, command)
 
+@map("I")
+def insert_before_filename(arg):
+	append_to_filename(arg)
+	arg.fm.ui.console.move(right=len('rename '), absolute=True)
+
 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('bf', fm.open_console(cmode.COMMAND, 'filter '))
 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!'))
@@ -243,7 +250,10 @@ map('gR', fm.cd(RANGERDIR))
 map('gc', '<C-W>', fm.tab_close())
 map('gt', '<TAB>', fm.tab_move(1))
 map('gT', '<S-TAB>', fm.tab_move(-1))
-map('gn', '<C-N>', fm.tab_new())
+@map('gn', '<C-N>')
+def newtab_and_gohome(arg):
+	arg.fm.tab_new()
+	arg.fm.cd('~')   # To return to the original directory, type ``
 for n in range(1, 10):
 	map('g' + str(n), fm.tab_open(n))
 	map('<A-' + str(n) + '>', fm.tab_open(n))
@@ -258,7 +268,7 @@ map('ct', fm.search(order='tag'))
 map('cc', fm.search(order='ctime'))
 map('cm', fm.search(order='mimetype'))
 map('cs', fm.search(order='size'))
-map('c<bg>', fm.hint('*c*time *m*imetype *s*ize'))
+map('c<bg>', fm.hint('*c*time *m*imetype *s*ize *t*ag  *w*:rename'))
 
 # ------------------------------------------------------- bookmarks
 for key in ALLOWED_BOOKMARK_KEYS:
@@ -266,6 +276,7 @@ for key in ALLOWED_BOOKMARK_KEYS:
 	map("m" + key, fm.set_bookmark(key))
 	map("um" + key, fm.unset_bookmark(key))
 map("`<bg>", "'<bg>", "m<bg>", fm.draw_bookmarks())
+map('um<bg>', fm.hint("delete which bookmark?"))
 
 # ---------------------------------------------------- change views
 map('i', fm.display_file())
@@ -350,18 +361,18 @@ map = keymanager.get_context('console')
 map.merge(global_keys)
 map.merge(readline_aliases)
 
-map('<up>', wdg.history_move(-1))
-map('<down>', wdg.history_move(1))
+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('<tab>', wdg.tab())
 map('<s-tab>', wdg.tab(-1))
-map('<c-c>', '<esc>', wdg.close())
+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(1))
+map('<delete>', 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 ac074e1c..d93d7685 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -88,7 +88,7 @@ max_history_size = 20
 max_console_history_size = 20
 
 # Try to keep so much space between the top/bottom border when scrolling:
-scroll_offset = 2
+scroll_offset = 8
 
 # Flush the input after each key hit?  (Noticable when ranger lags)
 flushinput = True
diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py
index 2e599c85..75f58ad7 100644
--- a/ranger/ext/accumulator.py
+++ b/ranger/ext/accumulator.py
@@ -68,6 +68,7 @@ class Accumulator(object):
 
 		return self.move(to=self.pointer)
 
+	# XXX Is this still necessary?  move() ensures correct pointer position
 	def correct_pointer(self):
 		lst = self.get_list()
 
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index 1a3d1ec9..beeaf6d3 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -24,7 +24,7 @@ def human_readable(byte, seperator=' '):
 		return '0'
 
 	exponent = int(math.log(byte, 2) / 10)
-	flt = float(byte) / (1 << (10 * exponent))
+	flt = round(float(byte) / (1 << (10 * exponent)), 2)
 
 	if exponent > MAX_EXPONENT:
 		return '>9000' # off scale
diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py
new file mode 100644
index 00000000..9723c1ed
--- /dev/null
+++ b/ranger/ext/spawn.py
@@ -0,0 +1,27 @@
+# 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 subprocess import Popen, PIPE
+ENCODING = 'utf-8'
+
+def spawn(*args):
+	"""Runs a program, waits for its termination and returns its stdout"""
+	if len(args) == 1:
+		popen_arguments = args[0]
+	else:
+		popen_arguments = args
+	process = Popen(popen_arguments, stdout=PIPE)
+	stdout, stderr = process.communicate()
+	return stdout.decode(ENCODING)
diff --git a/ranger/fsobject/__init__.py b/ranger/fsobject/__init__.py
index 10997df6..5fb4b877 100644
--- a/ranger/fsobject/__init__.py
+++ b/ranger/fsobject/__init__.py
@@ -16,16 +16,9 @@
 """FileSystemObjects are representation of files and directories
 with fast access to their properties through caching"""
 
-T_FILE = 'file'
-T_DIRECTORY = 'directory'
-T_UNKNOWN = 'unknown'
-T_NONEXISTANT = 'nonexistant'
-
 BAD_INFO = '?'
 
-class NotLoadedYet(Exception):
-	pass
-
+# So they can be imported from other files more easily:
 from .fsobject import FileSystemObject
 from .file import File
-from .directory import Directory, NoDirectoryGiven
+from .directory import Directory
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index 43af772a..60387e7b 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -13,10 +13,14 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import os
+import os.path
+import stat
+from stat import S_ISLNK, S_ISDIR
+from os.path import join, isdir, basename
 from collections import deque
 from time import time
 
+from ranger.ext.mount_path import mount_path
 from ranger.fsobject import BAD_INFO, File, FileSystemObject
 from ranger.shared import SettingsAware
 from ranger.ext.accumulator import Accumulator
@@ -34,8 +38,17 @@ def sort_by_directory(path):
 	"""returns 0 if path is a directory, otherwise 1 (for sorting)"""
 	return 1 - path.is_directory
 
-class NoDirectoryGiven(Exception):
-	pass
+def accept_file(fname, hidden_filter, name_filter):
+	if hidden_filter:
+		try:
+			if hidden_filter.search(fname):
+				return False
+		except:
+			if hidden_filter in fname:
+				return False
+	if name_filter and name_filter not in fname:
+		return False
+	return True
 
 class Directory(FileSystemObject, Accumulator, SettingsAware):
 	is_directory = True
@@ -68,14 +81,11 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 		'type': lambda path: path.mimetype,
 	}
 
-	def __init__(self, path):
-		from os.path import isfile
-
-		if isfile(path):
-			raise NoDirectoryGiven()
+	def __init__(self, path, **kw):
+		assert not os.path.isfile(path), "No directory given!"
 
 		Accumulator.__init__(self)
-		FileSystemObject.__init__(self, path)
+		FileSystemObject.__init__(self, path, **kw)
 
 		self.marked_items = list()
 
@@ -150,33 +160,19 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 		in each iteration.
 		"""
 
-		# log("generating loader for " + self.path + "(" + str(id(self)) + ")")
-		from os.path import join, isdir, basename
-		from os import listdir
-		import ranger.ext.mount_path
-
 		self.loading = True
 		self.load_if_outdated()
 
 		try:
 			if self.exists and self.runnable:
 				yield
-				self.mount_path = ranger.ext.mount_path.mount_path(self.path)
-
-				filenames = []
-				for fname in listdir(self.path):
-					if not self.settings.show_hidden:
-						hfilter = self.settings.hidden_filter
-						if hfilter:
-							if isinstance(hfilter, str) and hfilter in fname:
-								continue
-							if hasattr(hfilter, 'search') and \
-								hfilter.search(fname):
-								continue
-					if isinstance(self.filter, str) and self.filter \
-							and self.filter not in fname:
-						continue
-					filenames.append(join(self.path, fname))
+				self.mount_path = mount_path(self.path)
+
+				hidden_filter = not self.settings.show_hidden \
+						and self.settings.hidden_filter
+				filenames = [join(self.path, fname) \
+						for fname in os.listdir(self.path) \
+						if accept_file(fname, hidden_filter, self.filter)]
 				yield
 
 				self.load_content_mtime = os.stat(self.path).st_mtime
@@ -185,13 +181,25 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 
 				files = []
 				for name in filenames:
-					if isdir(name):
+					try:
+						file_lstat = os.lstat(name)
+						if S_ISLNK(file_lstat.st_mode):
+							file_stat = os.stat(name)
+						else:
+							file_stat = file_lstat
+						stats = (file_stat, file_lstat)
+						is_a_dir = S_ISDIR(file_stat.st_mode)
+					except:
+						stats = None
+						is_a_dir = False
+					if is_a_dir:
 						try:
 							item = self.fm.env.get_directory(name)
 						except:
-							item = Directory(name)
+							item = Directory(name, preload=stats,
+									path_is_abs=True)
 					else:
-						item = File(name)
+						item = File(name, preload=stats, path_is_abs=True)
 					item.load_if_outdated()
 					files.append(item)
 					yield
@@ -205,9 +213,10 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 				self._clear_marked_items()
 				for item in self.files:
 					if item.path in marked_paths:
-						self.mark_item(item, True)
+						item._mark(True)
+						self.marked_items.append(item)
 					else:
-						self.mark_item(item, False)
+						item._mark(False)
 
 				self.sort()
 
@@ -402,8 +411,8 @@ class Directory(FileSystemObject, Accumulator, SettingsAware):
 
 	def __len__(self):
 		"""The number of containing files"""
-		if not self.accessible or not self.content_loaded:
-			raise ranger.fsobject.NotLoadedYet()
+		assert self.accessible
+		assert self.content_loaded
 		assert self.files is not None
 		return len(self.files)
 
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index aa44016e..4618df33 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -13,11 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-control_characters = set(chr(n) for n in set(range(0, 9)) | set(range(14,32)))
 N_FIRST_BYTES = 20
+control_characters = set(chr(n) for n in
+		set(range(0, 9)) | set(range(14, 32)))
 
-from .fsobject import FileSystemObject as SuperClass
-class File(SuperClass):
+from ranger.fsobject import FileSystemObject
+class File(FileSystemObject):
 	is_file = True
 
 	@property
@@ -37,4 +38,3 @@ class File(SuperClass):
 		if self.firstbytes and control_characters & set(self.firstbytes):
 			return True
 		return False
-
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index f3d40614..6bb8b6ec 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -14,19 +14,17 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 CONTAINER_EXTENSIONS = 'rar zip tar gz bz bz2 tgz 7z iso cab'.split()
-DOCUMENT_EXTENSIONS = 'pdf doc ppt odt'.split()
-DOCUMENT_BASENAMES = 'README TODO LICENSE COPYING INSTALL'.split()
-DOCUMENT_EXTENSIONS = ()
-DOCUMENT_BASENAMES = ()
 
 import stat
 import os
+from stat import S_ISLNK, S_ISCHR, S_ISBLK, S_ISSOCK, S_ISFIFO, \
+		S_ISDIR, S_IXUSR
 from time import time
-from subprocess import Popen, PIPE
 from os.path import abspath, basename, dirname, realpath
-from . import T_FILE, T_DIRECTORY, T_UNKNOWN, T_NONEXISTANT, BAD_INFO
+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.human_readable import human_readable
 
 class FileSystemObject(MimeTypeAware, FileManagerAware):
@@ -55,7 +53,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 	stat = None
 	infostring = None
 	permissions = None
-	type = T_UNKNOWN
 	size = 0
 
 	last_used = None
@@ -70,15 +67,17 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 	container = False
 	mimetype_tuple = ()
 
-	def __init__(self, path):
+	def __init__(self, path, preload=None, path_is_abs=False):
 		MimeTypeAware.__init__(self)
 
-		path = abspath(path)
+		if not path_is_abs:
+			path = abspath(path)
 		self.path = path
 		self.basename = basename(path)
 		self.basename_lower = self.basename.lower()
 		self.dirname = dirname(path)
 		self.realpath = self.path
+		self.preload = preload
 
 		try:
 			lastdot = self.basename.rindex('.') + 1
@@ -86,7 +85,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		except ValueError:
 			self.extension = None
 
-		self.set_mimetype()
 		self.use()
 
 	def __repr__(self):
@@ -102,8 +100,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 	def filetype(self):
 		if self._filetype is None:
 			try:
-				got = Popen(["file", '-Lb', '--mime-type', self.path],
-						stdout=PIPE).communicate()[0]
+				got = spawn(["file", '-Lb', '--mime-type', self.path])
 			except OSError:
 				self._filetype = ''
 			else:
@@ -129,24 +126,38 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 
 	def set_mimetype(self):
 		"""assign attributes such as self.video according to the mimetype"""
-		self.mimetype = self.mimetypes.guess_type(self.basename, False)[0]
-		if self.mimetype is None:
-			self.mimetype = ''
+		self._mimetype = self.mimetypes.guess_type(self.basename, False)[0]
+		if self._mimetype is None:
+			self._mimetype = ''
 
-		self.video = self.mimetype.startswith('video')
-		self.image = self.mimetype.startswith('image')
-		self.audio = self.mimetype.startswith('audio')
+		self.video = self._mimetype.startswith('video')
+		self.image = self._mimetype.startswith('image')
+		self.audio = self._mimetype.startswith('audio')
 		self.media = self.video or self.image or self.audio
-		self.document = self.mimetype.startswith('text') \
-				or (self.extension in DOCUMENT_EXTENSIONS) \
-				or (self.basename in DOCUMENT_BASENAMES)
+		self.document = self._mimetype.startswith('text')
 		self.container = self.extension in CONTAINER_EXTENSIONS
 
 		keys = ('video', 'audio', 'image', 'media', 'document', 'container')
-		self.mimetype_tuple = tuple(key for key in keys if getattr(self, key))
+		self._mimetype_tuple = tuple(key for key in keys if getattr(self, key))
 
-		if self.mimetype == '':
-			self.mimetype = None
+		if self._mimetype == '':
+			self._mimetype = None
+
+	@property
+	def mimetype(self):
+		try:
+			return self._mimetype
+		except:
+			self.set_mimetype()
+			return self._mimetype
+
+	@property
+	def mimetype_tuple(self):
+		try:
+			return self._mimetype_tuple
+		except:
+			self.set_mimetype()
+			return self._mimetype_tuple
 
 	def mark(self, boolean):
 		directory = self.env.get_directory(self.dirname)
@@ -166,20 +177,20 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 			self.infostring = 'sock'
 		elif self.is_directory:
 			try:
-				self.size = len(os.listdir(self.path))
+				self.size = len(os.listdir(self.path))  # bite me
 			except OSError:
 				self.infostring = BAD_INFO
 				self.accessible = False
 			else:
-				self.infostring = " %d" % self.size
+				self.infostring = ' %d' % self.size
 				self.accessible = True
 				self.runnable = True
 		elif self.is_file:
-			try:
+			if self.stat:
 				self.size = self.stat.st_size
 				self.infostring = ' ' + human_readable(self.size)
-			except:
-				pass
+			else:
+				self.infostring = BAD_INFO
 		if self.is_link:
 			self.infostring = '->' + self.infostring
 
@@ -190,42 +201,48 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		"""
 
 		self.loaded = True
-		try:
-			self.stat = os.lstat(self.path)
-		except OSError:
-			self.stat = None
-			self.is_link = False
-			self.accessible = False
-		else:
-			self.is_link = stat.S_ISLNK(self.stat.st_mode)
+
+		# Get the stat object, either from preload or from os.[l]stat
+		self.stat = None
+		if self.preload:
+			self.stat = self.preload[1]
+			self.is_link = S_ISLNK(self.stat.st_mode)
 			if self.is_link:
-				try: # try to resolve the link
-					self.readlink = os.readlink(self.path)
-					self.realpath = realpath(self.path)
-					self.stat = os.stat(self.path)
-				except:  # it failed, so it must be a broken link
-					pass
+				self.stat = self.preload[0]
+			self.preload = None
+		else:
+			try:
+				self.stat = os.lstat(self.path)
+			except:
+				pass
+			else:
+				self.is_link = S_ISLNK(self.stat.st_mode)
+				if self.is_link:
+					try:
+						self.stat = os.stat(self.path)
+					except:
+						pass
+
+		# Set some attributes
+		if self.stat:
 			mode = self.stat.st_mode
-			self.is_device = bool(stat.S_ISCHR(mode) or stat.S_ISBLK(mode))
-			self.is_socket = bool(stat.S_ISSOCK(mode))
-			self.is_fifo = bool(stat.S_ISFIFO(mode))
-			self.accessible = True
-
-		if self.accessible and os.access(self.path, os.F_OK):
-			self.exists = True
+			self.is_device = bool(S_ISCHR(mode) or S_ISBLK(mode))
+			self.is_socket = bool(S_ISSOCK(mode))
+			self.is_fifo = bool(S_ISFIFO(mode))
 			self.accessible = True
-			if os.path.isdir(self.path):
-				self.type = T_DIRECTORY
-				self.runnable = bool(mode & stat.S_IXUSR)
-			elif os.path.isfile(self.path):
-				self.type = T_FILE
+			if os.access(self.path, os.F_OK):
+				self.exists = True
+				if S_ISDIR(mode):
+					self.runnable = bool(mode & S_IXUSR)
 			else:
-				self.type = T_UNKNOWN
-
+				self.exists = False
+				self.runnable = False
+			if self.is_link:
+				self.realpath = realpath(self.path)
+				self.readlink = os.readlink(self.path)
 		else:
-			self.type = T_NONEXISTANT
-			self.exists = False
-			self.runnable = False
+			self.accessible = False
+
 		self.determine_infostring()
 
 	def get_permission_string(self):
@@ -237,9 +254,9 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 		except:
 			return '----??----'
 
-		if stat.S_ISDIR(mode):
+		if S_ISDIR(mode):
 			perms = ['d']
-		elif stat.S_ISLNK(mode):
+		elif S_ISLNK(mode):
 			perms = ['l']
 		else:
 			perms = ['-']
diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py
index f3955825..4a2860b8 100644
--- a/ranger/gui/mouse_event.py
+++ b/ranger/gui/mouse_event.py
@@ -21,6 +21,7 @@ class MouseEvent(object):
 			curses.BUTTON2_PRESSED,
 			curses.BUTTON3_PRESSED,
 			curses.BUTTON4_PRESSED ]
+	CTRL_SCROLLWHEEL_MULTIPLIER = 5
 
 	def __init__(self, getmouse):
 		"""Creates a MouseEvent object from the result of win.getmouse()"""
@@ -42,11 +43,15 @@ class MouseEvent(object):
 			return False
 
 	def mouse_wheel_direction(self):
+		"""Returns the direction of the scroll action, 0 if there was none"""
+		# If the bstate > ALL_MOUSE_EVENTS, it's an invalid mouse button.
+		# I interpret invalid buttons as "scroll down" because all tested
+		# systems have a broken curses implementation and this is a workaround.
 		if self.bstate & curses.BUTTON4_PRESSED:
-			return -1
+			return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1
 		elif self.bstate & curses.BUTTON2_PRESSED \
 				or self.bstate > curses.ALL_MOUSE_EVENTS:
-			return 1
+			return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1
 		else:
 			return 0
 
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index e40003a2..e9c20395 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -185,12 +185,12 @@ class UI(DisplayableContainer):
 					keys = [27, keys[1] - 128]
 			self.handle_keys(*keys)
 			self.set_load_mode(previous_load_mode)
-			if self.settings.flushinput:
+			if self.settings.flushinput and not self.console.visible:
 				curses.flushinp()
 		else:
 			# Handle simple key presses, CTRL+X, etc here:
 			if key > 0:
-				if self.settings.flushinput:
+				if self.settings.flushinput and not self.console.visible:
 					curses.flushinp()
 				if key == curses.KEY_MOUSE:
 					self.handle_mouse()
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index e4ba4b64..6e020e5f 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -87,7 +87,8 @@ class BrowserColumn(Pager):
 
 	def click(self, event):
 		"""Handle a MouseEvent"""
-		if not (event.pressed(1) or event.pressed(3)):
+		direction = event.mouse_wheel_direction()
+		if not (event.pressed(1) or event.pressed(3) or direction):
 			return False
 
 		if self.target is None:
@@ -97,7 +98,12 @@ class BrowserColumn(Pager):
 			if self.target.accessible and self.target.content_loaded:
 				index = self.scroll_begin + event.y - self.y
 
-				if event.pressed(1):
+				if direction:
+					if self.level == -1:
+						self.fm.move_parent(direction)
+					else:
+						return False
+				elif event.pressed(1):
 					if not self.main_column:
 						self.fm.enter_dir(self.target.path)
 
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index 33418a2f..a90231f2 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -24,7 +24,7 @@ from ..displayable import DisplayableContainer
 class BrowserView(Widget, DisplayableContainer):
 	ratios = None
 	preview = True
-	preview_available = True
+	is_collapsed = False
 	draw_bookmarks = False
 	stretch_ratios = None
 	need_clear = False
@@ -196,6 +196,11 @@ class BrowserView(Widget, DisplayableContainer):
 		except:
 			pass
 
+	def _collapse(self):
+		# Should the last column be cut off? (Because there is no preview)
+		return self.settings.collapse_preview and self.preview and \
+			not self.columns[-1].has_preview() and self.stretch_ratios
+
 	def resize(self, y, x, hei, wid):
 		"""Resize all the columns according to the given ratio"""
 		DisplayableContainer.resize(self, y, x, hei, wid)
@@ -203,10 +208,8 @@ class BrowserView(Widget, DisplayableContainer):
 		pad = 1 if borders else 0
 		left = pad
 
-		cut_off_last = self.preview and not self.preview_available \
-				and self.stretch_ratios
-
-		if cut_off_last:
+		self.is_collapsed = self._collapse()
+		if self.is_collapsed:
 			generator = enumerate(self.stretch_ratios)
 		else:
 			generator = enumerate(self.ratios)
@@ -232,12 +235,12 @@ class BrowserView(Widget, DisplayableContainer):
 			left += wid
 
 	def click(self, event):
-		n = event.ctrl() and 5 or 1
+		if DisplayableContainer.click(self, event):
+			return True
 		direction = event.mouse_wheel_direction()
 		if direction:
 			self.main_column.scroll(direction)
-		else:
-			DisplayableContainer.click(self, event)
+		return False
 
 	def open_pager(self):
 		self.pager.visible = True
@@ -263,8 +266,5 @@ class BrowserView(Widget, DisplayableContainer):
 
 	def poke(self):
 		DisplayableContainer.poke(self)
-		if self.settings.collapse_preview and self.preview:
-			has_preview = self.columns[-1].has_preview()
-			if self.preview_available != has_preview:
-				self.preview_available = has_preview
-				self.resize(self.y, self.x, self.hei, self.wid)
+		if self.preview and self.is_collapsed != self._collapse():
+			self.resize(self.y, self.x, self.hei, self.wid)
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 59a779de..fa9e438e 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -452,17 +452,29 @@ class OpenConsole(ConsoleWithTab):
 					if file.shell_escaped_basename.startswith(start_of_word))
 
 	def _substitute_metachars(self, command):
-		dct = {}
+		macros = {}
 
 		if self.fm.env.cf:
-			dct['f'] = shell_quote(self.fm.env.cf.basename)
+			macros['f'] = shell_quote(self.fm.env.cf.basename)
 		else:
-			dct['f'] = ''
+			macros['f'] = ''
 
-		dct['s'] = ' '.join(shell_quote(fl.basename) \
+		macros['s'] = ' '.join(shell_quote(fl.basename) \
 				for fl in self.fm.env.get_selection())
 
-		return _CustomTemplate(command).safe_substitute(dct)
+		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:
diff --git a/ranger/help/__init__.py b/ranger/help/__init__.py
index 1a651d56..f304c7bc 100644
--- a/ranger/help/__init__.py
+++ b/ranger/help/__init__.py
@@ -24,7 +24,8 @@ NO_HELP = """No help was found.
 Possibly the program was invoked with "python -OO" which
 discards all documentation."""
 
-HELP_TOPICS = ('index', 'movement', 'starting', 'console', 'fileop')
+HELP_TOPICS = ('index', 'movement', 'starting', 'console', 'fileop',
+		'invocation')
 
 def get_docstring_of_module(path, module_name):
 	imported = __import__(path, fromlist=[module_name])
diff --git a/ranger/help/console.py b/ranger/help/console.py
index 04372f38..d1764841 100644
--- a/ranger/help/console.py
+++ b/ranger/help/console.py
@@ -19,6 +19,7 @@
 3.2. List of Commands
 3.3. The (Quick) Command Console
 3.4. The Open Console
+3.5. The Quick Open Console
 
 
 ==============================================================================
@@ -144,9 +145,9 @@ 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_open method will be specially
+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_open is called after each key press and if it returns True, the
+quick() is called after each key press and if it returns True, the
 command will be executed immediately.
 
 
@@ -163,8 +164,21 @@ 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.
 
-%f will be replaced with the basename of the highlighted file
-%s will be selected with all files in the selection
+Like in similar filemanagers there are some macros.  Use them in
+commands and they will be replaced with a list of files.
+	%f	the highlighted file
+	%d	the path of the current directory
+	%s	the selected files in the current directory.  If no files are
+		selected, it defaults to the same as %f
+	%t	all tagged files in the current directory
+	%c	the full paths of the currently copied/cut files
+
+%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:
 
@@ -175,7 +189,12 @@ There is a special syntax for more control:
 Those two can be combinated:
 
 !d!@mplayer    will open the selection with a detached mplayer
-               (again, this is equivalent to !d!mplayer %s)
+	       (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
 
@@ -207,7 +226,7 @@ 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)
+			 process (flag D) but discard the output (flag s)
 
 
 ==============================================================================
diff --git a/ranger/help/index.py b/ranger/help/index.py
index 794e3ea9..316e975f 100644
--- a/ranger/help/index.py
+++ b/ranger/help/index.py
@@ -26,6 +26,7 @@
 	|2?|	Running Files
 	|3?|	The console
 	|4?|	File operations
+	|5?|	Ranger invocation
 
 
 ==============================================================================
diff --git a/ranger/help/invocation.py b/ranger/help/invocation.py
new file mode 100644
index 00000000..3de574cc
--- /dev/null
+++ b/ranger/help/invocation.py
@@ -0,0 +1,106 @@
+# 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/>.
+"""
+5. Ranger invocation
+
+5.1. Command Line Arguments
+5.2. Python Options
+
+
+==============================================================================
+5.1. Command Line Arguments
+
+These options can be passed to ranger when starting it from the
+command line.
+
+--version
+      Print the version and exit.
+
+-h, --help
+      Print a list of options and exit.
+
+-d, --debug
+      Activate the debug mode:  Whenever an error occurs, ranger
+      will exit and print a full backtrace.  The default behaviour
+      is to merely print the name of the exception in the statusbar/log
+      and to try to keep running.
+
+-c, --clean
+      Activate the clean mode:  Ranger will not access or create any
+      configuration files nor will it leave any traces on your system.
+      This is useful when your configuration is broken, when you want
+      to avoid clutter, etc.
+
+--fail-if-run
+      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.
+
+-r <dir>, --confdir=<dir>
+      Define a different configuration directory.  The default is
+      $HOME/.ranger.
+
+-m <n>, --mode=<n>
+      When a filename is supplied, make it run in mode <n> |2|
+
+-f <flags>, --flags=<flags>
+      When a filename is supplied, run it with the flags <flags> |2|
+
+(Optional) Positional Argument
+      The positional argument should be a path to the directory you
+      want ranger to start in, or the file which you want to run.
+      Only one positional argument is accepted as of now.
+
+--
+      Stop looking for options.  All following arguments are treated as
+      positional arguments.
+
+Examples:
+      ranger episode1.avi
+      ranger --debug /usr/bin
+      ranger --confdir=~/.config/ranger --fail-if-run
+
+
+==============================================================================
+5.2. Python Options
+
+Ranger makes use of python optimize flags.  To use them, run ranger like this:
+      PYTHONOPTIMIZE=1 ranger
+An alternative is:
+      python -O `which ranger`
+Or you could change the first line of the ranger script and add -O/-OO.
+The first way is the recommended one.  Of course you can make an alias or
+a shell fuction to save typing.
+
+Using PYTHONOPTIMIZE=1 (-O) will make python discard assertion statements.
+Assertions are little pieces of code which are helpful for finding errors,
+but unless you're touching sensitive parts of ranger, you may want to
+disable them to save some computing power.
+
+Using PYTHONOPTIMIZE=2 (-OO) will additionally discard any docstrings.
+In ranger, most built-in documentation (F1/? keys) is implemented with
+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
+
+Note: The author expected "-OO" to reduce the memory usage, but that
+doesn't seem to happen.
+
+
+==============================================================================
+"""
+# vim:tw=78:sw=8:sts=8:ts=8:ft=help
diff --git a/ranger/help/movement.py b/ranger/help/movement.py
index e85bf336..194dd3a9 100644
--- a/ranger/help/movement.py
+++ b/ranger/help/movement.py
@@ -19,10 +19,11 @@
 1.1. Move around
 1.2. Browser control
 1.3. Searching
-1.4. Cycling
+1.4. Sorting
 1.5. Bookmarks
 1.6. Tabs
 1.7. Mouse usage
+1.8. Misc keys
 
 
 ==============================================================================
@@ -64,6 +65,15 @@ These keys work like in vim:
 	^B      move up by one screen
 	^F      move down by one screen
 
+This keys can be used to make movements beyond the current directory
+
+	]	move down in the parent directory
+	[	move up in the parent directory
+
+	}	traverse the directory tree, visiting each directory
+	{	traverse in the other direction. (not implemented yet,
+		currently this only moves back in history)
+
 
 ==============================================================================
 1.2. Browser control
@@ -74,6 +84,7 @@ These keys work like in vim:
 	^L	redraw the window
 	:	open the console |3?|
 	z	toggle options
+	u	undo certain things (unyank, unmark,...)
 
 	i	inspect the content of the file
 	E	edit the file
@@ -88,7 +99,7 @@ of the file you're pointing at.
 	V	remove all marks
 
 By "tagging" files, you can highlight them and mark them to be
-special in whatever context you want.
+special in whatever context you want.  Tags are persistent across sessions.
 
 	t	tag/untag the selection
 	T	untag the selection
@@ -115,10 +126,10 @@ visible files. Pressing "n" will move you to the next occurance,
 "N" to the previous one.
 
 You can search for more than just strings:
-	ct	search tagged files
-	cc	cycle through all files by their ctime (last modification)
+	cc	cycle through all files by their ctime (last inode change)
 	cm	cycle by mime type, connecting similar files
 	cs	cycle by size, large items first
+	ct	search tagged files
 
 
 ==============================================================================
@@ -139,7 +150,9 @@ be reversed.
 1.5. Bookmarks
 
 Type "m<key>" to bookmark the current directory. You can re-enter this
-directory by typing "`<key>". <key> can be any letter or digit.
+directory by typing "`<key>". <key> can be any letter or digit.  Unlike vim,
+both lowercase and uppercase bookmarks are persistent.
+
 Each time you jump to a bookmark, the special bookmark at key ` will be set
 to the last directory. So typing "``" gets you back to where you were before.
 
@@ -155,8 +168,9 @@ In Ranger, tabs are very simple though and only store the directory path.
 	gt	Go to the next tab. (also TAB)
 	gT	Go to the previous tab. (also Shift+TAB)
 	gn, ^N	Create a new tab
-	g<N>	Open a tab. N has to be a number from 0 to 9.
+	g<N>	Open a tab. N has to be a number from 1 to 9.
 		If the tab doesn't exist yet, it will be created.
+		On most terminals, Alt-1, Alt-2, etc., also work.
 	gc, ^W	Close the current tab.  The last tab cannot be closed.
 
 
@@ -172,5 +186,16 @@ Clicking into the preview window will usually run the file. |2?|
 
 
 ==============================================================================
+1.8 Misc keys
+
+	^P	Display the message log
+	du	Display the disk usage of the current directory
+	cd	Open the console with ":cd "
+	cw	Open the console with ":rename "
+	A	Open the console with ":rename <current filename>"
+	I	Same as A, put the cursor at the beginning of the filename
+
+
+==============================================================================
 """
 # vim:tw=78:sw=4:sts=8:ts=8:ft=help
diff --git a/ranger/help/starting.py b/ranger/help/starting.py
index 8fd8b0da..069d6a04 100644
--- a/ranger/help/starting.py
+++ b/ranger/help/starting.py
@@ -37,7 +37,7 @@ use them.  Otherwise use the file under the cursor.
 
 
 ==============================================================================
-2.2. open with:
+2.2. The "open with" prompt
 
 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.
diff --git a/ranger/shared/mimetype.py b/ranger/shared/mimetype.py
index 1a7f79a0..c6577056 100644
--- a/ranger/shared/mimetype.py
+++ b/ranger/shared/mimetype.py
@@ -17,9 +17,7 @@ from ranger import relpath
 import mimetypes
 class MimeTypeAware(object):
 	mimetypes = {}
-	__initialized = False
 	def __init__(self):
-		if not MimeTypeAware.__initialized:
-			MimeTypeAware.mimetypes = mimetypes.MimeTypes()
-			MimeTypeAware.mimetypes.read(relpath('data/mime.types'))
-			MimeTypeAware.__initialized = True
+		MimeTypeAware.__init__ = lambda _: None  # refuse multiple inits
+		MimeTypeAware.mimetypes = mimetypes.MimeTypes()
+		MimeTypeAware.mimetypes.read(relpath('data/mime.types'))