summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--HACKING.md94
-rw-r--r--Makefile4
-rw-r--r--README.md (renamed from README)11
-rw-r--r--doc/HACKING74
-rw-r--r--doc/examples/plugin_file_filter.py18
-rw-r--r--doc/examples/vim_file_chooser.vim2
-rw-r--r--doc/howto-publish-a-release.txt27
-rw-r--r--doc/ranger.1137
-rw-r--r--doc/ranger.pod153
-rw-r--r--doc/rifle.14
-rw-r--r--doc/screenshot.pngbin0 -> 55571 bytes
-rwxr-xr-xdoc/tools/convert_papermode_to_metadata.py69
-rwxr-xr-xranger.py2
-rw-r--r--ranger/__init__.py4
-rw-r--r--ranger/api/commands.py15
-rw-r--r--ranger/api/options.py2
-rw-r--r--ranger/colorschemes/default.py2
-rw-r--r--ranger/colorschemes/jungle.py2
-rw-r--r--ranger/colorschemes/snow.py2
-rw-r--r--ranger/colorschemes/solarized.py132
-rw-r--r--ranger/config/commands.py186
-rw-r--r--ranger/config/rc.conf52
-rw-r--r--ranger/config/rifle.conf18
-rw-r--r--ranger/container/bookmarks.py2
-rw-r--r--ranger/container/directory.py118
-rw-r--r--ranger/container/file.py4
-rw-r--r--ranger/container/fsobject.py62
-rw-r--r--ranger/container/history.py2
-rw-r--r--ranger/container/settings.py5
-rw-r--r--ranger/container/tags.py2
-rw-r--r--ranger/core/actions.py66
-rw-r--r--ranger/core/environment.py2
-rw-r--r--ranger/core/fm.py18
-rw-r--r--ranger/core/loader.py2
-rw-r--r--ranger/core/main.py4
-rw-r--r--ranger/core/metadata.py157
-rw-r--r--ranger/core/runner.py6
-rw-r--r--ranger/core/shared.py2
-rw-r--r--ranger/core/tab.py2
-rw-r--r--ranger/data/mime.types22
-rw-r--r--ranger/ext/accumulator.py2
-rw-r--r--ranger/ext/cached_function.py2
-rw-r--r--ranger/ext/curses_interrupt_handler.py2
-rw-r--r--ranger/ext/direction.py7
-rw-r--r--ranger/ext/get_executables.py2
-rw-r--r--ranger/ext/human_readable.py2
-rw-r--r--ranger/ext/iter_tools.py2
-rw-r--r--ranger/ext/keybinding_parser.py2
-rw-r--r--ranger/ext/mount_path.py2
-rw-r--r--ranger/ext/next_available_filename.py2
-rw-r--r--ranger/ext/openstruct.py17
-rw-r--r--ranger/ext/popen_forked.py2
-rw-r--r--ranger/ext/relative_symlink.py2
-rwxr-xr-xranger/ext/rifle.py13
-rw-r--r--ranger/ext/shell_escape.py2
-rw-r--r--ranger/ext/signals.py2
-rw-r--r--ranger/ext/spawn.py2
-rw-r--r--ranger/ext/widestring.py2
-rw-r--r--ranger/gui/ansi.py2
-rw-r--r--ranger/gui/bar.py2
-rw-r--r--ranger/gui/color.py2
-rw-r--r--ranger/gui/colorscheme.py7
-rw-r--r--ranger/gui/context.py2
-rw-r--r--ranger/gui/curses_shortcuts.py2
-rw-r--r--ranger/gui/displayable.py2
-rw-r--r--ranger/gui/mouse_event.py2
-rw-r--r--ranger/gui/ui.py8
-rw-r--r--ranger/gui/widgets/browsercolumn.py51
-rw-r--r--ranger/gui/widgets/browserview.py2
-rw-r--r--ranger/gui/widgets/console.py36
-rw-r--r--ranger/gui/widgets/pager.py2
-rw-r--r--ranger/gui/widgets/statusbar.py2
-rw-r--r--ranger/gui/widgets/taskview.py2
-rw-r--r--ranger/gui/widgets/titlebar.py7
-rwxr-xr-xsetup.py4
76 files changed, 1405 insertions, 285 deletions
diff --git a/.gitignore b/.gitignore
index 8c110f00..5357ead2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 *.pyc
 *.pyo
 stuff/*
+doc/ranger.1.html
+build
diff --git a/HACKING.md b/HACKING.md
new file mode 100644
index 00000000..0b14e38d
--- /dev/null
+++ b/HACKING.md
@@ -0,0 +1,94 @@
+Guidelines for Code Modification
+================================
+
+Coding Style
+------------
+
+* Use syntax compatible to both python 2.6+ and 3.1+.
+* Use docstrings with pydoc in mind
+* Follow the style guide for python code:
+    http://www.python.org/dev/peps/pep-0008/
+* Test the code with "doctest" where it makes sense
+
+
+Patches
+-------
+
+Send patches, created with "git format-patch", to the email address
+
+    hut@hut.pm
+
+or open a pull request on GitHub.
+
+
+Version Numbering
+-----------------
+
+Three numbers, A.B.C, where
+* A changes on a rewrite
+* B changes when major configuration incompatibilities occur
+* C changes with each release
+
+
+Starting Points
+---------------
+
+Good places to read about ranger internals are:
+
+* ranger/core/actions.py
+* ranger/container/fsobject.py
+
+About the UI:
+
+* ranger/gui/widgets/browsercolumn.py
+* ranger/gui/widgets/browserview.py
+* ranger/gui/ui.py
+
+
+Common Changes
+==============
+
+Adding options
+--------------
+
+* Add a default value in rc.conf, along with a comment that describes the option.
+* Add the option to the ALLOWED_SETTINGS dictionary in the file
+  ranger/container/settings.py.  Make sure to sort in the new entry
+  alphabetically.
+* Add an entry in the man page by editing doc/ranger.pod, then rebuild the man
+  page by running "make man" in the ranger root directory
+
+The setting is now accessible with self.settings.my_option, assuming self is a
+subclass of ranger.core.shared.SettingsAware.
+
+
+Adding colorschemes
+-------------------
+
+* Copy ranger/colorschemes/default.py to ranger/colorschemes/myscheme.py
+  and modify it according to your needs.  Alternatively, create a subclass of
+  ranger.colorschemes.default.Default and override the "use" method, as it is
+  done in the "Jungle" colorscheme.
+
+* Add this line to your ~/.config/ranger/rc.conf:
+
+    set colorscheme myscheme
+
+
+Change which programs start which file types
+--------------------------------------------
+
+Edit the configuration file ~/.config/ranger/rifle.conf.  The default one can
+be obtained by running "ranger --copy-config rifle".
+
+
+Change which file extensions have which mime type
+-------------------------------------------------
+
+Modify ranger/data/mime.types.  You may also add your own entries to ~/.mime.types
+
+
+Change which files are previewed in the auto preview
+----------------------------------------------------
+
+In ranger/container/file.py, change the constant PREVIEW_BLACKLIST
diff --git a/Makefile b/Makefile
index 0a79142e..6c31bb0f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 NAME = ranger
-VERSION = $(shell grep -m 1 -o '[0-9][0-9.]\+' README)
+VERSION = $(shell grep -m 1 -o '[0-9][0-9.]\+' README.md)
 NAME_RIFLE = rifle
 VERSION_RIFLE = $(VERSION)
 SNAPSHOT_NAME ?= $(NAME)-$(VERSION)-$(shell git rev-parse HEAD | cut -b 1-8).tar.gz
diff --git a/README b/README.md
index aa16ad03..0e0e3c02 100644
--- a/README
+++ b/README.md
@@ -5,8 +5,10 @@ minimalistic and nice curses interface with a view on the directory hierarchy.
 It ships with "rifle", a file launcher that is good at automatically finding
 out which program to use for what file type.
 
+![screenshot](doc/screenshot.png)
+
 This file describes ranger and how to get it to run.  For instructions on the
-usage, please read the man page.  See doc/HACKING for development specific
+usage, please read the man page.  See HACKING.md for development specific
 information.  For configuration, check the files in ranger/config/.  They
 are usually installed to /usr/lib/python*/site-packages/ranger/config/
 and can be obtained with ranger's --copy-config option.  The doc/examples/
@@ -21,10 +23,9 @@ About
 -----
 * Authors:     Check the copyright notices in each source file
 * License:     GNU General Public License Version 3
-
 * Website:     http://ranger.nongnu.org/
 * Download:    http://ranger.nongnu.org/ranger-stable.tar.gz
-* Bug reports: https://savannah.nongnu.org/bugs/?group=ranger&func=additem
+* Bug reports: https://github.com/hut/ranger/issues
 * git clone    http://git.sv.gnu.org/r/ranger.git
 
 
@@ -77,10 +78,10 @@ Use the package manager of your operating system to install ranger.
 Note that ranger can be started without installing by simply running ranger.py.
 
 To install ranger manually:
-    sudo make install
+> sudo make install
 
 This translates roughly to:
-    sudo python setup.py install --optimize=1 --record=install_log.txt
+> sudo python setup.py install --optimize=1 --record=install_log.txt
 
 This also saves a list of all installed files to install_log.txt, which you can
 use to uninstall ranger.
diff --git a/doc/HACKING b/doc/HACKING
deleted file mode 100644
index 68a1a942..00000000
--- a/doc/HACKING
+++ /dev/null
@@ -1,74 +0,0 @@
-Guidelines on Code Modification
-===============================
-
-Coding Style
-------------
-
-* Use syntax compatible to both python 2.6 and 3.1.
-* Use docstrings with pydoc in mind
-* Follow the style guide for python code:
-    http://www.python.org/dev/peps/pep-0008/
-* Test the code with "doctest" where it makes sense
-
-
-Patches
--------
-
-Send patches, created with "git format-patch", to the email adress
-
-    hut@lepus.uberspace.de
-
-If you plan to do major changes, or many changes over time, I encourage
-you to create a fork on GitHub, Gitorious or any other site.
-
-
-Starting Points
----------------
-
-Good places to read about ranger internals are:
-ranger/core/actions.py
-ranger/container/fsobject.py
-
-About the UI:
-ranger/gui/widgets/browsercolumn.py
-ranger/gui/widgets/browserview.py
-ranger/gui/ui.py
-
-
-Common Changes
---------------
-
-* Change which files are previewed in the auto preview:
-In ranger/container/file.py
-the constant PREVIEW_BLACKLIST
-
-* Adding options:
-In ranger/config/rc.conf
-add the default value, like: my_option = True
-In ranger/container/settings.py
-add the name of your option to the constant ALLOWED_SETTINGS
-
-The setting is now accessible at self.settings.my_option,
-assuming <self> is a "SettingsAware" object.
-
-* Adding colorschemes:
-Copy ranger/colorschemes/default.py to ranger/colorschemes/myscheme.py
-and modify it according to your needs.  Alternatively, mimic the jungle
-colorscheme.  It subclasses the default scheme and just modifies a few things.
-In ranger/config/rc.conf (or ~/.config/ranger/rc.conf), add the line:
-
-    set colorscheme myscheme
-
-* Change the file type => application associations:
-Edit the configuration file ~/.config/ranger/rifle.conf.  The default one can
-be obtained by running "ranger --copy-config rifle".
-
-* Change the file extension => mime type associations:
-Modify ranger/data/mime.types
-
-
-Version Numbering
------------------
-
-Three numbers;  The first changes on a rewrite, the second changes when major
-configuration incompatibilities occur and the third changes with each release.
diff --git a/doc/examples/plugin_file_filter.py b/doc/examples/plugin_file_filter.py
index bad5a368..b9bea1f3 100644
--- a/doc/examples/plugin_file_filter.py
+++ b/doc/examples/plugin_file_filter.py
@@ -1,18 +1,20 @@
-# Compatible with ranger 1.6.*
+# Compatible since ranger 1.6.1, git commit c82a8a76989c
 #
-# This plugin hides the directories "boot", "sbin", "proc" and "sys" in the
-# root directory.
+# This plugin hides the directories "/boot", "/sbin", "/proc" and "/sys" unless
+# the "show_hidden" option is activated.
 
 # Save the original filter function
 import ranger.container.directory
 old_accept_file = ranger.container.directory.accept_file
 
+HIDE_FILES = ("/boot", "/sbin", "/proc", "/sys")
+
 # Define a new one
-def custom_accept_file(fname, directory, hidden_filter, name_filter):
-       if hidden_filter and directory.path == '/' and fname in ('boot', 'sbin', 'proc', 'sys'):
-               return False
-       else:
-               return old_accept_file(fname, directory, hidden_filter, name_filter)
+def custom_accept_file(file, filters):
+    if not file.fm.settings.show_hidden and file.path in HIDE_FILES:
+        return False
+    else:
+        return old_accept_file(file, filters)
 
 # Overwrite the old function
 import ranger.container.directory
diff --git a/doc/examples/vim_file_chooser.vim b/doc/examples/vim_file_chooser.vim
index 68947d2d..df6aacc9 100644
--- a/doc/examples/vim_file_chooser.vim
+++ b/doc/examples/vim_file_chooser.vim
@@ -14,11 +14,13 @@ function! RangeChooser()
     "exec 'silent !ranger --choosefile=' . shellescape(temp)
     exec 'silent !ranger --choosefiles=' . shellescape(temp)
     if !filereadable(temp)
+        redraw!
         " Nothing to read.
         return
     endif
     let names = readfile(temp)
     if empty(names)
+        redraw!
         " Nothing to open.
         return
     endif
diff --git a/doc/howto-publish-a-release.txt b/doc/howto-publish-a-release.txt
new file mode 100644
index 00000000..2ef99afe
--- /dev/null
+++ b/doc/howto-publish-a-release.txt
@@ -0,0 +1,27 @@
+( ) test everything one last time:
+( ) * make test
+( ) * ./ranger.py [--clean]
+( ) * ranger/ext/rifle.py
+( ) * make install
+( ) make a release commit:
+( ) * update the number in the README
+( ) * update the number in ranger/__init__.py
+( ) * update the version number in ranger/ext/rifle.py
+( ) * rebuild the man page with the updated number
+( ) * write changelog entry
+( ) * think of a witty commit message
+( ) * change VERSION in ranger/__init__.py to something with "stable"
+( ) * push the commit
+( ) build .tar.gz with "make snapshot"
+( ) make, make install and test the snapshot one last time
+( ) update the website:
+( ) * add the new version as ranger-stable.tar.gz
+( ) * add the new version as ranger-X.Y.Z.tar.gz
+( ) * update both signatures (gpg -sb <file>)
+( ) * update the changelog
+( ) * update the man page
+( ) * rerun boobies.py
+( ) announce the update
+( ) * to the mailing list
+( ) * in the arch linux forum
+( ) * write a news entry on savannah
diff --git a/doc/ranger.1 b/doc/ranger.1
index c0971d47..b27aa9f3 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.6.1" "04/11/2014" "ranger manual"
+.TH RANGER 1 "ranger-1.6.1" "01/17/2015" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -142,7 +142,7 @@
 ranger \- visual file manager
 .SH "SYNOPSIS"
 .IX Header "SYNOPSIS"
-\&\fBranger\fR [\fB\-\-version\fR] [\fB\-\-help\fR] [\fB\-\-debug\fR] [\fB\-\-clean\fR] 
+\&\fBranger\fR [\fB\-\-version\fR] [\fB\-\-help\fR] [\fB\-\-debug\fR] [\fB\-\-clean\fR]
 [\fB\-\-confdir\fR=\fIdirectory\fR] [\fB\-\-copy\-config\fR=\fIwhich\fR]
 [\fB\-\-choosefile\fR=\fItarget\fR] [\fB\-\-choosefiles\fR=\fItarget\fR]
 [\fB\-\-choosedir\fR=\fItarget\fR] [\fB\-\-selectfile\fR=\fIfilepath\fR]
@@ -160,7 +160,7 @@ commands and \fI3?\fR for settings.
 .PP
 The \fI\s-1README\s0\fR contains install instructions.
 .PP
-The file \fIdoc/HACKING\fR contains guidelines for code modification.
+The file \fI\s-1HACKING\s0.md\fR contains guidelines for code modification.
 .PP
 The directory \fIdoc/configs\fR contains configuration files.  They are usually
 installed to \fI/usr/lib/python*/site\-packages/ranger/config\fR and can be
@@ -235,7 +235,7 @@ efficiently.
 .SS "\s-1TAGS\s0"
 .IX Subsection "TAGS"
 Tags are single characters which are displayed left of a filename.  You can use
-tags however you want.  Press \*(L"t\*(R" to toggle tags and \*(L"T\*(R" to remove any tags of
+tags however you want.  Press \*(L"t\*(R" to toggle tags and \*(L"ut\*(R" to remove any tags of
 the selection. The default tag is an Asterisk (\*(L"*\*(R"), but you can use any tag by
 typing \fI"<tagname>\fR.
 .SS "\s-1PREVIEWS\s0"
@@ -411,6 +411,8 @@ Move to the top
 .IP "G" 14
 .IX Item "G"
 Move to the bottom
+.IP "[, ]" 14
+Move up and down in the parent directory.
 .IP "^R" 14
 .IX Item "^R"
 Reload everything
@@ -419,7 +421,7 @@ Reload everything
 Redraw the screen
 .IP "i" 14
 .IX Item "i"
-Display the current file in a bigger window.
+Inspect the current file in a bigger window.
 .IP "E" 14
 .IX Item "E"
 Edit the current file in \f(CW$EDITOR\fR (\*(L"nano\*(R" by default)
@@ -428,6 +430,19 @@ Edit the current file in \f(CW$EDITOR\fR (\*(L"nano\*(R" by default)
 Open a shell in the current directory
 .IP "?" 14
 Opens this man page
+.IP "W" 14
+.IX Item "W"
+Opens the log window where you can review messages that pop up at the bottom.
+.IP "w" 14
+.IX Item "w"
+Opens the task window where you can view and modify background processes that
+currently run in ranger.  In there, you can type \*(L"dd\*(R" to abort a process and
+\&\*(L"J\*(R" or \*(L"K\*(R" to change the priority of a process.  Only one process is run at a
+time.
+.IP "^C" 14
+.IX Item "^C"
+Stop the currently running background process that ranger has started, like
+copying files, loading directories or file previews.
 .IP "<octal>=, +<who><what>, \-<who><what>" 14
 .IX Item "<octal>=, +<who><what>, -<who><what>"
 Change the permissions of the selection.  For example, \f(CW\*(C`777=\*(C'\fR is equivalent to
@@ -445,6 +460,16 @@ modern \s-1GUI\s0 programs.
 .IP "po" 14
 .IX Item "po"
 Paste the copied/cut files, overwriting existing files.
+.IP "pl, pL" 14
+.IX Item "pl, pL"
+Create symlinks (absolute or relative) to the copied files
+.IP "phl" 14
+.IX Item "phl"
+Create hardlinks to the copied files
+.IP "pht" 14
+.IX Item "pht"
+Duplicate the subdirectory tree of the copied directory, then create
+hardlinks for each contained file into the new directory tree.
 .IP "m\fIX\fR" 14
 .IX Item "mX"
 Create a bookmark with the name \fIX\fR
@@ -488,6 +513,19 @@ the cursor until you press \s-1ESC. \s0 To unselect files in the same way, use \
 Search for files in the current directory.
 .IP ":" 14
 Open the console.
+.IP "!" 14
+Open the console with the content \*(L"shell \*(R" so you can quickly run commands
+.IP "@" 14
+Open the console with the content \*(L"shell  \f(CW%s\fR\*(R", placing the cursor before the
+\&\*(L" \f(CW%s\fR\*(R" so you can quickly run commands with the current selection as the
+argument.
+.IP "r" 14
+.IX Item "r"
+Open the console with the content \*(L"open with \*(R" so you can decide which program
+to use to open the current file selection.
+.IP "cd" 14
+.IX Item "cd"
+Open the console with the content \*(L"cd \*(R"
 .IP "Alt\-\fIN\fR" 14
 .IX Item "Alt-N"
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -501,6 +539,11 @@ Go to the next or previous tab. You can also use \s-1TAB\s0 and \s-1SHIFT+TAB\s0
 .IP "gc, ^W" 14
 .IX Item "gc, ^W"
 Close the current tab.  The last tab cannot be closed this way.
+.IP "M" 14
+.IX Item "M"
+A key chain that allows you to quickly change the line mode of all the files of
+the current directory.  For a more permanent solution, use the command
+\&\*(L"default_linemode\*(R" in your rc.conf.
 .SS "READLINE-LIKE \s-1BINDINGS IN THE CONSOLE\s0"
 .IX Subsection "READLINE-LIKE BINDINGS IN THE CONSOLE"
 .IP "^B, ^F" 14
@@ -631,6 +674,11 @@ this pattern will hide all files that start with a dot or end with a tilde.
 .Vb 1
 \& set hidden_filter ^\e.|~$
 .Ve
+.IP "idle_delay [integer]" 4
+.IX Item "idle_delay [integer]"
+The delay that ranger idly waits for user input, in milliseconds, with a
+resolution of 100ms.  Lower delay reduces lag between directory updates but
+increases \s-1CPU\s0 load.
 .IP "max_console_history_size [integer, none]" 4
 .IX Item "max_console_history_size [integer, none]"
 How many console commands should be kept in history?  \*(L"none\*(R" will disable the
@@ -638,6 +686,11 @@ limit.
 .IP "max_history_size [integer, none]" 4
 .IX Item "max_history_size [integer, none]"
 How many directory changes should be kept in history?
+.IP "metadata_deep_search [bool]" 4
+.IX Item "metadata_deep_search [bool]"
+When the metadata manager module looks for metadata, should it only look for a
+\&\*(L".metadata.json\*(R" file in the current directory, or do a deep search and check
+all directories above the current one as well?
 .IP "mouse_enabled [bool] <zm>" 4
 .IX Item "mouse_enabled [bool] <zm>"
 Enable mouse input?
@@ -694,10 +747,10 @@ Sort directories first?
 .IP "sort_reverse [bool] <or>" 4
 .IX Item "sort_reverse [bool] <or>"
 Reverse the order of files?
-.IP "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>" 4
-.IX Item "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>"
+.IP "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>, <oz>" 4
+.IX Item "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>, <oz>"
 Which sorting mechanism should be used?  Choose one of \fBatime\fR, \fBbasename\fR,
-\&\fBctime\fR, \fBmtime\fR, \fBnatural\fR, \fBtype\fR, \fBsize\fR
+\&\fBctime\fR, \fBmtime\fR, \fBnatural\fR, \fBtype\fR, \fBsize\fR, \fBrandom\fR
 .Sp
 Note: You can reverse the order by typing an uppercase second letter in the key
 combination, e.g. \*(L"oN\*(R" to sort from Z to A.
@@ -755,23 +808,30 @@ ranger.  For your convenience, this is a list of the \*(L"public\*(R" commands i
 \& copypmap key newkey [newkey2...]
 \& copytmap key newkey [newkey2...]
 \& cunmap keys...
+\& default_linemode [path=regexp | tag=tags] linemodename
 \& delete
 \& edit [filename]
 \& eval [\-q] python_code
 \& filter [string]
+\& filter_inode_type [dfl]
 \& find pattern
+\& flat level
 \& grep pattern
+\& linemode linemodename
 \& load_copy_buffer
 \& map key command
 \& mark pattern
 \& mark_tag [tags]
+\& meta key value
 \& mkdir dirname
 \& open_with [application] [flags] [mode]
 \& pmap key command
+\& prompt_metadata [key1 [key2 [...]]]
 \& punmap keys...
 \& quit
 \& quit!
 \& relink newpath
+\& rename_append
 \& rename newname
 \& save_copy_buffer
 \& scout [\-FLAGS] pattern
@@ -855,6 +915,20 @@ See \f(CW\*(C`copymap\*(C'\fR
 .IP "cunmap [\fIkeys...\fR]" 2
 .IX Item "cunmap [keys...]"
 Removes key mappings of the console. Works like the \f(CW\*(C`unmap\*(C'\fR command.
+.IP "default_linemode [\fIpath=regexp\fR | \fItag=tags\fR] \fIlinemodename\fR" 2
+.IX Item "default_linemode [path=regexp | tag=tags] linemodename"
+Sets the default linemode.  See \fIlinemode\fR command.
+.Sp
+Examples:
+.Sp
+Set the global default linemode to \*(L"permissions\*(R":
+ :default_linemode permissions
+.Sp
+Set the default linemode to \*(L"permissions\*(R" for all files tagged with \*(L"p\*(R" or \*(L"P\*(R":
+ :default_linemode tag=pP permissions
+.Sp
+Set the default linemode for all files in ~/books/ to \*(L"metatitle\*(R":
+ :default_linemode path=/home/.*?/books/.* metatitle
 .IP "delete" 2
 .IX Item "delete"
 Destroy all files in the selection with a roundhouse kick.  ranger will ask for
@@ -879,6 +953,12 @@ Displays only the files which contain the \fIstring\fR in their basename.  Runni
 this command without any parameter will reset the fitler.
 .Sp
 This command is based on the \fIscout\fR command and supports all of its options.
+.IP "filter_inode_type [dfl]" 2
+.IX Item "filter_inode_type [dfl]"
+Displays only the files of specified inode type. To display only directories,
+use the 'd' parameter. To display only files, use the 'f' parameter. To display
+only links, use the 'l' parameter. Parameters can be combined. To remove this
+filter, use no parameter.
 .IP "find \fIpattern\fR" 2
 .IX Item "find pattern"
 Search files in the current directory that contain the given (case-insensitive)
@@ -886,9 +966,25 @@ string in their name as you type.  Once there is an unambiguous result, it will
 be run immediately. (Or entered, if it's a directory.)
 .Sp
 This command is based on the \fIscout\fR command and supports all of its options.
+.IP "flat level" 2
+.IX Item "flat level"
+Flattens the directory view up to the specified level. Level \-1 means infinite
+level. Level 0 means standard view without flattened directory view. Level
+values \-2 and less are invalid.
 .IP "grep \fIpattern\fR" 2
 .IX Item "grep pattern"
 Looks for a string in all marked files or directories.
+.IP "linemode \fIlinemodename\fR" 2
+.IX Item "linemode linemodename"
+Sets the linemode of all files in the current directory.  The linemode may be:
+.Sp
+.Vb 5
+\& "filename": display each line as "<basename>...<size>"
+\& "permissions": display each line as "<permissions> <owner> <group> <basename>"
+\& "metatitle": display metadata from .metadata.json files if
+\&     available, fall back to the "filename" linemode if no
+\&     metadata was found.  See :meta command.
+.Ve
 .IP "load_copy_buffer" 2
 .IX Item "load_copy_buffer"
 Load the copy buffer from \fI~/.config/ranger/copy_buffer\fR.  This can be used to
@@ -912,6 +1008,17 @@ This command is based on the \fIscout\fR command and supports all of its options
 .IX Item "mark_tag [tags]"
 Mark all tags that are tagged with either of the given tags.  When leaving out
 the tag argument, all tagged files are marked.
+.IP "meta \fIkey\fR \fIvalue\fR" 2
+.IX Item "meta key value"
+Set the metadata of the currently highlighted file.  Example:
+.Sp
+.Vb 2
+\& :meta title The Hitchhiker\*(Aqs Guide to the Galaxy
+\& :meta year 1979
+.Ve
+.Sp
+This metadata can be displayed by, for example, using the \*(L"metatitle\*(R" line mode
+by typing Mt.
 .IP "mkdir \fIdirname\fR" 2
 .IX Item "mkdir dirname"
 Creates a directory with the name \fIdirname\fR.
@@ -928,6 +1035,10 @@ Note that if you specify an application, the mode is ignored.
 .IP "pmap \fIkey\fR \fIcommand\fR" 2
 .IX Item "pmap key command"
 Binds keys for the pager. Works like the \f(CW\*(C`map\*(C'\fR command.
+.IP "prompt_metadata [\fIkeys ...\fR]" 2
+.IX Item "prompt_metadata [keys ...]"
+Prompt the user to input metadata with the \f(CW\*(C`meta\*(C'\fR command for multiple keys in
+a row.
 .IP "punmap [\fIkeys ...\fR]" 2
 .IX Item "punmap [keys ...]"
 Removes key mappings of the pager. Works like the \f(CW\*(C`unmap\*(C'\fR command.
@@ -942,6 +1053,10 @@ it by typing `` or '' the next time you start ranger.
 .IX Item "relink newpath"
 Change the link destination of the current symlink file to <newpath>. First
 <tab> will load the original link.
+.IP "rename_append" 2
+.IX Item "rename_append"
+Opens the console with \*(L":rename <current file>\*(R" with the cursor automatically
+placed before the file extension
 .IP "rename \fInewname\fR" 2
 .IX Item "rename newname"
 Rename the current file.  If a file with that name already exists, the renaming
@@ -1163,6 +1278,8 @@ provided along with the source code.
 .IX Item "The project page: <http://ranger.nongnu.org/>"
 .IP "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>" 4
 .IX Item "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>"
+.IP "\s-1IRC\s0 channel: #ranger on freenode.net" 4
+.IX Item "IRC channel: #ranger on freenode.net"
 .PD
 .PP
 ranger is maintained with the git version control system.  To fetch a fresh
@@ -1176,7 +1293,7 @@ copy, run:
 \&\fIrifle\fR\|(1)
 .SH "BUGS"
 .IX Header "BUGS"
-Report bugs here: <http://savannah.nongnu.org/bugs/?group=ranger>
+Report bugs here: <https://github.com/hut/ranger/issues>
 .PP
 Please include as much relevant information as possible.  For the most
 diagnostic output, run ranger like this: \f(CW\*(C`PYTHONOPTIMIZE= ranger \-\-debug\*(C'\fR
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 7dc50d32..67297d18 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -7,7 +7,7 @@ ranger - visual file manager
 
 =head1 SYNOPSIS
 
-B<ranger> [B<--version>] [B<--help>] [B<--debug>] [B<--clean>] 
+B<ranger> [B<--version>] [B<--help>] [B<--debug>] [B<--clean>]
 [B<--confdir>=I<directory>] [B<--copy-config>=I<which>]
 [B<--choosefile>=I<target>] [B<--choosefiles>=I<target>]
 [B<--choosedir>=I<target>] [B<--selectfile>=I<filepath>]
@@ -33,7 +33,7 @@ commands and I<3?> for settings.
 
 The F<README> contains install instructions.
 
-The file F<doc/HACKING> contains guidelines for code modification.
+The file F<HACKING.md> contains guidelines for code modification.
 
 The directory F<doc/configs> contains configuration files.  They are usually
 installed to F</usr/lib/python*/site-packages/ranger/config> and can be
@@ -134,7 +134,7 @@ efficiently.
 =head2 TAGS
 
 Tags are single characters which are displayed left of a filename.  You can use
-tags however you want.  Press "t" to toggle tags and "T" to remove any tags of
+tags however you want.  Press "t" to toggle tags and "ut" to remove any tags of
 the selection. The default tag is an Asterisk ("*"), but you can use any tag by
 typing I<"<tagnameE<gt>>.
 
@@ -317,6 +317,10 @@ Move to the top
 
 Move to the bottom
 
+=item [, ]
+
+Move up and down in the parent directory.
+
 =item ^R
 
 Reload everything
@@ -327,7 +331,7 @@ Redraw the screen
 
 =item i
 
-Display the current file in a bigger window.
+Inspect the current file in a bigger window.
 
 =item E
 
@@ -341,6 +345,22 @@ Open a shell in the current directory
 
 Opens this man page
 
+=item W
+
+Opens the log window where you can review messages that pop up at the bottom.
+
+=item w
+
+Opens the task window where you can view and modify background processes that
+currently run in ranger.  In there, you can type "dd" to abort a process and
+"J" or "K" to change the priority of a process.  Only one process is run at a
+time.
+
+=item ^C
+
+Stop the currently running background process that ranger has started, like
+copying files, loading directories or file previews.
+
 =item <octal>=, +<who><what>, -<who><what>
 
 Change the permissions of the selection.  For example, C<777=> is equivalent to
@@ -363,6 +383,19 @@ modern GUI programs.
 
 Paste the copied/cut files, overwriting existing files.
 
+=item pl, pL
+
+Create symlinks (absolute or relative) to the copied files
+
+=item phl
+
+Create hardlinks to the copied files
+
+=item pht
+
+Duplicate the subdirectory tree of the copied directory, then create
+hardlinks for each contained file into the new directory tree.
+
 =item mI<X>
 
 Create a bookmark with the name I<X>
@@ -421,6 +454,25 @@ Search for files in the current directory.
 
 Open the console.
 
+=item !
+
+Open the console with the content "shell " so you can quickly run commands
+
+=item @
+
+Open the console with the content "shell  %s", placing the cursor before the
+" %s" so you can quickly run commands with the current selection as the
+argument.
+
+=item r
+
+Open the console with the content "open with " so you can decide which program
+to use to open the current file selection.
+
+=item cd
+
+Open the console with the content "cd "
+
 =item Alt-I<N>
 
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -438,6 +490,12 @@ Go to the next or previous tab. You can also use TAB and SHIFT+TAB instead.
 
 Close the current tab.  The last tab cannot be closed this way.
 
+=item M
+
+A key chain that allows you to quickly change the line mode of all the files of
+the current directory.  For a more permanent solution, use the command
+"default_linemode" in your rc.conf.
+
 =back
 
 =head2 READLINE-LIKE BINDINGS IN THE CONSOLE
@@ -603,6 +661,12 @@ this pattern will hide all files that start with a dot or end with a tilde.
 
  set hidden_filter ^\.|~$
 
+=item idle_delay [integer]
+
+The delay that ranger idly waits for user input, in milliseconds, with a
+resolution of 100ms.  Lower delay reduces lag between directory updates but
+increases CPU load.
+
 =item max_console_history_size [integer, none]
 
 How many console commands should be kept in history?  "none" will disable the
@@ -612,6 +676,12 @@ limit.
 
 How many directory changes should be kept in history?
 
+=item metadata_deep_search [bool]
+
+When the metadata manager module looks for metadata, should it only look for a
+".metadata.json" file in the current directory, or do a deep search and check
+all directories above the current one as well?
+
 =item mouse_enabled [bool] <zm>
 
 Enable mouse input?
@@ -684,10 +754,10 @@ Sort directories first?
 
 Reverse the order of files?
 
-=item sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>
+=item sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>, <oz>
 
 Which sorting mechanism should be used?  Choose one of B<atime>, B<basename>,
-B<ctime>, B<mtime>, B<natural>, B<type>, B<size>
+B<ctime>, B<mtime>, B<natural>, B<type>, B<size>, B<random>
 
 Note: You can reverse the order by typing an uppercase second letter in the key
 combination, e.g. "oN" to sort from Z to A.
@@ -723,7 +793,7 @@ Gather and display data about version control systems. Supported vcs: git, hg.
 =item vcs_backend_git, vcs_backend_hg, vcs_backend_bzr [string]
 
 Sets the state for the version control backend. The possible values are:
-    
+
  disabled   don't display any information.
  local      display only local state.
  enabled    display both, local and remote state. May be slow for hg and bzr.
@@ -755,23 +825,30 @@ ranger.  For your convenience, this is a list of the "public" commands including
  copypmap key newkey [newkey2...]
  copytmap key newkey [newkey2...]
  cunmap keys...
+ default_linemode [path=regexp | tag=tags] linemodename
  delete
  edit [filename]
  eval [-q] python_code
  filter [string]
+ filter_inode_type [dfl]
  find pattern
+ flat level
  grep pattern
+ linemode linemodename
  load_copy_buffer
  map key command
  mark pattern
  mark_tag [tags]
+ meta key value
  mkdir dirname
  open_with [application] [flags] [mode]
  pmap key command
+ prompt_metadata [key1 [key2 [...]]]
  punmap keys...
  quit
  quit!
  relink newpath
+ rename_append
  rename newname
  save_copy_buffer
  scout [-FLAGS] pattern
@@ -869,6 +946,21 @@ See C<copymap>
 
 Removes key mappings of the console. Works like the C<unmap> command.
 
+=item default_linemode [I<path=regexp> | I<tag=tags>] I<linemodename>
+
+Sets the default linemode.  See I<linemode> command.
+
+Examples:
+
+Set the global default linemode to "permissions":
+ :default_linemode permissions
+
+Set the default linemode to "permissions" for all files tagged with "p" or "P":
+ :default_linemode tag=pP permissions
+
+Set the default linemode for all files in ~/books/ to "metatitle":
+ :default_linemode path=/home/.*?/books/.* metatitle
+
 =item delete
 
 Destroy all files in the selection with a roundhouse kick.  ranger will ask for
@@ -897,6 +989,13 @@ this command without any parameter will reset the fitler.
 
 This command is based on the I<scout> command and supports all of its options.
 
+=item filter_inode_type [dfl]
+
+Displays only the files of specified inode type. To display only directories,
+use the 'd' parameter. To display only files, use the 'f' parameter. To display
+only links, use the 'l' parameter. Parameters can be combined. To remove this
+filter, use no parameter.
+
 =item find I<pattern>
 
 Search files in the current directory that contain the given (case-insensitive)
@@ -905,10 +1004,26 @@ be run immediately. (Or entered, if it's a directory.)
 
 This command is based on the I<scout> command and supports all of its options.
 
+=item flat level
+
+Flattens the directory view up to the specified level. Level -1 means infinite
+level. Level 0 means standard view without flattened directory view. Level
+values -2 and less are invalid.
+
 =item grep I<pattern>
 
 Looks for a string in all marked files or directories.
 
+=item linemode I<linemodename>
+
+Sets the linemode of all files in the current directory.  The linemode may be:
+
+ "filename": display each line as "<basename>...<size>"
+ "permissions": display each line as "<permissions> <owner> <group> <basename>"
+ "metatitle": display metadata from .metadata.json files if
+     available, fall back to the "filename" linemode if no
+     metadata was found.  See :meta command.
+
 =item load_copy_buffer
 
 Load the copy buffer from F<~/.config/ranger/copy_buffer>.  This can be used to
@@ -936,6 +1051,16 @@ This command is based on the I<scout> command and supports all of its options.
 Mark all tags that are tagged with either of the given tags.  When leaving out
 the tag argument, all tagged files are marked.
 
+=item meta I<key> I<value>
+
+Set the metadata of the currently highlighted file.  Example:
+
+ :meta title The Hitchhiker's Guide to the Galaxy
+ :meta year 1979
+
+This metadata can be displayed by, for example, using the "metatitle" line mode
+by typing Mt.
+
 =item mkdir I<dirname>
 
 Creates a directory with the name I<dirname>.
@@ -955,6 +1080,11 @@ Note that if you specify an application, the mode is ignored.
 
 Binds keys for the pager. Works like the C<map> command.
 
+=item prompt_metadata [I<keys ...>]
+
+Prompt the user to input metadata with the C<meta> command for multiple keys in
+a row.
+
 =item punmap [I<keys ...>]
 
 Removes key mappings of the pager. Works like the C<unmap> command.
@@ -973,6 +1103,11 @@ it by typing `` or '' the next time you start ranger.
 Change the link destination of the current symlink file to <newpath>. First
 <tab> will load the original link.
 
+=item rename_append
+
+Opens the console with ":rename <current file>" with the cursor automatically
+placed before the file extension
+
 =item rename I<newname>
 
 Rename the current file.  If a file with that name already exists, the renaming
@@ -1259,6 +1394,8 @@ GNU General Public License 3 or (at your option) any later version.
 
 =item The mailing list: L<http://savannah.nongnu.org/mail/?group=ranger>
 
+=item IRC channel: #ranger on freenode.net
+
 =back
 
 ranger is maintained with the git version control system.  To fetch a fresh
@@ -1278,7 +1415,7 @@ rifle(1)
 
 =head1 BUGS
 
-Report bugs here: L<http://savannah.nongnu.org/bugs/?group=ranger>
+Report bugs here: L<https://github.com/hut/ranger/issues>
 
 Please include as much relevant information as possible.  For the most
 diagnostic output, run ranger like this: C<PYTHONOPTIMIZE= ranger --debug>
diff --git a/doc/rifle.1 b/doc/rifle.1
index df63d118..999d56d6 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RIFLE 1"
-.TH RIFLE 1 "rifle-1.6.1" "04/11/2014" "rifle manual"
+.TH RIFLE 1 "rifle-1.6.1" "08/26/2014" "rifle manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff --git a/doc/screenshot.png b/doc/screenshot.png
new file mode 100644
index 00000000..42f13bcf
--- /dev/null
+++ b/doc/screenshot.png
Binary files differdiff --git a/doc/tools/convert_papermode_to_metadata.py b/doc/tools/convert_papermode_to_metadata.py
new file mode 100755
index 00000000..a1d6372d
--- /dev/null
+++ b/doc/tools/convert_papermode_to_metadata.py
@@ -0,0 +1,69 @@
+#!/bin/python
+"""
+usage: ./convert_papermode_to_metadata.py
+
+This script converts the .paperinfo CSV file in the current directory to an
+equivalent .metadata.json file.
+
+ranger used to store metadata in .paperinfo files, but that format was rather
+limited, so .metadata.json files were introduced.
+"""
+
+import csv
+import json
+import os
+import sys
+
+if sys.version < '3.':
+    getuserinput = raw_input
+else:
+    getuserinput = input
+
+FIELDS = ["name", "year", "title", "authors", "url"]
+
+def replace(source, target):
+    if not os.path.exists(source):
+        print("Source file `%s' doesn't exist, skipping." % source)
+        return
+
+    # Ask for user confirmation if the target file already exists
+    if os.path.exists(target):
+        sys.stdout.write("Warning: target file `%s' exists! Overwrite? [y/N]")
+        userinput = getuserinput()
+        if not (userinput.startswith("y") or userinput.startswith("Y")):
+            print("Skipping file `%s'" % source)
+            return
+
+    result = dict()
+
+    # Read the input file and convert it to a dictionary
+    with open(".paperinfo", "r") as infile:
+        reader = csv.reader(infile, skipinitialspace=True)
+        for lineno, row in enumerate(reader):
+            if len(row) != len(FIELDS):
+                print("skipping invalid row `%s' on line %d" % (row, lineno))
+                continue
+            name = row[0]
+            entry = {}
+
+            # Filling up the resulting entry dict
+            for i, column in enumerate(row[1:]):
+                if column:
+                    entry[FIELDS[i + 1]] = column
+
+            # Adding the dict if it isn't empty
+            if entry:
+                result[name] = entry
+
+    # Write the obtained dictionary into the target file
+    if result:
+        with open(".metadata.json", "w") as outfile:
+            json.dump(result, outfile, indent=2)
+    else:
+        print("Skipping writing `%s' due to a lack of data" % target)
+
+if __name__ == "__main__":
+    if set(['--help', '-h']) & set(sys.argv[1:]):
+        print(__doc__.strip())
+    else:
+        replace(".paperinfo", ".metadata.json")
diff --git a/ranger.py b/ranger.py
index cd0c4305..4d8c5963 100755
--- a/ranger.py
+++ b/ranger.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python -O
 # ranger - a vim-inspired file manager for the console  (coding: utf-8)
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # =====================
diff --git a/ranger/__init__.py b/ranger/__init__.py
index f6b17b10..af7cee33 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """A console file manager with VI key bindings.
@@ -15,7 +15,7 @@ import os
 __license__ = 'GPL3'
 __version__ = '1.6.1'
 __author__ = __maintainer__ = 'Roman Zimbelmann'
-__email__ = 'hut@lepus.uberspace.de'
+__email__ = 'hut@hut.pm'
 
 # Constants
 RANGERDIR = os.path.dirname(__file__)
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 98b0a23a..52028128 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # TODO: Add an optional "!" to all commands and set a flag if it's there
@@ -75,7 +75,7 @@ class CommandContainer(object):
                 return None
 
     def command_generator(self, start):
-        return (cmd + ' ' for cmd in self.commands if cmd.startswith(start))
+        return sorted(cmd + ' ' for cmd in self.commands if cmd.startswith(start))
 
 
 class Command(FileManagerAware):
@@ -92,6 +92,7 @@ class Command(FileManagerAware):
         self.line = line
         self.args = line.split()
         self.quantifier = quantifier
+        self.quickly_executed = False
         try:
             self.firstpart = line[:line.rindex(' ') + 1]
         except ValueError:
@@ -405,8 +406,14 @@ class AliasCommand(Command):
         return self._make_cmd().cancel()
 
     def _make_cmd(self):
-        Cmd = self.fm.commands.get_command(self._line.split()[0])
-        return Cmd(self._line + ' ' + self.rest(1))
+        cmd_class = self.fm.commands.get_command(self._line.split()[0])
+        cmd = cmd_class(self._line + ' ' + self.rest(1))
+        cmd.quickly_executed = self.quickly_executed
+        cmd.quantifier = self.quantifier
+        cmd.escape_macros_for_shell = self.escape_macros_for_shell
+        cmd.resolve_macros = self.resolve_macros
+        cmd.allow_abbrev = self.allow_abbrev
+        return cmd
 
 
 if __name__ == '__main__':
diff --git a/ranger/api/options.py b/ranger/api/options.py
index 3fbd3d47..d30c7995 100644
--- a/ranger/api/options.py
+++ b/ranger/api/options.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # THIS WHOLE FILE IS OBSOLETE AND EXISTS FOR BACKWARDS COMPATIBILITIY
diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py
index c32fbf1c..47ebff27 100644
--- a/ranger/colorschemes/default.py
+++ b/ranger/colorschemes/default.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.gui.colorscheme import ColorScheme
diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py
index 2b20281c..91ffc9c6 100644
--- a/ranger/colorschemes/jungle.py
+++ b/ranger/colorschemes/jungle.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.gui.color import *
diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py
index 1507c20f..c7793a26 100644
--- a/ranger/colorschemes/snow.py
+++ b/ranger/colorschemes/snow.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.gui.colorscheme import ColorScheme
diff --git a/ranger/colorschemes/solarized.py b/ranger/colorschemes/solarized.py
new file mode 100644
index 00000000..7d209ef9
--- /dev/null
+++ b/ranger/colorschemes/solarized.py
@@ -0,0 +1,132 @@
+# Joseph Tannhuber <sepp.tannhuber@yahoo.de>, 2013
+# Solarized like colorscheme, similar to solarized-dircolors
+# from https://github.com/seebi/dircolors-solarized.
+# This is a modification of Roman Zimbelmann's default colorscheme.
+# This software is distributed under the terms of the GNU GPL version 3.
+
+from ranger.gui.colorscheme import ColorScheme
+from ranger.gui.color import *
+
+class Solarized(ColorScheme):
+    progress_bar_color = 33
+
+    def use(self, context):
+        fg, bg, attr = default_colors
+
+        if context.reset:
+            return default_colors
+
+        elif context.in_browser:
+            fg = 244
+            if context.selected:
+                attr = reverse
+            else:
+                attr = normal
+            if context.empty or context.error:
+                fg = 235
+                bg = 160
+            if context.border:
+                fg = default
+            if context.media:
+                if context.image:
+                    fg = 136
+                else:
+                    fg = 166
+            if context.container:
+                fg = 61
+            if context.directory:
+                fg = 33
+            elif context.executable and not \
+                    any((context.media, context.container,
+                        context.fifo, context.socket)):
+                fg = 64
+                attr |= bold
+            if context.socket:
+                fg = 136
+                bg = 230
+                attr |= bold
+            if context.fifo:
+                fg = 136
+                bg = 230
+                attr |= bold
+            if context.device:
+                fg = 244
+                bg = 230
+                attr |= bold
+            if context.link:
+                fg = context.good and 37 or 160
+                attr |= bold
+                if context.bad:
+                    bg = 235
+            if context.tag_marker and not context.selected:
+                attr |= bold
+                if fg in (red, magenta):
+                    fg = white
+                else:
+                    fg = red
+            if not context.selected and (context.cut or context.copied):
+                fg = 234
+                attr |= bold
+            if context.main_column:
+                if context.selected:
+                    attr |= bold
+                if context.marked:
+                    attr |= bold
+                    bg = 237
+            if context.badinfo:
+                if attr & reverse:
+                    bg = magenta
+                else:
+                    fg = magenta
+
+        elif context.in_titlebar:
+            attr |= bold
+            if context.hostname:
+                fg = context.bad and 16 or 255
+                if context.bad:
+                    bg = 166
+            elif context.directory:
+                fg = 33
+            elif context.tab:
+                fg = context.good and 47 or 33
+                bg = 239
+            elif context.link:
+                fg = cyan
+
+        elif context.in_statusbar:
+            if context.permissions:
+                if context.good:
+                    fg = 93
+                elif context.bad:
+                    fg = 160
+                    bg = 235
+            if context.marked:
+                attr |= bold | reverse
+                fg = 237
+                bg = 47
+            if context.message:
+                if context.bad:
+                    attr |= bold
+                    fg = 160
+                    bg = 235
+            if context.loaded:
+                bg = self.progress_bar_color
+
+        if context.text:
+            if context.highlight:
+                attr |= reverse
+
+        if context.in_taskview:
+            if context.title:
+                fg = 93
+
+            if context.selected:
+                attr |= reverse
+
+            if context.loaded:
+                if context.selected:
+                    fg = self.progress_bar_color
+                else:
+                    bg = self.progress_bar_color
+
+        return fg, bg, attr
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 68714009..c02a30c3 100644
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This configuration file is licensed under the same terms as ranger.
 # ===================================================================
 # This file contains ranger's commands.
@@ -107,7 +107,8 @@ class cd(Command):
             self.shift()
             destination = os.path.realpath(self.rest(1))
             if os.path.isfile(destination):
-                destination = os.path.dirname(destination)
+                self.fm.select_file(destination)
+                return
         else:
             destination = self.rest(1)
 
@@ -381,6 +382,43 @@ class setintag(setlocal):
         self.fm.set_option_from_string(name, value, tags=tags)
 
 
+class default_linemode(Command):
+    def execute(self):
+        import re
+        from ranger.container.fsobject import POSSIBLE_LINEMODES
+
+        if len(self.args) < 2:
+            self.fm.notify("Usage: default_linemode [path=<regexp> | tag=<tag(s)>] <linemode>", bad=True)
+
+        # Extract options like "path=..." or "tag=..." from the command line
+        arg1 = self.arg(1)
+        method = "always"
+        argument = None
+        if arg1.startswith("path="):
+            method = "path"
+            argument = re.compile(arg1[5:])
+            self.shift()
+        elif arg1.startswith("tag="):
+            method = "tag"
+            argument = arg1[4:]
+            self.shift()
+
+        # Extract and validate the line mode from the command line
+        linemode = self.rest(1)
+        if linemode not in POSSIBLE_LINEMODES:
+            self.fm.notify("Invalid linemode: %s; should be %s" %
+                    (linemode, "/".join(POSSIBLE_LINEMODES)), bad=True)
+
+        # Add the prepared entry to the fm.default_linemodes
+        entry = [method, argument, linemode]
+        self.fm.default_linemodes.appendleft(entry)
+
+        # Redraw the columns
+        if hasattr(self.fm.ui, "browser"):
+            for col in self.fm.ui.browser.columns:
+                col.need_redraw = True
+
+
 class quit(Command):
     """:quit
 
@@ -685,6 +723,18 @@ class rename(Command):
     def tab(self):
         return self._tab_directory_content()
 
+class rename_append(Command):
+    """:rename_append
+
+    Creates an open_console for the rename command, automatically placing the cursor before the file extension.
+    """
+
+    def execute(self):
+        cf = self.fm.thisfile
+        if cf.basename.find('.') != 0 and cf.basename.rfind('.') != -1 and not cf.is_directory:
+            self.fm.open_console('rename ' + cf.basename, position=(7 + cf.basename.rfind('.')))
+        else:
+            self.fm.open_console('rename ' + cf.basename)
 
 class chmod(Command):
     """:chmod <octal number>
@@ -744,20 +794,19 @@ class bulkrename(Command):
 
         # Create and edit the file list
         filenames = [f.basename for f in self.fm.thistab.get_selection()]
-        listfile = tempfile.NamedTemporaryFile()
+        listfile = tempfile.NamedTemporaryFile(delete=False)
+        listpath = listfile.name
 
         if py3:
             listfile.write("\n".join(filenames).encode("utf-8"))
         else:
             listfile.write("\n".join(filenames))
-        listfile.flush()
-        self.fm.execute_file([File(listfile.name)], app='editor')
-        listfile.seek(0)
-        if py3:
-            new_filenames = listfile.read().decode("utf-8").split("\n")
-        else:
-            new_filenames = listfile.read().split("\n")
         listfile.close()
+        self.fm.execute_file([File(listpath)], app='editor')
+        listfile = open(listpath, 'r')
+        new_filenames = listfile.read().split("\n")
+        listfile.close()
+        os.unlink(listpath)
         if all(a == b for a, b in zip(filenames, new_filenames)):
             self.fm.notify("No renaming to be done!")
             return
@@ -923,6 +972,9 @@ class map_(Command):
     resolve_macros = False
 
     def execute(self):
+        if not self.arg(1) or not self.arg(2):
+            return self.fm.notify("Not enough arguments", bad=True)
+
         self.fm.ui.keymaps.bind(self.context, self.arg(1), self.rest(2))
 
 
@@ -1033,9 +1085,12 @@ class scout(Command):
 
         if self.KEEP_OPEN in flags and thisdir != self.fm.thisdir:
             # reopen the console:
-            self.fm.open_console(self.line[0:-len(pattern)])
+            if not pattern:
+                self.fm.open_console(self.line)
+            else:
+                self.fm.open_console(self.line[0:-len(pattern)])
 
-        if thisdir != self.fm.thisdir and pattern != "..":
+        if self.quickly_executed and thisdir != self.fm.thisdir and pattern != "..":
             self.fm.block_input(0.5)
 
     def cancel(self):
@@ -1132,6 +1187,33 @@ class scout(Command):
         return count == 1
 
 
+class filter_inode_type(Command):
+    """
+    :filter_inode_type [dfl]
+
+    Displays only the files of specified inode type. Parameters
+    can be combined.
+
+        d display directories
+        f display files
+        l display links
+    """
+
+    FILTER_DIRS  = 'd'
+    FILTER_FILES = 'f'
+    FILTER_LINKS = 'l'
+
+    def execute(self):
+        if not self.arg(1):
+            self.fm.thisdir.inode_type_filter = None
+        else:
+            self.fm.thisdir.inode_type_filter = lambda file: (
+                    True if ((self.FILTER_DIRS  in self.arg(1) and file.is_directory) or
+                             (self.FILTER_FILES in self.arg(1) and file.is_file and not file.is_link) or
+                             (self.FILTER_LINKS in self.arg(1) and file.is_link)) else False)
+        self.fm.thisdir.refilter()
+
+
 class grep(Command):
     """:grep <string>
 
@@ -1243,3 +1325,83 @@ class log(Command):
 
         pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
         self.fm.run([pager, tmp.name])
+
+class flat(Command):
+    """
+    :flat <level>
+
+    Flattens the directory view up to the specified level.
+
+        -1 fully flattened
+         0 remove flattened view
+    """
+
+    def execute(self):
+        try:
+            level = self.rest(1)
+            level = int(level)
+        except ValueError:
+            level = self.quantifier
+        if level < -1:
+            self.fm.notify("Need an integer number (-1, 0, 1, ...)", bad=True)
+        self.fm.thisdir.unload()
+        self.fm.thisdir.flat = level
+        self.fm.thisdir.load_content()
+
+
+# Metadata commands
+# --------------------------------
+
+class prompt_metadata(Command):
+    """
+    :prompt_metadata <key1> [<key2> [<key3> ...]]
+
+    Prompt the user to input metadata for multiple keys in a row.
+    """
+
+    _command_name = "meta"
+    _console_chain = None
+    def execute(self):
+        prompt_metadata._console_chain = self.args[1:]
+        self._process_command_stack()
+
+    def _process_command_stack(self):
+        if prompt_metadata._console_chain:
+            key = prompt_metadata._console_chain.pop()
+            self._fill_console(key)
+        else:
+            for col in self.fm.ui.browser.columns:
+                col.need_redraw = True
+
+    def _fill_console(self, key):
+        metadata = self.fm.metadata.get_metadata(self.fm.thisfile.path)
+        if key in metadata and metadata[key]:
+            existing_value = metadata[key]
+        else:
+            existing_value = ""
+        text = "%s %s %s" % (self._command_name, key, existing_value)
+        self.fm.open_console(text, position=len(text))
+
+
+class meta(prompt_metadata):
+    """
+    :meta <key> [<value>]
+
+    Change metadata of a file.  Deletes the key if value is empty.
+    """
+
+    def execute(self):
+        key = self.arg(1)
+        value = self.rest(1)
+        update_dict = dict()
+        update_dict[key] = self.rest(2)
+        selection = self.fm.thistab.get_selection()
+        for f in selection:
+            self.fm.metadata.set_metadata(f.path, update_dict)
+        self._process_command_stack()
+
+    def tab(self):
+        key = self.arg(1)
+        metadata = self.fm.metadata.get_metadata(self.fm.thisfile.path)
+        if key in metadata and metadata[key]:
+            return self.arg(0) + " " + metadata[key]
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 52b06b4b..687d591e 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -35,7 +35,7 @@ set confirm_on_delete multiple
 
 # Which script is used to generate file previews?
 # ranger ships with scope.sh, a script that calls external programs (see
-# README for dependencies) to preview images, archives, etc.
+# README.md for dependencies) to preview images, archives, etc.
 set preview_script ~/.config/ranger/scope.sh
 
 # Use the external preview script or display simple plain text or image previews?
@@ -147,7 +147,7 @@ set autoupdate_cumulative_size false
 # Turning this on makes sense for screen readers:
 set show_cursor false
 
-# One of: size, basename, mtime, type
+# One of: size, natural, basename, atime, ctime, mtime, type, random
 set sort natural
 
 # Additional sorting options
@@ -166,6 +166,19 @@ set cd_bookmarks true
 # disable this feature.
 set preview_max_size 0
 
+# Add the highlighted file to the path in the titlebar
+set show_selection_in_titlebar true
+
+# The delay that ranger idly waits for user input, in milliseconds, with a
+# resolution of 100ms.  Lower delay reduces lag between directory updates but
+# increases CPU load.
+set idle_delay 2000
+
+# When the metadata manager module looks for metadata, should it only look for
+# a ".metadata.json" file in the current directory, or do a deep search and
+# check all directories above the current one as well?
+set metadata_deep_search false
+
 # ===================================================================
 # == Local Options
 # ===================================================================
@@ -224,6 +237,11 @@ map r  chain draw_possible_programs; console open_with
 map f  console find 
 map cd console cd 
 
+# Change the line mode
+map Mf linemode filename
+map Mp linemode permissions
+map Mt linemode metatitle
+
 # Tagging / Marking
 map t       tag_toggle
 map ut      tag_remove
@@ -299,14 +317,15 @@ map g? cd /usr/share/doc/ranger
 map E  edit
 map du shell -p du --max-depth=1 -h --apparent-size
 map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh
-map yp shell -d echo -n %d/%f | xsel -i
-map yd shell -d echo -n %d    | xsel -i
-map yn shell -d echo -n %f    | xsel -i
+map yp shell -f echo -n %d/%f | xsel -i; xsel -o | xsel -i -b
+map yd shell -f echo -n %d    | xsel -i; xsel -o | xsel -i -b
+map yn shell -f echo -n %f    | xsel -i; xsel -o | xsel -i -b
 
 # Filesystem Operations
 map =  chmod
 
 map cw console rename 
+map a  rename_append
 map A  eval fm.open_console('rename ' + fm.thisfile.basename)
 map I  eval fm.open_console('rename ' + fm.thisfile.basename, position=7)
 
@@ -372,6 +391,7 @@ map <a-9>     tab_open 9
 
 # Sorting
 map or toggle_option sort_reverse
+map oz set sort=random
 map os chain set sort=size;      set sort_reverse=False
 map ob chain set sort=basename;  set sort_reverse=False
 map on chain set sort=natural;   set sort_reverse=False
@@ -414,17 +434,17 @@ map m<bg>   draw_bookmarks
 copymap m<bg>  um<bg> `<bg> '<bg>
 
 # Generate all the chmod bindings with some python help:
-eval for arg in "rwxXst": cmd("map +u{0} shell -d chmod u+{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map +g{0} shell -d chmod g+{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map +o{0} shell -d chmod o+{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map +a{0} shell -d chmod a+{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map +{0}  shell -d chmod u+{0} %s".format(arg))
-
-eval for arg in "rwxXst": cmd("map -u{0} shell -d chmod u-{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map -g{0} shell -d chmod g-{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map -o{0} shell -d chmod o-{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map -a{0} shell -d chmod a-{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map -{0}  shell -d chmod u-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +u{0} shell -f chmod u+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +g{0} shell -f chmod g+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +o{0} shell -f chmod o+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +a{0} shell -f chmod a+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +{0}  shell -f chmod u+{0} %s".format(arg))
+
+eval for arg in "rwxXst": cmd("map -u{0} shell -f chmod u-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -g{0} shell -f chmod g-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -o{0} shell -f chmod o-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -a{0} shell -f chmod a-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -{0}  shell -f chmod u-{0} %s".format(arg))
 
 # ===================================================================
 # == Define keys for the console
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 68189cd7..3371ec6f 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -56,6 +56,7 @@
 ext x?html?, has surf,           X, flag f = surf -- file://"$1"
 ext x?html?, has vimprobable,    X, flag f = vimprobable -- "$@"
 ext x?html?, has vimprobable2,   X, flag f = vimprobable2 -- "$@"
+ext x?html?, has dwb,            X, flag f = dwb -- "$@"
 ext x?html?, has jumanji,        X, flag f = jumanji -- "$@"
 ext x?html?, has luakit,         X, flag f = luakit -- "$@"
 ext x?html?, has uzbl,           X, flag f = uzbl -- "$@"
@@ -81,11 +82,12 @@ ext x?html?, has w3m,             terminal = w3m "$@"
 # Define the "editor" for text files as first action
 mime ^text,  label editor = $EDITOR -- "$@"
 mime ^text,  label pager  = "$PAGER" -- "$@"
-!mime ^text, label editor, ext xml|csv|tex|py|pl|rb|sh|php = $EDITOR -- "$@"
-!mime ^text, label pager,  ext xml|csv|tex|py|pl|rb|sh|php = "$PAGER" -- "$@"
+!mime ^text, label editor, ext xml|csv|tex|py|pl|rb|js|sh|php = $EDITOR -- "$@"
+!mime ^text, label pager,  ext xml|csv|tex|py|pl|rb|js|sh|php = "$PAGER" -- "$@"
 
 ext 1                         = man "$1"
 ext s[wmf]c, has zsnes, X     = zsnes "$1"
+ext s[wmf]c, has snes9x-gtk,X = snes9x-gtk "$1"
 ext nes, has fceux, X         = fceux "$1"
 ext exe                       = wine "$1"
 name ^[mM]akefile$            = make
@@ -96,6 +98,7 @@ name ^[mM]akefile$            = make
 ext py  = python -- "$1"
 ext pl  = perl -- "$1"
 ext rb  = ruby -- "$1"
+ext js  = node -- "$1"
 ext sh  = sh -- "$1"
 ext php = php -- "$1"
 
@@ -132,10 +135,11 @@ mime ^video, terminal, !X, has mplayer   = mplayer -- "$@"
 #-------------------------------------------
 # Image Viewing:
 #-------------------------------------------
-mime ^image, has eog,    X, flag f = eog -- "$@"
 mime ^image, has sxiv,   X, flag f = sxiv -- "$@"
 mime ^image, has feh,    X, flag f = feh -- "$@"
 mime ^image, has mirage, X, flag f = mirage -- "$@"
+mime ^image, has eog,    X, flag f = eog -- "$@"
+mime ^image, has eom,    X, flag f = eom -- "$@"
 mime ^image, has gimp,   X, flag f = gimp -- "$@"
 ext xcf,                 X, flag f = gimp -- "$@"
 
@@ -148,6 +152,7 @@ ext pdf, has mupdf,    X, flag f = mupdf -- "$@"
 ext pdf, has apvlv,    X, flag f = apvlv -- "$@"
 ext pdf, has xpdf,     X, flag f = xpdf -- "$@"
 ext pdf, has evince,   X, flag f = evince -- "$@"
+ext pdf, has atril,    X, flag f = atril -- "$@"
 ext pdf, has okular,   X, flag f = okular -- "$@"
 ext pdf, has epdfview, X, flag f = epdfview -- "$@"
 
@@ -160,6 +165,7 @@ ext od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has soffice,     X, flag f
 ext od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has ooffice,     X, flag f = ooffice "$@"
 
 ext djvu, has evince, X, flag f = evince -- "$@"
+ext djvu, has atril,  X, flag f = atril -- "$@"
 
 #-------------------------------------------
 # Archives
@@ -183,6 +189,6 @@ label wallpaper, number 13, mime ^image, X = feh --bg-center "$1"
 label wallpaper, number 14, mime ^image, X = feh --bg-fill "$1"
 
 # Define the editor for non-text files + pager as last action
-              !mime ^text, !ext xml|csv|tex|py|pl|rb|sh|php  = ask
-label editor, !mime ^text, !ext xml|csv|tex|py|pl|rb|sh|php  = $EDITOR -- "$@"
-label pager,  !mime ^text, !ext xml|csv|tex|py|pl|rb|sh|php  = "$PAGER" -- "$@"
+              !mime ^text, !ext xml|csv|tex|py|pl|rb|js|sh|php  = ask
+label editor, !mime ^text, !ext xml|csv|tex|py|pl|rb|js|sh|php  = $EDITOR -- "$@"
+label pager,  !mime ^text, !ext xml|csv|tex|py|pl|rb|js|sh|php  = "$PAGER" -- "$@"
diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py
index 912f3f3a..d664991b 100644
--- a/ranger/container/bookmarks.py
+++ b/ranger/container/bookmarks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import string
diff --git a/ranger/container/directory.py b/ranger/container/directory.py
index 136cc8ac..3a609cf6 100644
--- a/ranger/container/directory.py
+++ b/ranger/container/directory.py
@@ -1,7 +1,8 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import os.path
+import random
 import re
 
 from os import stat as os_stat, lstat as os_lstat
@@ -19,7 +20,7 @@ from ranger.container.settings import LocalSettings
 
 def sort_by_basename(path):
     """returns path.basename (for sorting)"""
-    return path.basename
+    return path.drawn_basename
 
 def sort_by_basename_icase(path):
     """returns case-insensitive path.basename (for sorting)"""
@@ -35,15 +36,39 @@ def sort_naturally(path):
 def sort_naturally_icase(path):
     return path.basename_natural_lower
 
-def accept_file(fname, directory, hidden_filter, name_filter):
-    if hidden_filter and hidden_filter.search(fname):
-        return False
-    if name_filter and not name_filter.search(fname):
-        return False
-    if directory.temporary_filter and not directory.temporary_filter.search(fname):
-        return False
+def accept_file(file, filters):
+    """
+    Returns True if file shall be shown, otherwise False.
+    Parameters:
+        file - an instance of FileSystemObject
+        filters - an array of lambdas, each expects a file and
+                  returns True if file shall be shown,
+                  otherwise False.
+    """
+    for filter in filters:
+        if filter and not filter(file):
+            return False
     return True
 
+def walklevel(some_dir, level):
+    some_dir = some_dir.rstrip(os.path.sep)
+    followlinks = True if level > 0 else False
+    assert os.path.isdir(some_dir)
+    num_sep = some_dir.count(os.path.sep)
+    for root, dirs, files in os.walk(some_dir, followlinks=followlinks):
+        yield root, dirs, files
+        num_sep_this = root.count(os.path.sep)
+        if level != -1 and num_sep + level <= num_sep_this:
+            del dirs[:]
+
+def mtimelevel(path, level):
+    mtime = os.stat(path).st_mtime
+    for dirpath, dirnames, filenames in walklevel(path, level):
+        dirlist = [os.path.join("/", dirpath, d) for d in dirnames
+                if level == -1 or dirpath.count(os.path.sep) - path.count(os.path.sep) <= level]
+        mtime = max(mtime, max([-1] + [os.stat(d).st_mtime for d in dirlist]))
+    return mtime
+
 class Directory(FileSystemObject, Accumulator, Loadable):
     is_directory = True
     enterable = False
@@ -51,12 +76,14 @@ class Directory(FileSystemObject, Accumulator, Loadable):
     cycle_list = None
     loading = False
     progressbar_supported = True
+    flat = 0
 
     filenames = None
     files = None
     files_all = None
     filter = None
     temporary_filter = None
+    inode_type_filter = None
     marked_items = None
     scroll_begin = 0
 
@@ -77,10 +104,11 @@ class Directory(FileSystemObject, Accumulator, Loadable):
     sort_dict = {
         'basename': sort_by_basename,
         'natural': sort_naturally,
-        'size': lambda path: -path.size,
+        'size': lambda path: -(path.size or 1),
         'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
         'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1),
         'atime': lambda path: -(path.stat and path.stat.st_atime or 1),
+        'random': lambda path: random.random(),
         'type': lambda path: path.mimetype or '',
     }
 
@@ -157,6 +185,8 @@ class Directory(FileSystemObject, Accumulator, Loadable):
     def get_selection(self):
         """READ ONLY"""
         self._gc_marked_items()
+        if not self.files:
+            return []
         if self.marked_items:
             return [item for item in self.files if item.marked]
         elif self.pointed_obj:
@@ -170,13 +200,22 @@ class Directory(FileSystemObject, Accumulator, Loadable):
 
         self.last_update_time = time()
 
+        filters = []
+
         if not self.settings.show_hidden and self.settings.hidden_filter:
             hidden_filter = re.compile(self.settings.hidden_filter)
-        else:
-            hidden_filter = None
-
-        self.files = [f for f in self.files_all if accept_file(
-            f.basename, self, hidden_filter, self.filter)]
+            hidden_filter_search = hidden_filter.search
+            filters.append(lambda file: not hidden_filter_search(file.basename))
+        if self.filter:
+            filter_search = self.filter.search
+            filters.append(lambda file: filter_search(file.basename))
+        if self.inode_type_filter:
+            filters.append(self.inode_type_filter)
+        if self.temporary_filter:
+            temporary_filter_search = self.temporary_filter.search
+            filters.append(lambda file: temporary_filter_search(file.basename))
+
+        self.files = [f for f in self.files_all if accept_file(f, filters)]
         self.move_to_obj(self.pointed_obj)
 
     # XXX: Check for possible race conditions
@@ -191,6 +230,8 @@ class Directory(FileSystemObject, Accumulator, Loadable):
         self.percent = 0
         self.load_if_outdated()
 
+        basename_is_rel_to = self.path if self.flat else None
+
         try:
             if self.runnable:
                 yield
@@ -198,7 +239,20 @@ class Directory(FileSystemObject, Accumulator, Loadable):
 
                 self.mount_path = mount_path(mypath)
 
-                filelist = os.listdir(mypath)
+                if self.flat:
+                    filelist = []
+                    for dirpath, dirnames, filenames in walklevel(mypath, self.flat):
+                        dirlist = [os.path.join("/", dirpath, d) for d in dirnames
+                                if self.flat == -1 or dirpath.count(os.path.sep) - mypath.count(os.path.sep) <= self.flat]
+                        filelist += dirlist
+                        filelist += [os.path.join("/", dirpath, f) for f in filenames]
+                    filenames = filelist
+                    self.load_content_mtime = mtimelevel(mypath, self.flat)
+                else:
+                    filelist = os.listdir(mypath)
+                    filenames = [mypath + (mypath == '/' and fname or '/' + fname)
+                            for fname in filelist]
+                    self.load_content_mtime = os.stat(mypath).st_mtime
 
                 if self._cumulative_size_calculated:
                     # If self.content_loaded is true, this is not the first
@@ -218,12 +272,8 @@ class Directory(FileSystemObject, Accumulator, Loadable):
                 if self.is_link:
                     self.infostring = '->' + self.infostring
 
-                filenames = [mypath + (mypath == '/' and fname or '/' + fname)
-                        for fname in filelist]
                 yield
 
-                self.load_content_mtime = os.stat(mypath).st_mtime
-
                 marked_paths = [obj.path for obj in self.marked_items]
 
                 files = []
@@ -246,15 +296,20 @@ class Directory(FileSystemObject, Accumulator, Loadable):
                         stats = None
                         is_a_dir = False
                     if is_a_dir:
-                        try:
-                            item = self.fm.get_directory(name)
-                            item.load_if_outdated()
-                        except:
-                            item = Directory(name, preload=stats,
-                                    path_is_abs=True)
+                        if self.flat:
+                            item = Directory(name, preload=stats, path_is_abs=True,
+                                    basename_is_rel_to=basename_is_rel_to)
                             item.load()
+                        else:
+                            try:
+                                item = self.fm.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 = File(name, preload=stats, path_is_abs=True,
+                                    basename_is_rel_to=basename_is_rel_to)
                         item.load()
                         disk_usage += item.size
 
@@ -505,7 +560,10 @@ class Directory(FileSystemObject, Accumulator, Loadable):
             return True
 
         try:
-            real_mtime = os.stat(self.path).st_mtime
+            if self.flat:
+                real_mtime = mtimelevel(self.path, self.flat)
+            else:
+                real_mtime = os.stat(self.path).st_mtime
         except OSError:
             real_mtime = None
             return False
@@ -542,6 +600,10 @@ class Directory(FileSystemObject, Accumulator, Loadable):
         """Is the directory empty?"""
         return self.files is None or len(self.files) == 0
 
+    def _set_linemode_of_children(self, mode):
+        for f in self.files:
+            f._set_linemode(mode)
+
     def __nonzero__(self):
         """Always True"""
         return True
diff --git a/ranger/container/file.py b/ranger/container/file.py
index ab677125..fb8094a6 100644
--- a/ranger/container/file.py
+++ b/ranger/container/file.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import re
@@ -43,6 +43,8 @@ class File(FileSystemObject):
     preview_known = False
     preview_loading = False
 
+    _linemode = "filename"
+
     @property
     def firstbytes(self):
         try:
diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py
index 86730fae..08161c3c 100644
--- a/ranger/container/fsobject.py
+++ b/ranger/container/fsobject.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio',
@@ -12,9 +12,14 @@ DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits',
 
 BAD_INFO = '?'
 
+POSSIBLE_LINEMODES = ("filename", "metatitle", "permissions")
+DEFAULT_LINEMODE = "filename"
+
 import re
-from os import lstat, stat
-from os.path import abspath, basename, dirname, realpath, splitext, extsep
+from grp import getgrgid
+from os import lstat, stat, getcwd
+from os.path import abspath, basename, dirname, realpath, splitext, extsep, relpath
+from pwd import getpwuid
 from ranger.core.shared import FileManagerAware, SettingsAware
 from ranger.ext.shell_escape import shell_escape
 from ranger.ext.spawn import spawn
@@ -78,12 +83,22 @@ class FileSystemObject(FileManagerAware, SettingsAware):
     vcs_outdated = False
     vcs_enabled = False
 
-    def __init__(self, path, preload=None, path_is_abs=False):
+    basename_is_rel_to = None
+
+    _linemode = DEFAULT_LINEMODE
+
+    def __init__(self, path, preload=None, path_is_abs=False, basename_is_rel_to=None):
         if not path_is_abs:
             path = abspath(path)
         self.path = path
-        self.basename = basename(path)
-        self.basename_lower = self.basename.lower()
+        self.basename_is_rel_to = basename_is_rel_to
+        if basename_is_rel_to == None:
+            self.basename = basename(path)
+            self.drawn_basename = self.basename
+        else:
+            self.basename = basename(path)
+            self.drawn_basename = relpath(path, basename_is_rel_to)
+        self.basename_lower = self.drawn_basename.lower()
         self.extension = splitext(self.basename)[1].lstrip(extsep) or None
         self.dirname = dirname(path)
         self.preload = preload
@@ -95,6 +110,21 @@ class FileSystemObject(FileManagerAware, SettingsAware):
         except ValueError:
             self.extension = None
 
+        # Set the line mode from fm.default_linemodes
+        for method, argument, linemode in self.fm.default_linemodes:
+            if linemode in POSSIBLE_LINEMODES:
+                if method == "always":
+                    self._linemode = linemode
+                    break
+                if method == "path" and argument.search(path):
+                    self._linemode = linemode
+                    break
+                if method == "tag" and self.realpath in self.fm.tags and \
+                        self.fm.tags.marker(self.realpath) in argument:
+                    self._linemode = linemode
+                    break
+
+
     def __repr__(self):
         return "<{0} {1}>".format(self.__class__.__name__, self.path)
 
@@ -112,7 +142,7 @@ class FileSystemObject(FileManagerAware, SettingsAware):
     @lazy_property
     def basename_natural(self):
         return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
-            enumerate(_extract_number_re.split(self.basename))]
+            enumerate(_extract_number_re.split(self.drawn_basename))]
 
     @lazy_property
     def basename_natural_lower(self):
@@ -123,6 +153,19 @@ class FileSystemObject(FileManagerAware, SettingsAware):
     def safe_basename(self):
         return self.basename.translate(_safe_string_table)
 
+    @lazy_property
+    def user(self):
+        try:
+            return getpwuid(self.stat.st_uid)[0]
+        except:
+            return str(self.stat.st_uid)
+
+    @lazy_property
+    def group(self):
+        try:
+            return getgrgid(self.stat.st_gid)[0]
+        except:
+            return str(self.stat.st_gid)
 
     for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
         exec("%s = lazy_property("
@@ -363,5 +406,10 @@ class FileSystemObject(FileManagerAware, SettingsAware):
             real_ctime = None
         if not self.stat or self.stat.st_ctime != real_ctime:
             self.load()
+            if self.settings.vcs_aware:
+                self.vcs_outdated = True
             return True
         return False
+
+    def _set_linemode(self, mode):
+        self._linemode = mode
diff --git a/ranger/container/history.py b/ranger/container/history.py
index 432ee827..5cd9d48f 100644
--- a/ranger/container/history.py
+++ b/ranger/container/history.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # TODO: rewrite to use deque instead of list
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 90b6f7ce..55585029 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from inspect import isfunction
@@ -25,8 +25,10 @@ ALLOWED_SETTINGS = {
     'draw_progress_bar_in_status_bar': bool,
     'flushinput': bool,
     'hidden_filter': str,
+    'idle_delay': int,
     'max_console_history_size': (int, type(None)),
     'max_history_size': (int, type(None)),
+    'metadata_deep_search': bool,
     'mouse_enabled': bool,
     'open_all_images': bool,
     'padding_right': bool,
@@ -39,6 +41,7 @@ ALLOWED_SETTINGS = {
     'scroll_offset': int,
     'shorten_title': int,
     'show_cursor': bool,  # TODO: not working?
+    'show_selection_in_titlebar': bool,
     'show_hidden_bookmarks': bool,
     'show_hidden': bool,
     'sort_case_insensitive': bool,
diff --git a/ranger/container/tags.py b/ranger/container/tags.py
index c63c61a0..e9175e5f 100644
--- a/ranger/container/tags.py
+++ b/ranger/container/tags.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # TODO: add a __getitem__ method to get the tag of a file
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index ec853f1b..d73270d5 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import codecs
@@ -27,12 +27,14 @@ from ranger.core.tab import Tab
 from ranger.container.file import File
 from ranger.core.loader import CommandLoader, CopyLoader
 from ranger.container.settings import ALLOWED_SETTINGS
+from ranger.container.fsobject import POSSIBLE_LINEMODES, DEFAULT_LINEMODE
 
 MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
 
 class _MacroTemplate(string.Template):
     """A template for substituting macros in commands"""
     delimiter = ranger.MACRO_DELIMITER
+    idpattern = r"[_a-z0-9]*"
 
 class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
     # --------------------------
@@ -50,6 +52,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
         self.garbage_collect(-1)
         self.enter_dir(old_path)
         self.change_mode('normal')
+        if self.metadata:
+            self.metadata.reset()
 
     def change_mode(self, mode):
         if mode == self.mode:
@@ -150,6 +154,52 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
         """Redraw the window"""
         self.ui.redraw_window()
 
+    def linemode(self, mode, directory=None, depth=0):
+        """
+        Change what is displayed as a filename.
+
+        - "mode" may be: "filename", "permissions", "metatitle", the mode
+          "normal" is mapped to "filename".
+        - "directory" specifies the directory. None means the current directory
+        - "depth" specifies the recursion depth
+        """
+
+        if mode == "normal":
+            mode = DEFAULT_LINEMODE
+
+        if mode not in POSSIBLE_LINEMODES:
+            self.notify("Unhandled linemode: `%s'" % mode, bad=True)
+            return
+
+        if directory is None:
+            directory = self.fm.thisdir
+
+        directories = set([directory])
+        bucket = set()
+
+        current_depth = 0
+
+        while True:
+            if current_depth >= depth:
+                for direct in directories:
+                    direct._set_linemode_of_children(mode)
+                break
+
+            else:
+                for direct in directories:
+                    direct._set_linemode_of_children(mode)
+                    for file_ in direct.files:
+                        if file_.is_directory:
+                            bucket.add(file_)
+
+                directories, bucket = bucket, directories
+                bucket.clear()
+                current_depth += 1
+
+        # Ask the browsercolumns to redraw
+        for col in self.ui.browser.columns:
+            col.need_redraw = True
+
     def open_console(self, string='', prompt=None, position=None):
         """Open the console"""
         self.change_mode('normal')
@@ -451,7 +501,15 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
     def enter_dir(self, path, remember=False, history=True):
         """Enter the directory at the given path"""
         cwd = self.thisdir
+        # csh variable is lowercase
+        cdpath = os.environ.get('CDPATH', None) or os.environ.get('cdpath', None)
         result = self.thistab.enter_dir(path, history=history)
+        if result == 0 and cdpath:
+            for p in cdpath.split(':'):
+                curpath = os.path.join(p, path)
+                if os.path.isdir(curpath):
+                    result = self.thistab.enter_dir(curpath, history=history)
+                    break
         if cwd != self.thisdir:
             if remember:
                 self.bookmarks.remember(cwd)
@@ -810,7 +868,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
     if version_info[0] == 3:
         def sha1_encode(self, path):
             return os.path.join(ranger.CACHEDIR,
-                    sha1(path.encode('utf-8')).hexdigest()) + '.jpg'
+                    sha1(path.encode('utf-8', 'backslashreplace')) \
+                            .hexdigest()) + '.jpg'
     else:
         def sha1_encode(self, path):
             return os.path.join(ranger.CACHEDIR,
@@ -820,6 +879,9 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
         pager = self.ui.get_pager()
         path = file.realpath
 
+        if not path:
+            return None
+
         if self.settings.preview_images and file.image:
             pager.set_image(path)
             return None
diff --git a/ranger/core/environment.py b/ranger/core/environment.py
index 562212d4..5e57ab2c 100644
--- a/ranger/core/environment.py
+++ b/ranger/core/environment.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 # THIS WHOLE FILE IS OBSOLETE AND EXISTS FOR BACKWARDS COMPATIBILITIY
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 2a9ce315..4fae117d 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The File Manager, putting the pieces together"""
@@ -20,6 +20,7 @@ from ranger.gui.ui import UI
 from ranger.container.bookmarks import Bookmarks
 from ranger.core.runner import Runner
 from ranger.ext.img_display import ImageDisplayer
+from ranger.core.metadata import MetadataManager
 from ranger.ext.rifle import Rifle
 from ranger.container.directory import Directory
 from ranger.ext.signals import SignalDispatcher
@@ -56,9 +57,11 @@ class FM(Actions, SignalDispatcher):
         self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS)
         self.py3 = sys.version_info >= (3, )
         self.previews = {}
+        self.default_linemodes = deque()
         self.loader = Loader()
         self.copy_buffer = set()
         self.do_cut = False
+        self.metadata = MetadataManager()
 
         try:
             self.username = pwd.getpwuid(os.geteuid()).pw_name
@@ -95,6 +98,9 @@ class FM(Actions, SignalDispatcher):
         self.rifle = Rifle(rifleconf)
         self.rifle.reload_config()
 
+        if not ranger.arg.clean and self.tags is None:
+            self.tags = Tags(self.confpath('tagged'))
+
         if self.bookmarks is None:
             if ranger.arg.clean:
                 bookmarkfile = None
@@ -106,9 +112,6 @@ class FM(Actions, SignalDispatcher):
                     autosave=self.settings.autosave_bookmarks)
             self.bookmarks.load()
 
-        if not ranger.arg.clean and self.tags is None:
-            self.tags = Tags(self.confpath('tagged'))
-
         self.ui.setup_curses()
         self.ui.initialize()
 
@@ -117,6 +120,7 @@ class FM(Actions, SignalDispatcher):
         self.rifle.hook_after_executing = lambda a, b, flags: \
             self.ui.initialize() if 'f' not in flags else None
         self.rifle.hook_logger = self.notify
+        old_preprocessing_hook = self.rifle.hook_command_preprocessing
 
         # This hook allows image viewers to open all images in the current
         # directory, keeping the order of files the same as in ranger.
@@ -153,7 +157,7 @@ class FM(Actions, SignalDispatcher):
                     if new_command:
                         command = "set -- %s; %s" % (escaped_filenames,
                                 new_command)
-            return command
+            return old_preprocessing_hook(command)
 
         self.rifle.hook_command_preprocessing = sxiv_workaround_hook
 
@@ -161,6 +165,10 @@ class FM(Actions, SignalDispatcher):
             self.notify(text, bad=True)
         self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
 
+        self.settings.signal_bind('setopt.metadata_deep_search',
+                lambda signal: setattr(signal.fm.metadata, 'deep_search',
+                    signal.value))
+
     def destroy(self):
         debug = ranger.arg.debug
         if self.ui:
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index ac63dcc9..17d6bf09 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from collections import deque
diff --git a/ranger/core/main.py b/ranger/core/main.py
index b0ae4a26..9ae8e0ff 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The main function responsible to initialize the FM object and stuff."""
@@ -165,7 +165,7 @@ def main():
             print(crash_traceback)
             print("ranger crashed.  " \
                 "Please report this traceback at:")
-            print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem")
+            print("https://github.com/hut/ranger/issues")
             return 1
         return 0
 
diff --git a/ranger/core/metadata.py b/ranger/core/metadata.py
new file mode 100644
index 00000000..fc545b9c
--- /dev/null
+++ b/ranger/core/metadata.py
@@ -0,0 +1,157 @@
+# Copyright (C) 2014  Roman Zimbelmann <hut@hut.pm>
+# This software is distributed under the terms of the GNU GPL version 3.
+
+"""
+A Metadata Manager that reads information about files from a json database.
+
+The database is contained in a local .metadata.json file.
+"""
+
+# TODO: Better error handling if a json file can't be decoded
+# TODO: Update metadata keys if a file gets renamed/moved
+# TODO: A global metadata file, maybe as a replacement for tags
+
+METADATA_FILE_NAME = ".metadata.json"
+DEEP_SEARCH_DEFAULT = False
+
+import copy
+from os.path import join, dirname, exists, basename
+from ranger.ext.openstruct import DefaultOpenStruct as ostruct
+
+class MetadataManager(object):
+    def __init__(self):
+        # metadata_cache maps filenames to dicts containing their metadata
+        self.metadata_cache = dict()
+        # metafile_cache maps .metadata.json filenames to their entries
+        self.metafile_cache = dict()
+        self.deep_search = DEEP_SEARCH_DEFAULT
+
+    def reset(self):
+        self.metadata_cache.clear()
+        self.metafile_cache.clear()
+
+    def get_metadata(self, filename):
+        try:
+            return ostruct(copy.deepcopy(self.metadata_cache[filename]))
+        except KeyError:
+            try:
+                return ostruct(copy.deepcopy(self._get_entry(filename)))
+            except KeyError:
+                return ostruct()
+
+    def set_metadata(self, filename, update_dict):
+        import json
+        result = None
+        found = False
+
+        if not self.deep_search:
+            metafile = next(self._get_metafile_names(filename))
+            return self._set_metadata_raw(filename, update_dict, metafile)
+
+        metafile = self._get_metafile_name(filename)
+        return self._set_metadata_raw(filename, update_dict, metafile)
+
+    def _set_metadata_raw(self, filename, update_dict, metafile):
+        import json
+        valid = (filename, basename(filename))
+
+        entries = self._get_metafile_content(metafile)
+        try:
+            entry = entries[filename]
+        except KeyError:
+            try:
+                entry = entries[basename(filename)]
+            except KeyError:
+                entry = entries[basename(filename)] = {}
+        entry.update(update_dict)
+
+        # Delete key if the value is empty
+        for key, value in update_dict.items():
+            if value == "":
+                del entry[key]
+
+        # If file's metadata become empty after an update, remove it entirely
+        if entry == {}:
+            try:
+                del entries[filename]
+            except KeyError:
+                try:
+                    del entries[basename(filename)]
+                except KeyError:
+                    pass
+
+        # Full update of the cache, to be on the safe side:
+        self.metadata_cache[filename] = entry
+        self.metafile_cache[metafile] = entries
+
+        with open(metafile, "w") as f:
+            json.dump(entries, f, check_circular=True, indent=2)
+
+    def _get_entry(self, filename):
+        if filename in self.metadata_cache:
+            return self.metadata_cache[filename]
+        else:
+            valid = (filename, basename(filename))
+
+            # Try to find an entry for this file in any of
+            # the applicable .metadata.json files
+            for metafile in self._get_metafile_names(filename):
+                entries = self._get_metafile_content(metafile)
+                # Check for a direct match:
+                if filename in entries:
+                    entry = entries[filename]
+                # Check for a match of the base name:
+                elif basename(filename) in entries:
+                    entry = entries[basename(filename)]
+                else:
+                    # No match found, try another entry
+                    continue
+
+                self.metadata_cache[filename] = entry
+                return entry
+
+            raise KeyError
+
+    def _get_metafile_content(self, metafile):
+        import json
+        if metafile in self.metafile_cache:
+            return self.metafile_cache[metafile]
+        else:
+            if exists(metafile):
+                with open(metafile, "r") as f:
+                    try:
+                        entries = json.load(f)
+                    except ValueError:
+                        raise ValueError("Failed decoding JSON file %s" %
+                                metafile)
+                self.metafile_cache[metafile] = entries
+                return entries
+            else:
+                return {}
+
+    def _get_metafile_names(self, path):
+        # Iterates through the paths of all .metadata.json files that could
+        # influence the metadata of the given file.
+        # When deep_search is deactivated, this only yields the .metadata.json
+        # file in the same directory as the given file.
+
+        base = dirname(path)
+        yield join(base, METADATA_FILE_NAME)
+        if self.deep_search:
+            dirs = base.split("/")[1:]
+            for i in reversed(range(len(dirs))):
+                yield join("/" + "/".join(dirs[0:i]), METADATA_FILE_NAME)
+
+    def _get_metafile_name(self, filename):
+        first = None
+        for metafile in self._get_metafile_names(filename):
+            if first is None:
+                first = metafile
+
+            entries = self._get_metafile_content(metafile)
+            if filename in entries or basename(filename) in entries:
+                return metafile
+
+        # _get_metafile_names should return >0 names, but just in case...:
+        assert first is not None, "failed finding location for .metadata.json"
+        return first
diff --git a/ranger/core/runner.py b/ranger/core/runner.py
index 9066f234..001b6e9a 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """This module is an abstract layer over subprocess.Popen
@@ -142,6 +142,10 @@ class Runner(object):
 
         if 'shell' not in popen_kws:
             popen_kws['shell'] = isinstance(action, str)
+        if popen_kws['shell']:
+            # Set default shell for Popen
+            popen_kws['executable'] = os.environ['SHELL']
+
         if 'stdout' not in popen_kws:
             popen_kws['stdout'] = sys.stdout
         if 'stderr' not in popen_kws:
diff --git a/ranger/core/shared.py b/ranger/core/shared.py
index 759d062b..0e78437f 100644
--- a/ranger/core/shared.py
+++ b/ranger/core/shared.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """Shared objects contain singletons for shared use."""
diff --git a/ranger/core/tab.py b/ranger/core/tab.py
index 2bb85733..7d5b8ee9 100644
--- a/ranger/core/tab.py
+++ b/ranger/core/tab.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import os
diff --git a/ranger/data/mime.types b/ranger/data/mime.types
index 2adc2e99..b3c3e8dc 100644
--- a/ranger/data/mime.types
+++ b/ranger/data/mime.types
@@ -10,15 +10,17 @@
 #
 ###############################################################################
 
-audio/flac       flac
-audio/musepack   mpc mpp mp+
-audio/ogg        oga ogg spx
-audio/wavpack    wv wvc
+audio/flac              flac
+audio/musepack          mpc mpp mp+
+audio/ogg               oga ogg spx
+audio/wavpack           wv wvc
 
-video/mkv        mkv
-video/webm       webm
-video/flash      flv
-video/ogg        ogv ogm
-video/divx       div divx
+video/mkv               mkv
+video/webm              webm
+video/flash             flv
+video/ogg               ogv ogm
+video/divx              div divx
+
+text/x-ruby             rb
+application/javascript  js
 
-text/x-ruby      rb
diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py
index a4e342aa..2228f55e 100644
--- a/ranger/ext/accumulator.py
+++ b/ranger/ext/accumulator.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.ext.direction import Direction
diff --git a/ranger/ext/cached_function.py b/ranger/ext/cached_function.py
index 4ee95ac0..438233b8 100644
--- a/ranger/ext/cached_function.py
+++ b/ranger/ext/cached_function.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2012-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 def cached_function(fnc):
diff --git a/ranger/ext/curses_interrupt_handler.py b/ranger/ext/curses_interrupt_handler.py
index e880a6c1..cc04a06d 100644
--- a/ranger/ext/curses_interrupt_handler.py
+++ b/ranger/ext/curses_interrupt_handler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """Interrupt Signal handler for curses
diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py
index d6b465cd..e85aefb2 100644
--- a/ranger/ext/direction.py
+++ b/ranger/ext/direction.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """This class provides convenient methods for movement operations.
@@ -81,6 +81,9 @@ class Direction(dict):
     def percentage(self):
         return 'percentage' in self and self['percentage']
 
+    def cycle(self):
+        return self.get('cycle') in ('true', 'on', 'yes')
+
     def multiply(self, n):
         for key in ('up', 'right', 'down', 'left'):
             try:
@@ -126,6 +129,8 @@ class Direction(dict):
                 pos += maximum
         else:
             pos += current
+        if self.cycle():
+            return minimum + pos % (maximum + offset - minimum)
         return int(max(min(pos, maximum + offset - 1), minimum))
 
     def select(self, lst, current, pagesize, override=None, offset=1):
diff --git a/ranger/ext/get_executables.py b/ranger/ext/get_executables.py
index bd87607e..e127f1d2 100644
--- a/ranger/ext/get_executables.py
+++ b/ranger/ext/get_executables.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from stat import S_IXOTH, S_IFREG
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index 975aa28a..53a114ab 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 def human_readable(byte, separator=' '):
diff --git a/ranger/ext/iter_tools.py b/ranger/ext/iter_tools.py
index 0b0af8db..e70bfeee 100644
--- a/ranger/ext/iter_tools.py
+++ b/ranger/ext/iter_tools.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from collections import deque
diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py
index d7b24be3..0fc7e340 100644
--- a/ranger/ext/keybinding_parser.py
+++ b/ranger/ext/keybinding_parser.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import sys
diff --git a/ranger/ext/mount_path.py b/ranger/ext/mount_path.py
index 1c11139d..c2e01927 100644
--- a/ranger/ext/mount_path.py
+++ b/ranger/ext/mount_path.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from os.path import realpath, abspath, dirname, ismount
diff --git a/ranger/ext/next_available_filename.py b/ranger/ext/next_available_filename.py
index 65c51d09..74bb48ce 100644
--- a/ranger/ext/next_available_filename.py
+++ b/ranger/ext/next_available_filename.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2011-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import os.path
diff --git a/ranger/ext/openstruct.py b/ranger/ext/openstruct.py
index aea44630..55dd0a40 100644
--- a/ranger/ext/openstruct.py
+++ b/ranger/ext/openstruct.py
@@ -1,6 +1,8 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
+import collections
+
 # prepend __ to arguments because one might use "args"
 # or "keywords" as a keyword argument.
 
@@ -9,3 +11,16 @@ class OpenStruct(dict):
     def __init__(self, *__args, **__keywords):
         dict.__init__(self, *__args, **__keywords)
         self.__dict__ = self
+
+
+class DefaultOpenStruct(collections.defaultdict):
+    """The fusion of dict and struct, with default values"""
+    def __init__(self, *__args, **__keywords):
+        collections.defaultdict.__init__(self, None, *__args, **__keywords)
+        self.__dict__ = self
+
+    def __getattr__(self, name):
+        if name not in self.__dict__:
+            return None
+        else:
+            return self.__dict__[name]
diff --git a/ranger/ext/popen_forked.py b/ranger/ext/popen_forked.py
index 6cc931c6..b3b34e1b 100644
--- a/ranger/ext/popen_forked.py
+++ b/ranger/ext/popen_forked.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2012-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import os
diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py
index 419c044a..78a7422e 100644
--- a/ranger/ext/relative_symlink.py
+++ b/ranger/ext/relative_symlink.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from os import symlink, sep
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 1cc2dcf8..ad24b780 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -1,5 +1,5 @@
 #!/usr/bin/python
-# Copyright (C) 2012-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2012-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """rifle, the file executor/opener of ranger
@@ -9,7 +9,7 @@ When used together with ranger, it doesn't have to be installed to $PATH.
 
 Example usage:
 
-    rifle = Rifle("rilfe.conf")
+    rifle = Rifle("rifle.conf")
     rifle.reload_config()
     rifle.execute(["file1", "file2"])
 """
@@ -23,7 +23,7 @@ __version__ = 'rifle 1.6.1'
 
 # Options and constants that a user might want to change:
 DEFAULT_PAGER = 'less'
-DEFAULT_EDITOR = 'nano'
+DEFAULT_EDITOR = 'vim'
 ASK_COMMAND = 'ask'
 ENCODING = 'utf-8'
 
@@ -210,7 +210,12 @@ class Rifle(object):
         elif function == 'mime':
             return bool(re.search(argument, self._get_mimetype(files[0])))
         elif function == 'has':
-            return argument in get_executables()
+            if argument.startswith("$"):
+                if argument[1:] in os.environ:
+                    return os.environ[argument[1:]] in get_executables()
+                return False
+            else:
+                return argument in get_executables()
         elif function == 'terminal':
             return _is_terminal()
         elif function == 'number':
diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py
index 85656c4c..ba03ae63 100644
--- a/ranger/ext/shell_escape.py
+++ b/ranger/ext/shell_escape.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """Functions to escape metacharacters of arguments for shell commands."""
diff --git a/ranger/ext/signals.py b/ranger/ext/signals.py
index 07ffc899..33996b16 100644
--- a/ranger/ext/signals.py
+++ b/ranger/ext/signals.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """An efficient and minimalistic signaling/hook module.
diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py
index 29ecf40e..6ef8d4c3 100644
--- a/ranger/ext/spawn.py
+++ b/ranger/ext/spawn.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from subprocess import Popen, PIPE
diff --git a/ranger/ext/widestring.py b/ranger/ext/widestring.py
index e9f36fbe..6b00daaf 100644
--- a/ranger/ext/widestring.py
+++ b/ranger/ext/widestring.py
@@ -1,5 +1,5 @@
 # -*- encoding: utf8 -*-
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import sys
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py
index fa0ed914..429395e4 100644
--- a/ranger/gui/ansi.py
+++ b/ranger/gui/ansi.py
@@ -1,5 +1,5 @@
 # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
-# Copyright (C) 2010-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2010-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """A library to help to convert ANSI codes to curses instructions."""
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index 7c5b834f..a65e55bf 100644
--- a/ranger/gui/bar.py
+++ b/ranger/gui/bar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.ext.widestring import WideString, utf_char_width
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index d64b5b40..c3ecf4c8 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """Contains abbreviations to curses color/attribute constants.
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index f7eb22b6..580621e1 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """Colorschemes define colors for specific contexts.
@@ -20,9 +20,8 @@ path/to/ranger/colorschemes/
 context is a struct which contains all entries of CONTEXT_KEYS,
 associated with either True or False.
 
-define which colorscheme to use by having this to your options.py:
-from ranger import colorschemes
-colorscheme = "name"
+Define which colorscheme in your settings (e.g. ~/.config/ranger/rc.conf):
+set colorscheme yourschemename
 """
 
 import os
diff --git a/ranger/gui/context.py b/ranger/gui/context.py
index c335bd23..7301717f 100644
--- a/ranger/gui/context.py
+++ b/ranger/gui/context.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 CONTEXT_KEYS = ['reset', 'error', 'badinfo',
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index e75205d6..35975940 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
 # This software is distributed under the terms of the GNU GPL version 3.
 
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
index 6920cf92..38b58541 100644
--- a/ranger/gui/displayable.py
+++ b/ranger/gui/displayable.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 from ranger.core.shared import FileManagerAware, EnvironmentAware
diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py
index 12e53477..617d42e4 100644
--- a/ranger/gui/mouse_event.py
+++ b/ranger/gui/mouse_event.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import curses
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 1f95ac59..3898a7a5 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 import os
@@ -31,8 +31,6 @@ def _setup_mouse(signal):
     else:
         curses.mousemask(0)
 
-# TODO: progress bar
-# TODO: branch view
 class UI(DisplayableContainer):
     is_set_up = False
     load_mode = False
@@ -117,7 +115,9 @@ class UI(DisplayableContainer):
                 self.win.nodelay(1)
             else:
                 self.win.nodelay(0)
-                curses.halfdelay(20)
+                # Sanitize halfdelay setting
+                halfdelay = min(255, max(1, self.settings.idle_delay // 100))
+                curses.halfdelay(halfdelay)
 
     def destroy(self):
         """Destroy all widgets and turn off curses"""
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index 0edb9c8d..564e9a5f 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The BrowserColumn widget displays the contents of a directory or file."""
@@ -248,16 +248,41 @@ class BrowserColumn(Pager):
             else:
                 tagged_marker = " "
 
+            # Extract linemode-related information from the drawn object
+            metadata = None
+            use_linemode = drawn._linemode
+            if use_linemode == "metatitle":
+                metadata = self.fm.metadata.get_metadata(drawn.path)
+                if not metadata.title:
+                    use_linemode = "filename"
+
+            metakey = hash(repr(sorted(metadata.items()))) if metadata else 0
             key = (self.wid, selected_i == i, drawn.marked, self.main_column,
                     drawn.path in copied, tagged_marker, drawn.infostring,
-                    drawn.vcsfilestatus, drawn.vcsremotestatus, self.fm.do_cut)
+                    drawn.vcsfilestatus, drawn.vcsremotestatus, self.fm.do_cut,
+                    use_linemode, metakey)
 
             if key in drawn.display_data:
                 self.execute_curses_batch(line, drawn.display_data[key])
                 self.color_reset()
                 continue
 
-            text = drawn.basename
+
+            # Deal with the line mode
+            text = ""
+            if use_linemode == "metatitle":
+                assert metadata.title, "Ensure that metadata.title is set!"
+                if metadata.year:
+                    text = "%s - %s" % (metadata.year, metadata.title)
+                else:
+                    text = metadata.title
+            if use_linemode == "filename":
+                text = drawn.drawn_basename
+            elif use_linemode == "permissions":
+                text = "%s %s %s %s" % (drawn.get_permission_string(),
+                        drawn.user, drawn.group, drawn.drawn_basename)
+
+
             if drawn.marked and (self.main_column or \
                     self.settings.display_tags_in_all_columns):
                 text = " " + text
@@ -285,11 +310,21 @@ class BrowserColumn(Pager):
                 space -= vcsstringlen
 
             # info string
-            infostring = self._draw_infostring_display(drawn, space)
-            infostringlen = self._total_len(infostring)
-            if space - infostringlen > 2:
-                predisplay_right = infostring + predisplay_right
-                space -= infostringlen
+            infostring = []
+            infostringlen = 0
+            if use_linemode == "filename":
+                infostring = self._draw_infostring_display(drawn, space)
+            elif use_linemode == "metatitle":
+                if metadata.authors:
+                    authorstring = metadata.authors
+                    if ',' in authorstring:
+                        authorstring = authorstring[0:authorstring.find(",")]
+                    infostring.append([" " + authorstring + " ", ["infostring"]])
+            if infostring:
+                infostringlen = self._total_len(infostring)
+                if space - infostringlen > 2:
+                    predisplay_right = infostring + predisplay_right
+                    space -= infostringlen
 
             textstring = self._draw_text_display(text, space)
             textstringlen = self._total_len(textstring)
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index 618d15a8..5413fc86 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The BrowserView manages a set of BrowserColumns."""
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 91366e7c..97efed8a 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The Console widget implements a vim-like console"""
@@ -214,20 +214,23 @@ class Console(Widget):
                 return unicode_buffer, line, pos
 
         if self.fm.py3:
-            unicode_buffer += key
-            try:
-                decoded = unicode_buffer.encode("latin-1").decode("utf-8")
-            except UnicodeDecodeError:
-                return unicode_buffer, line, pos
-            except UnicodeEncodeError:
-                return unicode_buffer, line, pos
-            else:
+            if len(unicode_buffer) >= 4:
                 unicode_buffer = ""
-                if pos == len(line):
-                    line += decoded
+            if ord(key) in range(0, 256):
+                unicode_buffer += key
+                try:
+                    decoded = unicode_buffer.encode("latin-1").decode("utf-8")
+                except UnicodeDecodeError:
+                    return unicode_buffer, line, pos
+                except UnicodeEncodeError:
+                    return unicode_buffer, line, pos
                 else:
-                    line = line[:pos] + decoded + line[pos:]
-                pos += len(decoded)
+                    unicode_buffer = ""
+                    if pos == len(line):
+                        line += decoded
+                    else:
+                        line = line[:pos] + decoded + line[pos:]
+                    pos += len(decoded)
         else:
             if pos == len(line):
                 line += key
@@ -356,7 +359,11 @@ class Console(Widget):
             return
 
         self.allow_close = True
-        self.fm.execute_console(self.line)
+        if cmd:
+            cmd.execute()
+        else:
+            self.fm.execute_console(self.line)
+
         if self.allow_close:
             self._close_command_prompt(trigger_cancel_function=False)
 
@@ -416,6 +423,7 @@ class Console(Widget):
         else:
             cmd = cls(self.line)
             if cmd and cmd.quick():
+                cmd.quickly_executed = True
                 self.execute(cmd)
 
     def ask(self, text, callback, choices=['y', 'n']):
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index a1417cc2..1dfe72c2 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
 # This software is distributed under the terms of the GNU GPL version 3.
 
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 0b7f2d9b..22b28165 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The statusbar displays information about the current file and directory.
diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py
index 5d2aa475..de31e807 100644
--- a/ranger/gui/widgets/taskview.py
+++ b/ranger/gui/widgets/taskview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The TaskView allows you to modify what the loader is doing."""
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index 38a99a92..55cc25e6 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013  Roman Zimbelmann <hut@lepus.uberspace.de>
+# Copyright (C) 2009-2013  Roman Zimbelmann <hut@hut.pm>
 # This software is distributed under the terms of the GNU GPL version 3.
 
 """The titlebar is the widget at the top, giving you broad overview.
@@ -115,8 +115,9 @@ class TitleBar(Widget):
             bar.add(path.basename, clr, directory=path)
             bar.add('/', clr, fixed=True, directory=path)
 
-        if self.fm.thisfile is not None:
-            bar.add(self.fm.thisfile.basename, 'file')
+        if self.fm.thisfile is not None and \
+                self.settings.show_selection_in_titlebar:
+            bar.add(self.fm.thisfile.drawn_basename, 'file')
 
     def _get_right_part(self, bar):
         # TODO: fix that pressed keys are cut off when chaining CTRL keys
diff --git a/setup.py b/setup.py
index 84325cd9..d38b43ab 100755
--- a/setup.py
+++ b/setup.py
@@ -26,9 +26,9 @@ if __name__ == '__main__':
                 ['doc/ranger.1',
                  'doc/rifle.1']),
             ('share/doc/ranger',
-                ['README',
+                ['README.md',
                  'CHANGELOG',
-                 'doc/HACKING',
+                 'HACKING.md',
                  'doc/colorschemes.txt']),
             ('share/doc/ranger/config/colorschemes',
                 _findall('doc/config/colorschemes')),