about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--doc/ranger.127
-rw-r--r--doc/ranger.pod20
-rw-r--r--doc/rifle.124
-rw-r--r--examples/bash_automatic_cd.sh2
-rw-r--r--examples/rc_emacs.conf8
-rw-r--r--ranger/__init__.py35
-rw-r--r--ranger/api/commands.py5
-rwxr-xr-xranger/config/commands.py10
-rw-r--r--ranger/config/rc.conf8
-rw-r--r--ranger/config/rifle.conf5
-rw-r--r--ranger/container/fsobject.py4
-rw-r--r--ranger/container/settings.py2
-rw-r--r--ranger/core/actions.py16
-rw-r--r--ranger/core/fm.py32
-rw-r--r--ranger/core/linemode.py5
-rw-r--r--ranger/core/main.py45
-rw-r--r--ranger/core/runner.py8
-rwxr-xr-xranger/data/scope.sh9
-rw-r--r--ranger/ext/get_executables.py14
-rw-r--r--ranger/ext/img_display.py78
-rw-r--r--ranger/ext/logutils.py78
-rw-r--r--ranger/ext/shell_escape.py2
-rw-r--r--ranger/ext/spawn.py48
-rw-r--r--ranger/ext/vcs/bzr.py22
-rw-r--r--ranger/ext/vcs/git.py87
-rw-r--r--ranger/ext/vcs/hg.py10
-rw-r--r--ranger/ext/vcs/svn.py25
-rw-r--r--ranger/ext/vcs/vcs.py59
-rw-r--r--ranger/gui/ansi.py2
-rw-r--r--ranger/gui/displayable.py6
-rw-r--r--ranger/gui/ui.py14
-rw-r--r--ranger/gui/widgets/pager.py2
-rw-r--r--ranger/gui/widgets/statusbar.py4
34 files changed, 503 insertions, 218 deletions
diff --git a/README.md b/README.md
index b5fc03ea..5174f1c1 100644
--- a/README.md
+++ b/README.md
@@ -69,12 +69,13 @@ Optional:
 Optional, for enhanced file previews (with "scope.sh"):
 
 * img2txt (from caca-utils) for ASCII-art image previews
-* highlight for syntax highlighting of code
-* atool for previews of archives
+* highlight or pygmentize for syntax highlighting of code
+* atool, acat, bsdtar and/or unrar for previews of archives
 * lynx, w3m or elinks for previews of html pages
 * pdftotext for pdf previews
 * transmission-show for viewing bit-torrent information
 * mediainfo or exiftool for viewing information about media files
+* odt2txt for OpenDocument text files (odt, ods, odp and sxw)
 
 
 Installing
diff --git a/doc/ranger.1 b/doc/ranger.1
index b2ba1aac..26a78a49 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.29)
+.\" 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.7.2" "16/05/16" "ranger manual"
+.TH RANGER 1 "ranger-1.7.2" "10/08/2016" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -287,6 +287,24 @@ This only works in iTerm2 compiled with image preview support, but works over
 ssh.
 .PP
 To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to iterm2.
+.PP
+\fIurxvt\fR
+.IX Subsection "urxvt"
+.PP
+This only works in urxvt compiled with pixbuf support. Does not work over ssh.
+.PP
+Essentially this mode sets an image as a terminal background temporarily, so it
+will break any previously set image background.
+.PP
+To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to urxvt.
+.PP
+\fIurxvt-full\fR
+.IX Subsection "urxvt-full"
+.PP
+The same as urxvt but utilizing not only the preview pane but the whole terminal
+window.
+.PP
+To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to urxvt-full.
 .SS "\s-1SELECTION\s0"
 .IX Subsection "SELECTION"
 The \fIselection\fR is defined as \*(L"All marked files \s-1IF THERE ARE ANY,\s0 otherwise
@@ -337,6 +355,8 @@ can use it for something like this command:
 The macro \f(CW%space\fR expands to a space character. You can use it to add spaces to
 the end of a command when needed, while preventing editors to strip spaces off
 the end of the line automatically.
+.PP
+To write a literal %, you need to escape it by writing %%.
 .SS "\s-1BOOKMARKS\s0"
 .IX Subsection "BOOKMARKS"
 Type \fBm<key>\fR to bookmark the current directory. You can re-enter this
@@ -1029,7 +1049,7 @@ Examples:
 .IP "filter [\fIstring\fR]" 2
 .IX Item "filter [string]"
 Displays only the files which contain the \fIstring\fR in their basename.  Running
-this command without any parameter will reset the fitler.
+this command without any parameter will reset the filter.
 .Sp
 This command is based on the \fIscout\fR command and supports all of its options.
 .IP "filter_inode_type [dfl]" 2
@@ -1168,6 +1188,7 @@ influence its behaviour:
 \& \-m = mark the matching files after pressing enter
 \& \-M = unmark the matching files after pressing enter
 \& \-p = permanent filter: hide non\-matching files after pressing enter
+\& \-r = interpret pattern as a regular expression pattern
 \& \-s = smart case; like \-i unless pattern contains upper case letters
 \& \-t = apply filter and search pattern as you type
 \& \-v = inverts the match
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 7c5a7768..fe2b45eb 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -186,6 +186,22 @@ ssh.
 
 To enable this feature, set the option C<preview_images_method> to iterm2.
 
+=head3 urxvt
+
+This only works in urxvt compiled with pixbuf support. Does not work over ssh.
+
+Essentially this mode sets an image as a terminal background temporarily, so it
+will break any previously set image background.
+
+To enable this feature, set the option C<preview_images_method> to urxvt.
+
+=head3 urxvt-full
+
+The same as urxvt but utilizing not only the preview pane but the whole terminal
+window.
+
+To enable this feature, set the option C<preview_images_method> to urxvt-full.
+
 =head2 SELECTION
 
 The I<selection> is defined as "All marked files IF THERE ARE ANY, otherwise
@@ -234,6 +250,8 @@ The macro %space expands to a space character. You can use it to add spaces to
 the end of a command when needed, while preventing editors to strip spaces off
 the end of the line automatically.
 
+To write a literal %, you need to escape it by writing %%.
+
 =head2 BOOKMARKS
 
 Type B<m<keyE<gt>> to bookmark the current directory. You can re-enter this
@@ -1063,7 +1081,7 @@ Examples:
 =item filter [I<string>]
 
 Displays only the files which contain the I<string> in their basename.  Running
-this command without any parameter will reset the fitler.
+this command without any parameter will reset the filter.
 
 This command is based on the I<scout> command and supports all of its options.
 
diff --git a/doc/rifle.1 b/doc/rifle.1
index 0b67edcc..f90bb6e1 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.29)
+.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -46,7 +46,7 @@
 .ie \n(.g .ds Aq \(aq
 .el       .ds Aq '
 .\"
-.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" If the F register is >0, we'll generate index entries on stderr for
 .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
 .\" entries marked with X<> in POD.  Of course, you'll have to process the
 .\" output yourself in some meaningful fashion.
@@ -54,20 +54,16 @@
 .\" Avoid warning from groff about undefined register 'F'.
 .de IX
 ..
-.nr rF 0
-.if \n(.g .if rF .nr rF 1
-.if (\n(rF:(\n(.g==0)) \{
-.    if \nF \{
-.        de IX
-.        tm Index:\\$1\t\\n%\t"\\$2"
+.if !\nF .nr F 0
+.if \nF>0 \{\
+.    de IX
+.    tm Index:\\$1\t\\n%\t"\\$2"
 ..
-.        if !\nF==2 \{
-.            nr % 0
-.            nr F 2
-.        \}
+.    if !\nF==2 \{\
+.        nr % 0
+.        nr F 2
 .    \}
 .\}
-.rr rF
 .\"
 .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
 .\" Fear.  Run.  Save yourself.  No user-serviceable parts.
@@ -133,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RIFLE 1"
-.TH RIFLE 1 "rifle-1.7.2" "02/24/2016" "rifle manual"
+.TH RIFLE 1 "rifle-1.7.2" "09/03/2016" "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/examples/bash_automatic_cd.sh b/examples/bash_automatic_cd.sh
index 040bf21a..bdac5757 100644
--- a/examples/bash_automatic_cd.sh
+++ b/examples/bash_automatic_cd.sh
@@ -6,6 +6,8 @@
 # the last visited one after ranger quits.
 # To undo the effect of this function, you can type "cd -" to return to the
 # original directory.
+# 
+# On OS X 10 or later, replace `usr/bin/ranger` with `/usr/local/bin/ranger`.
 
 function ranger-cd {
     tempfile="$(mktemp -t tmp.XXXXXX)"
diff --git a/examples/rc_emacs.conf b/examples/rc_emacs.conf
index 39a6d654..694496c2 100644
--- a/examples/rc_emacs.conf
+++ b/examples/rc_emacs.conf
@@ -73,6 +73,14 @@ set preview_images false
 #   Preview images in full color using iTerm2 image previews
 #   (http://iterm2.com/images.html). This requires using iTerm2 compiled
 #   with image preview support.
+#
+# * urxvt:
+#   Preview images in full color using urxvt image backgrounds. This
+#   requires using urxvt compiled with pixbuf support.
+#
+# * urxvt-full:
+#   The same as urxvt but utilizing not only the preview pane but the
+#   whole terminal window.
 set preview_images_method w3m
 
 # Use a unicode "..." character to mark cut-off filenames?
diff --git a/ranger/__init__.py b/ranger/__init__.py
index 4d8e4afe..9c4f8922 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -29,44 +29,9 @@ CACHEDIR = os.path.expanduser("~/.cache/ranger")
 USAGE = '%prog [options] [path]'
 VERSION = 'ranger-master %s\n\nPython %s' % (__version__, sys.version)
 
-try:
-    ExceptionClass = FileNotFoundError
-except NameError:
-    ExceptionClass = IOError
-try:
-    LOGFILE = tempfile.gettempdir() + '/ranger_errorlog'
-except ExceptionClass:
-    LOGFILE = '/dev/null'
-del ExceptionClass
 
 # If the environment variable XDG_CONFIG_HOME is non-empty, CONFDIR is ignored
 # and the configuration directory will be $XDG_CONFIG_HOME/ranger instead.
 CONFDIR = '~/.config/ranger'
 
-# Debugging functions.  These will be activated when run with --debug.
-# Example usage in the code:
-# import ranger; ranger.log("hello world")
-
-
-def log(*objects, **keywords):
-    """Writes objects to a logfile (for the purpose of debugging only.)
-    Has the same arguments as print() in python3.
-    """
-    from ranger import arg
-    if LOGFILE is None or not arg.debug or arg.clean:
-        return
-    start = keywords.get('start', 'ranger:')
-    sep   = keywords.get('sep', ' ')
-    end   = keywords.get('end', '\n')
-    _file = keywords['file'] if 'file' in keywords else open(LOGFILE, 'a')
-    _file.write(sep.join(map(str, (start, ) + objects)) + end)
-
-
-def log_traceback():
-    from ranger import arg
-    if LOGFILE is None or not arg.debug or arg.clean:
-        return
-    import traceback
-    traceback.print_stack(file=open(LOGFILE, 'a'))
-
 from ranger.core.main import main
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 9b8ec7d5..93c50adb 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -236,6 +236,11 @@ class Command(FileManagerAware):
                     break
         return flags, rest
 
+    @lazy_property
+    def log(self):
+        import logging
+        return logging.getLogger('ranger.commands.' + self.__class__.__name__)
+
     # XXX: Lazy properties? Not so smart? self.line can change after all!
     @lazy_property
     def _tabinsert_left(self):
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index a5117696..18efbfc1 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -493,14 +493,8 @@ class terminal(Command):
     Spawns an "x-terminal-emulator" starting in the current directory.
     """
     def execute(self):
-        import os
-        from ranger.ext.get_executables import get_executables
-        command = os.environ.get('TERMCMD', os.environ.get('TERM'))
-        if command not in get_executables():
-            command = 'x-terminal-emulator'
-        if command not in get_executables():
-            command = 'xterm'
-        self.fm.run(command, flags='f')
+        from ranger.ext.get_executables import get_term
+        self.fm.run(get_term(), flags='f')
 
 
 class delete(Command):
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index cc7f5007..f25d3a0c 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -79,6 +79,14 @@ set preview_images false
 #   Preview images in full color using iTerm2 image previews
 #   (http://iterm2.com/images.html). This requires using iTerm2 compiled
 #   with image preview support.
+#
+# * urxvt:
+#   Preview images in full color using urxvt image backgrounds. This
+#   requires using urxvt compiled with pixbuf support.
+#
+# * urxvt-full:
+#   The same as urxvt but utilizing not only the preview pane but the
+#   whole terminal window.
 set preview_images_method w3m
 
 # Use a unicode "..." character to mark cut-off filenames?
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index d6165f21..aadc2f2c 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -181,8 +181,11 @@ ext xcf,                    X, flag f = gimp -- "$@"
 #-------------------------------------------
 # Archives
 #-------------------------------------------
+
+# avoid password prompt by providing empty password
+ext 7z, has 7z = 7z -p l "$@" | "$PAGER"
 # This requires atool
-ext 7z|ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,  has als     = als -- "$@" | "$PAGER"
+ext ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,  has als     = als -- "$@" | "$PAGER"
 ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has als     = als -- "$@" | "$PAGER"
 ext 7z|ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,  has aunpack = aunpack -- "$@"
 ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has aunpack = aunpack -- "$@"
diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py
index d73fa76c..9b312e6d 100644
--- a/ranger/container/fsobject.py
+++ b/ranger/container/fsobject.py
@@ -20,7 +20,7 @@ from pwd import getpwuid
 from ranger.core.linemode import *
 from ranger.core.shared import FileManagerAware, SettingsAware
 from ranger.ext.shell_escape import shell_escape
-from ranger.ext.spawn import spawn
+from ranger.ext import spawn
 from ranger.ext.lazy_property import lazy_property
 from ranger.ext.human_readable import human_readable
 
@@ -131,7 +131,7 @@ class FileSystemObject(FileManagerAware, SettingsAware):
     @lazy_property
     def filetype(self):
         try:
-            return spawn(["file", '-Lb', '--mime-type', self.path])
+            return spawn.check_output(["file", '-Lb', '--mime-type', self.path])
         except OSError:
             return ""
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index a5d71874..1faf5860 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -79,7 +79,7 @@ ALLOWED_SETTINGS = {
 ALLOWED_VALUES = {
     'confirm_on_delete': ['always', 'multiple', 'never'],
     'line_numbers': ['false', 'absolute', 'relative'],
-    'preview_images_method': ['w3m', 'iterm2'],
+    'preview_images_method': ['w3m', 'iterm2', 'urxvt', 'urxvt-full'],
     'vcs_backend_bzr': ['enabled', 'local', 'disabled'],
     'vcs_backend_git': ['enabled', 'local', 'disabled'],
     'vcs_backend_hg': ['enabled', 'local', 'disabled'],
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index c119c501..1b35e57b 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -13,6 +13,7 @@ from inspect import cleandoc
 from stat import S_IEXEC
 from hashlib import sha1
 from sys import version_info
+from logging import getLogger
 
 import ranger
 from ranger.ext.direction import Direction
@@ -31,6 +32,8 @@ from ranger.core.linemode import DEFAULT_LINEMODE
 
 MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
 
+log = getLogger(__name__)
+
 
 class _MacroTemplate(string.Template):
     """A template for substituting macros in commands"""
@@ -152,7 +155,7 @@ class Actions(FileManagerAware, SettingsAware):
         elif bad is True and ranger.arg.debug:
             raise Exception(str(text))
         text = str(text)
-        self.log.appendleft(text)
+        log.debug("Command notify invoked: [Bad: {0}, Text: '{1}']".format(bad, text))
         if self.ui and self.ui.is_on:
             self.ui.status.notify("  ".join(text.split("\n")),
                     duration=duration, bad=bad)
@@ -344,9 +347,10 @@ class Actions(FileManagerAware, SettingsAware):
         Load a config file.
         """
         filename = os.path.expanduser(filename)
+        log.debug("Sourcing config file '{0}'".format(filename))
         with open(filename, 'r') as f:
             for line in f:
-                line = line.lstrip().rstrip("\r\n")
+                line = line.strip(" \r\n")
                 if line.startswith("#") or not line.strip():
                     continue
                 try:
@@ -419,7 +423,7 @@ class Actions(FileManagerAware, SettingsAware):
         Example:
         self.move(down=4, pages=True)  # moves down by 4 pages.
         self.move(to=2, pages=True)  # moves to page 2.
-        self.move(to=1, percentage=True)  # moves to 80%
+        self.move(to=80, percentage=True)  # moves to 80%
         """
         cwd = self.thisdir
         direction = Direction(kw)
@@ -869,11 +873,13 @@ class Actions(FileManagerAware, SettingsAware):
             self.notify("Could not find manpage.", bad=True)
 
     def display_log(self):
+        logs = list(self.get_log())
         pager = self.ui.open_pager()
-        if self.log:
-            pager.set_source(["Message Log:"] + list(self.log))
+        if logs:
+            pager.set_source(["Message Log:"] + logs)
         else:
             pager.set_source(["Message Log:", "No messages!"])
+        pager.move(to=100, percentage=True)
 
     def display_file(self):
         if not self.thisfile or not self.thisfile.is_file:
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 523239c1..5d630464 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -5,6 +5,7 @@
 
 from time import time
 from collections import deque
+import logging
 import mimetypes
 import os.path
 import pwd
@@ -15,6 +16,7 @@ import sys
 import ranger.api
 from ranger.core.actions import Actions
 from ranger.core.tab import Tab
+from ranger.container import settings
 from ranger.container.tags import Tags, TagsDummy
 from ranger.gui.ui import UI
 from ranger.container.bookmarks import Bookmarks
@@ -24,8 +26,10 @@ from ranger.core.metadata import MetadataManager
 from ranger.ext.rifle import Rifle
 from ranger.container.directory import Directory
 from ranger.ext.signals import SignalDispatcher
-from ranger import __version__
 from ranger.core.loader import Loader
+from ranger.ext import logutils
+
+log = logging.getLogger(__name__)
 
 
 class FM(Actions, SignalDispatcher):
@@ -49,7 +53,6 @@ class FM(Actions, SignalDispatcher):
             self.ui = ui
         self.start_paths = paths
         self.directories = dict()
-        self.log = deque(maxlen=20)
         self.bookmarks = bookmarks
         self.current_tab = 1
         self.tabs = {}
@@ -70,10 +73,6 @@ class FM(Actions, SignalDispatcher):
         self.hostname = socket.gethostname()
         self.home_path = os.path.expanduser('~')
 
-        self.log.append('ranger {0} started! Process ID is {1}.'
-                .format(__version__, os.getpid()))
-        self.log.append('Running on Python ' + sys.version.replace('\n', ''))
-
         mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
         mimetypes.knownfiles.append(self.relpath('data/mime.types'))
         self.mimetypes = mimetypes.MimeTypes()
@@ -97,7 +96,13 @@ class FM(Actions, SignalDispatcher):
             rifleconf = self.relpath('config/rifle.conf')
         self.rifle = Rifle(rifleconf)
         self.rifle.reload_config()
-        self.image_displayer = self._get_image_displayer()
+
+        def set_image_displayer():
+            self.image_displayer = self._get_image_displayer()
+        set_image_displayer()
+        self.settings.signal_bind('setopt.preview_images_method',
+                set_image_displayer,
+                priority=settings.SIGNAL_PRIORITY_AFTER_SYNC)
 
         if not ranger.arg.clean and self.tags is None:
             self.tags = Tags(self.confpath('tagged'))
@@ -198,11 +203,24 @@ class FM(Actions, SignalDispatcher):
                 if debug:
                     raise
 
+    def get_log(self):
+        """Return the current log
+
+        The log is returned as a list of string
+        """
+        for log in logutils.log_queue:
+            for line in log.split('\n'):
+                yield line
+
     def _get_image_displayer(self):
         if self.settings.preview_images_method == "w3m":
             return W3MImageDisplayer()
         elif self.settings.preview_images_method == "iterm2":
             return ITerm2ImageDisplayer()
+        elif self.settings.preview_images_method == "urxvt":
+            return URXVTImageDisplayer()
+        elif self.settings.preview_images_method == "urxvt-full":
+            return URXVTImageFSDisplayer()
         else:
             return ImageDisplayer()
 
diff --git a/ranger/core/linemode.py b/ranger/core/linemode.py
index 96557515..c7839723 100644
--- a/ranger/core/linemode.py
+++ b/ranger/core/linemode.py
@@ -7,6 +7,7 @@ import sys
 from abc import *
 from datetime import datetime
 from ranger.ext.human_readable import human_readable
+from ranger.ext import spawn
 
 DEFAULT_LINEMODE = "filename"
 
@@ -97,9 +98,9 @@ class FileInfoLinemode(LinemodeBase):
 
     def infostring(self, file, metadata):
         if not file.is_directory:
-            from subprocess import check_output, CalledProcessError
+            from subprocess import Popen, PIPE, CalledProcessError
             try:
-                fileinfo = check_output(["file", "-bL", file.path]).strip()
+                fileinfo = spawn.check_output(["file", "-bL", file.path]).strip()
             except CalledProcessError:
                 return "unknown"
             if sys.version_info[0] >= 3:
diff --git a/ranger/core/main.py b/ranger/core/main.py
index 118a7480..b0e3b600 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -6,6 +6,10 @@
 import os.path
 import sys
 import tempfile
+from ranger import __version__
+from logging import getLogger
+
+log = getLogger(__name__)
 
 
 def main():
@@ -15,6 +19,14 @@ def main():
     from ranger.container.settings import Settings
     from ranger.core.shared import FileManagerAware, SettingsAware
     from ranger.core.fm import FM
+    from ranger.ext.logutils import setup_logging
+
+    ranger.arg = arg = parse_arguments()
+    setup_logging(debug=arg.debug, logfile=arg.logfile)
+
+    log.info("Ranger version {0}".format(__version__))
+    log.info('Running on Python ' + sys.version.replace('\n', ''))
+    log.info("Process ID is {0}".format(os.getpid()))
 
     try:
         locale.setlocale(locale.LC_ALL, '')
@@ -31,7 +43,9 @@ def main():
     if 'SHELL' not in os.environ:
         os.environ['SHELL'] = 'sh'
 
-    ranger.arg = arg = parse_arguments()
+    log.debug("config dir: '{0}'".format(arg.confdir))
+    log.debug("cache dir: '{0}'".format(arg.cachedir))
+
     if arg.copy_config is not None:
         fm = FM()
         fm.copy_config_files(arg.copy_config)
@@ -73,8 +87,6 @@ def main():
                    "deprecated.\nPlease use the standalone file launcher "
                    "'rifle' instead.\n")
 
-            def print_function(string):
-                print(string)
             from ranger.ext.rifle import Rifle
             fm = FM()
             if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')):
@@ -112,7 +124,7 @@ def main():
         if fm.username == 'root':
             fm.settings.preview_files = False
             fm.settings.use_preview_script = False
-            fm.log.append("Running as root, disabling the file previews.")
+            log.info("Running as root, disabling the file previews.")
         if not arg.debug:
             from ranger.ext import curses_interrupt_handler
             curses_interrupt_handler.install_interrupt_handler()
@@ -199,6 +211,8 @@ def parse_arguments():
             help="activate debug mode")
     parser.add_option('-c', '--clean', action='store_true',
             help="don't touch/require any config files. ")
+    parser.add_option('--logfile', type='string', metavar='file',
+            help="log file to use, '-' for stderr")
     parser.add_option('-r', '--confdir', type='string',
             metavar='dir', default=default_confdir,
             help="change the configuration directory. (%default)")
@@ -265,14 +279,18 @@ def load_settings(fm, clean):
         allow_access_to_confdir(ranger.arg.confdir, True)
 
         # Load custom commands
-        if os.path.exists(fm.confpath('commands.py')):
+        custom_comm_path = fm.confpath('commands.py')
+        if os.path.exists(custom_comm_path):
             old_bytecode_setting = sys.dont_write_bytecode
             sys.dont_write_bytecode = True
             try:
                 import commands
                 fm.commands.load_commands_from_module(commands)
-            except ImportError:
-                pass
+            except ImportError as e:
+                log.debug("Failed to import custom commands from '{0}'".format(custom_comm_path))
+                log.exception(e)
+            else:
+                log.debug("Loaded custom commands from '{0}'".format(custom_comm_path))
             sys.dont_write_bytecode = old_bytecode_setting
 
         allow_access_to_confdir(ranger.arg.confdir, False)
@@ -297,6 +315,7 @@ def load_settings(fm, clean):
             pass
         else:
             if not os.path.exists(fm.confpath('plugins', '__init__.py')):
+                log.debug("Creating missing '__init__.py' file in plugin folder")
                 f = open(fm.confpath('plugins', '__init__.py'), 'w')
                 f.close()
 
@@ -313,12 +332,12 @@ def load_settings(fm, clean):
                     else:
                         module = importlib.import_module('plugins.' + plugin)
                         fm.commands.load_commands_from_module(module)
-                    fm.log.append("Loaded plugin '%s'." % plugin)
+                    log.debug("Loaded plugin '{0}'".format(plugin))
                 except Exception as e:
-                    fm.log.append("Error in plugin '%s'" % plugin)
-                    import traceback
-                    for line in traceback.format_exception_only(type(e), e):
-                        fm.log.append(line)
+                    mex = "Error while loading plugin '{0}'".format(plugin)
+                    log.error(mex)
+                    log.exception(e)
+                    fm.notify(mex, bad=True)
             ranger.fm = None
 
         # COMPAT: Load the outdated options.py
@@ -361,6 +380,8 @@ def allow_access_to_confdir(confdir, allow):
                 print("To run ranger without the need for configuration")
                 print("files, use the --clean option.")
                 raise SystemExit()
+        else:
+            log.debug("Created config directory '{0}'".format(confdir))
         if confdir not in sys.path:
             sys.path[0:0] = [confdir]
     else:
diff --git a/ranger/core/runner.py b/ranger/core/runner.py
index 6d03f8fb..78d52764 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -25,7 +25,7 @@ t: run application in a new terminal window
 import os
 import sys
 from subprocess import Popen, PIPE
-from ranger.ext.get_executables import get_executables
+from ranger.ext.get_executables import get_executables, get_term
 from ranger.ext.popen_forked import Popen_forked
 
 
@@ -194,11 +194,7 @@ class Runner(object):
         if 't' in context.flags:
             if 'DISPLAY' not in os.environ:
                 return self._log("Can not run with 't' flag, no display found!")
-            term = os.environ.get('TERMCMD', os.environ.get('TERM'))
-            if term not in get_executables():
-                term = 'x-terminal-emulator'
-            if term not in get_executables():
-                term = 'xterm'
+            term = get_term()
             if isinstance(action, str):
                 action = term + ' -e ' + action
             else:
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 669d1e34..a0fb2ec0 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -65,14 +65,18 @@ fi
 
 case "$extension" in
     # Archive extensions:
-    7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
+    a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
     rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)
         try als "$path" && { dump | trim; exit 0; }
         try acat "$path" && { dump | trim; exit 3; }
         try bsdtar -lf "$path" && { dump | trim; exit 0; }
         exit 1;;
     rar)
+        # avoid password prompt by providing empty password
         try unrar -p- lt "$path" && { dump | trim; exit 0; } || exit 1;;
+    7z)
+        # avoid password prompt by providing empty password
+        try 7z -p l "$path" && { dump | trim; exit 0; } || exit 1;;
     # PDF documents:
     pdf)
         try pdftotext -l 10 -nopgbrk -q "$path" - && \
@@ -80,6 +84,9 @@ case "$extension" in
     # BitTorrent Files
     torrent)
         try transmission-show "$path" && { dump | trim; exit 5; } || exit 1;;
+    # ODT Files
+    odt|ods|odp|sxw)
+        try odt2txt "$path" && { dump | trim; exit 5; } || exit 1;;
     # HTML Pages:
     htm|html|xhtml)
         try w3m    -dump "$path" && { dump | trim | fmt -s -w $width; exit 4; }
diff --git a/ranger/ext/get_executables.py b/ranger/ext/get_executables.py
index ee988e49..f2a31345 100644
--- a/ranger/ext/get_executables.py
+++ b/ranger/ext/get_executables.py
@@ -4,6 +4,7 @@
 from stat import S_IXOTH, S_IFREG
 from ranger.ext.iter_tools import unique
 from os import listdir, environ, stat
+import shlex
 
 
 _cached_executables = None
@@ -44,3 +45,16 @@ def get_executables_uncached(*paths):
             if filestat.st_mode & (S_IXOTH | S_IFREG):
                 executables.add(item)
     return executables
+
+
+def get_term():
+    """Get the user terminal executable name.
+
+    Either $TERMCMD, $TERM, "x-terminal-emulator" or "xterm", in this order.
+    """
+    command = environ.get('TERMCMD', environ.get('TERM'))
+    if shlex.split(command)[0] not in get_executables():
+        command = 'x-terminal-emulator'
+        if command not in get_executables():
+            command = 'xterm'
+    return command
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index ada7b31c..0e6b08fe 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -6,7 +6,7 @@
 """Interface for drawing images into the console
 
 This module provides functions to draw images in the terminal using supported
-implementations, which are currently w3m and iTerm2.
+implementations, which are currently w3m, iTerm2 and urxvt.
 """
 
 import base64
@@ -295,3 +295,79 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware):
             return 0, 0
         file_handle.close()
         return width, height
+
+
+class URXVTImageDisplayer(ImageDisplayer, FileManagerAware):
+    """Implementation of ImageDisplayer working by setting the urxvt
+    background image "under" the preview pane.
+
+    Ranger must be running in urxvt for this to work.
+
+    """
+
+    def _get_max_sizes(self):
+        """Use the whole terminal."""
+        w = 100
+        h = 100
+        return w, h
+
+    def _get_centered_offsets(self):
+        """Center the image."""
+        x = 50
+        y = 50
+        return x, y
+
+    def _get_sizes(self):
+        """Return the width and height of the preview pane in relation to the
+        whole terminal window.
+
+        """
+        if self.fm.ui.pager.visible:
+            return self._get_max_sizes()
+
+        total_columns_ratio = sum(self.fm.settings.column_ratios)
+        preview_column_ratio = self.fm.settings.column_ratios[-1]
+        w = int((100 * preview_column_ratio) / total_columns_ratio)
+        h = 100  # As much as possible while preserving the aspect ratio.
+        return w, h
+
+    def _get_offsets(self):
+        """Return the offsets of the image center."""
+        if self.fm.ui.pager.visible:
+            return self._get_centered_offsets()
+
+        x = 100  # Right-aligned.
+        y = 2    # TODO: Use the font size to calculate this offset.
+        return x, y
+
+    def draw(self, path, start_x, start_y, width, height):
+        # The coordinates in the arguments are ignored as urxvt takes
+        # the coordinates in a non-standard way: the position of the
+        # image center as a percentage of the terminal size. As a
+        # result all values below are in percents.
+
+        x, y = self._get_offsets()
+        w, h = self._get_sizes()
+
+        sys.stdout.write("\033]20;{path};{w}x{h}+{x}+{y}:op=keep-aspect\a".format(**vars()))
+        sys.stdout.flush()
+
+    def clear(self, start_x, start_y, width, height):
+        sys.stdout.write("\033]20;;100x100+1000+1000\a")
+        sys.stdout.flush()
+
+    def quit(self):
+        sys.stdout.write("\033]20;;100x100+1000+1000\a")
+        sys.stdout.flush()
+
+
+class URXVTImageFSDisplayer(URXVTImageDisplayer):
+    """URXVTImageDisplayer that utilizes the whole terminal."""
+
+    def _get_sizes(self):
+        """Use the whole terminal."""
+        return self._get_max_sizes()
+
+    def _get_offsets(self):
+        """Center the image."""
+        return self._get_centered_offsets()
diff --git a/ranger/ext/logutils.py b/ranger/ext/logutils.py
new file mode 100644
index 00000000..0de6c333
--- /dev/null
+++ b/ranger/ext/logutils.py
@@ -0,0 +1,78 @@
+import logging
+from collections import deque
+
+LOG_FORMAT = "[%(levelname)s] %(message)s"
+LOG_FORMAT_EXT = "%(asctime)s,%(msecs)d [%(name)s] |%(levelname)s| %(message)s"
+LOG_DATA_FORMAT = "%H:%M:%S"
+
+
+class QueueHandler(logging.Handler):
+    """
+    This handler store logs events into a queue.
+    """
+
+    def __init__(self, queue):
+        """
+        Initialize an instance, using the passed queue.
+        """
+        logging.Handler.__init__(self)
+        self.queue = queue
+
+    def enqueue(self, record):
+        """
+        Enqueue a log record.
+        """
+        self.queue.append(record)
+
+    def emit(self, record):
+        self.enqueue(self.format(record))
+
+
+log_queue = deque(maxlen=1000)
+concise_formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=LOG_DATA_FORMAT)
+extended_formatter = logging.Formatter(fmt=LOG_FORMAT_EXT, datefmt=LOG_DATA_FORMAT)
+
+
+def setup_logging(debug=True, logfile=None):
+    """
+    All the produced logs using the standard logging function
+    will be collected in a queue by the `queue_handler` as well
+    as outputted on the standard error `stderr_handler`.
+
+    The verbosity and the format of the log message is
+    controlled by the `debug` parameter
+
+     - debug=False:
+            a concise log format will be used, debug messsages will be discarded
+            and only important message will be passed to the `stderr_handler`
+
+     - debug=True:
+            an extended log format will be used, all messages will be processed
+            by both the handlers
+    """
+    root_logger = logging.getLogger()
+
+    if debug:
+        # print all logging in extended format
+        log_level = logging.DEBUG
+        formatter = extended_formatter
+    else:
+        # print only warning and critical message
+        # in a concise format
+        log_level = logging.INFO
+        formatter = concise_formatter
+
+    handlers = []
+    handlers.append(QueueHandler(log_queue))
+    if logfile:
+        if logfile is '-':
+            handlers.append(logging.StreamHandler())
+        else:
+            handlers.append(logging.FileHandler(logfile))
+
+    for handler in handlers:
+        handler.setLevel(log_level)
+        handler.setFormatter(formatter)
+        root_logger.addHandler(handler)
+
+    root_logger.setLevel(0)
diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py
index fe542084..d57ff339 100644
--- a/ranger/ext/shell_escape.py
+++ b/ranger/ext/shell_escape.py
@@ -3,7 +3,7 @@
 
 """Functions to escape metacharacters of arguments for shell commands."""
 
-META_CHARS = (' ', "'", '"', '`', '&', '|', ';',
+META_CHARS = (' ', "'", '"', '`', '&', '|', ';', '#',
         '$', '!', '(', ')', '[', ']', '<', '>', '\t')
 UNESCAPABLE = set(map(chr, list(range(9)) + list(range(10, 32))
         + list(range(127, 256))))
diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py
index 7c5c921c..77983341 100644
--- a/ranger/ext/spawn.py
+++ b/ranger/ext/spawn.py
@@ -1,18 +1,44 @@
 # This file is part of ranger, the console file manager.
 # License: GNU GPL version 3, see the file "AUTHORS" for details.
 
-from subprocess import Popen, PIPE
+from os import devnull
+from subprocess import Popen, PIPE, CalledProcessError
 ENCODING = 'utf-8'
 
 
-def spawn(*args):
-    """Runs a program, waits for its termination and returns its stdout"""
-    if len(args) == 1:
-        popen_arguments = args[0]
-        shell = isinstance(popen_arguments, str)
+def check_output(popenargs, **kwargs):
+    """Runs a program, waits for its termination and returns its output
+
+    This function is functionally identical to python 2.7's subprocess.check_output,
+    but is favored due to python 2.6 compatibility.
+
+    Will be run through shell if popenargs is a string, otherwise the command
+    is executed directly.
+
+    The keyword argument `decode` determines if the output shall be decoded
+    with the encoding ENCODING.
+
+    Further keyword arguments are passed to Popen.
+    """
+
+    do_decode = kwargs.pop('decode', True)
+    kwargs.setdefault('stdout', PIPE)
+    kwargs.setdefault('shell', isinstance(popenargs, str))
+
+    if 'stderr' in kwargs:
+        process = Popen(popenargs, **kwargs)
+        stdout, _ = process.communicate()
     else:
-        popen_arguments = args
-        shell = False
-    process = Popen(popen_arguments, stdout=PIPE, shell=shell)
-    stdout, stderr = process.communicate()
-    return stdout.decode(ENCODING)
+        with open(devnull, mode='w') as fd_devnull:
+            process = Popen(popenargs, stderr=fd_devnull, **kwargs)
+            stdout, _ = process.communicate()
+
+    if process.returncode != 0:
+        error = CalledProcessError(process.returncode, popenargs)
+        error.output = stdout
+        raise error
+
+    if do_decode and stdout is not None:
+        stdout = stdout.decode(ENCODING)
+
+    return stdout
diff --git a/ranger/ext/vcs/bzr.py b/ranger/ext/vcs/bzr.py
index 5decc8b1..d0db1faf 100644
--- a/ranger/ext/vcs/bzr.py
+++ b/ranger/ext/vcs/bzr.py
@@ -25,7 +25,7 @@ class Bzr(Vcs):
     def _remote_url(self):
         """Remote url"""
         try:
-            return self._run(['config', 'parent_location']).rstrip('\n') or None
+            return self._run(['config', 'parent_location']) or None
         except VcsError:
             return None
 
@@ -87,29 +87,29 @@ class Bzr(Vcs):
         statuses = set()
 
         # Paths with status
-        output = self._run(['status', '--short', '--no-classify']).rstrip('\n')
-        if not output:
+        lines = self._run(['status', '--short', '--no-classify']).split('\n')
+        if not lines:
             return 'sync'
-        for line in output.split('\n'):
+        for line in lines:
             statuses.add(self._status_translate(line[:2]))
 
         for status in self.DIRSTATUSES:
             if status in statuses:
                 return status
+
         return 'sync'
 
     def data_status_subpaths(self):
         statuses = {}
 
         # Ignored
-        output = self._run(['ls', '--null', '--ignored']).rstrip('\x00')
-        if output:
-            for path in output.split('\x00'):
-                statuses[path] = 'ignored'
+        paths = self._run(['ls', '--null', '--ignored']).split('\0')[:-1]
+        for path in paths:
+            statuses[path] = 'ignored'
 
         # Paths with status
-        output = self._run(['status', '--short', '--no-classify']).rstrip('\n')
-        for line in output.split('\n'):
+        lines = self._run(['status', '--short', '--no-classify']).split('\n')
+        for line in lines:
             statuses[os.path.normpath(line[4:])] = self._status_translate(line[:2])
 
         return statuses
@@ -121,7 +121,7 @@ class Bzr(Vcs):
 
     def data_branch(self):
         try:
-            return self._run(['nick']).rstrip('\n') or None
+            return self._run(['nick']) or None
         except VcsError:
             return None
 
diff --git a/ranger/ext/vcs/git.py b/ranger/ext/vcs/git.py
index 8f4d9ff8..06e066d2 100644
--- a/ranger/ext/vcs/git.py
+++ b/ranger/ext/vcs/git.py
@@ -4,13 +4,19 @@
 """Git module"""
 
 from datetime import datetime
-import json
 import os
 import re
+import unicodedata
 
 from .vcs import Vcs, VcsError
 
 
+def string_control_replace(string, replacement):
+    """Replace all unicode control characters with replacement"""
+    return ''.join(
+        (replacement if unicodedata.category(char)[0] == 'C' else char for char in string))
+
+
 class Git(Vcs):
     """VCS implementation for Git"""
     _status_translations = (
@@ -30,26 +36,17 @@ class Git(Vcs):
 
     def _head_ref(self):
         """Returns HEAD reference"""
-        return self._run(['symbolic-ref', self.HEAD]).rstrip('\n') or None
+        return self._run(['symbolic-ref', self.HEAD]) or None
 
     def _remote_ref(self, ref):
         """Returns remote reference associated to given ref"""
         if ref is None:
             return None
-        return self._run(['for-each-ref', '--format=%(upstream)', ref]).rstrip('\n') or None
+        return self._run(['for-each-ref', '--format=%(upstream)', ref]) or None
 
     def _log(self, refspec=None, maxres=None, filelist=None):
         """Returns an array of dicts containing revision info for refspec"""
-        args = [
-            '--no-pager', 'log',
-            '--pretty={'
-            '%x00short%x00:%x00%h%x00,'
-            '%x00revid%x00:%x00%H%x00,'
-            '%x00author%x00:%x00%an <%ae>%x00,'
-            '%x00date%x00:%ct,'
-            '%x00summary%x00:%x00%s%x00'
-            '}'
-        ]
+        args = ['--no-pager', 'log', '--pretty=%h%x00%H%x00%an <%ae>%x00%ct%x00%s%x00%x00']
         if refspec:
             args += ['-1', refspec]
         elif maxres:
@@ -58,18 +55,22 @@ class Git(Vcs):
             args += ['--'] + filelist
 
         try:
-            output = self._run(args).rstrip('\n')
+            output = self._run(args)
         except VcsError:
             return None
         if not output:
             return None
 
         log = []
-        for line in output\
-                .replace('\\', '\\\\').replace('"', '\\"').replace('\x00', '"').split('\n'):
-            line = json.loads(line)
-            line['date'] = datetime.fromtimestamp(line['date'])
-            log.append(line)
+        for line in output[:-2].split('\0\0\n'):
+            commit_hash_abbrev, commit_hash, author, timestamp, subject = line.split('\0')
+            log.append({
+                'short': commit_hash_abbrev,
+                'revid': commit_hash,
+                'author': string_control_replace(author, ' '),
+                'date': datetime.fromtimestamp(int(timestamp)),
+                'summary': string_control_replace(subject, ' '),
+            })
         return log
 
     def _status_translate(self, code):
@@ -100,10 +101,10 @@ class Git(Vcs):
 
         # Paths with status
         skip = False
-        output = self._run(['status', '--porcelain', '-z']).rstrip('\x00')
-        if not output:
+        lines = self._run(['status', '--porcelain', '-z']).split('\0')[:-1]
+        if not lines:
             return 'sync'
-        for line in output.split('\x00'):
+        for line in lines:
             if skip:
                 skip = False
                 continue
@@ -114,39 +115,37 @@ class Git(Vcs):
         for status in self.DIRSTATUSES:
             if status in statuses:
                 return status
+
         return 'sync'
 
     def data_status_subpaths(self):
         statuses = {}
 
         # Ignored directories
-        output = self._run([
+        paths = self._run([
             'ls-files', '-z', '--others', '--directory', '--ignored', '--exclude-standard'
-        ]).rstrip('\x00')
-        if output:
-            for path in output.split('\x00'):
-                if path.endswith('/'):
-                    statuses[os.path.normpath(path)] = 'ignored'
+        ]).split('\0')[:-1]
+        for path in paths:
+            if path.endswith('/'):
+                statuses[os.path.normpath(path)] = 'ignored'
 
         # Empty directories
-        output = self._run(
-            ['ls-files', '-z', '--others', '--directory', '--exclude-standard']).rstrip('\x00')
-        if output:
-            for path in output.split('\x00'):
-                if path.endswith('/'):
-                    statuses[os.path.normpath(path)] = 'none'
+        paths = self._run(
+            ['ls-files', '-z', '--others', '--directory', '--exclude-standard']).split('\0')[:-1]
+        for path in paths:
+            if path.endswith('/'):
+                statuses[os.path.normpath(path)] = 'none'
 
         # Paths with status
-        output = self._run(['status', '--porcelain', '-z', '--ignored']).rstrip('\x00')
-        if output:
-            skip = False
-            for line in output.split('\x00'):
-                if skip:
-                    skip = False
-                    continue
-                statuses[os.path.normpath(line[3:])] = self._status_translate(line[:2])
-                if line.startswith('R'):
-                    skip = True
+        lines = self._run(['status', '--porcelain', '-z', '--ignored']).split('\0')[:-1]
+        skip = False
+        for line in lines:
+            if skip:
+                skip = False
+                continue
+            statuses[os.path.normpath(line[3:])] = self._status_translate(line[:2])
+            if line.startswith('R'):
+                skip = True
 
         return statuses
 
diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py
index cf15e35e..21460975 100644
--- a/ranger/ext/vcs/hg.py
+++ b/ranger/ext/vcs/hg.py
@@ -36,7 +36,7 @@ class Hg(Vcs):
             args += ['--'] + filelist
 
         try:
-            output = self._run(args).rstrip('\n')
+            output = self._run(args)
         except VcsError:
             return None
         if not output:
@@ -56,13 +56,13 @@ class Hg(Vcs):
     def _remote_url(self):
         """Remote url"""
         try:
-            return self._run(['showconfig', 'paths.default']).rstrip('\n') or None
+            return self._run(['showconfig', 'paths.default']) or None
         except VcsError:
             return None
 
     def _status_translate(self, code):
         """Translate status code"""
-        for code_x, status in self._status_translations:  # pylint: disable=invalid-name
+        for code_x, status in self._status_translations:
             if code in code_x:
                 return status
         return 'unknown'
@@ -80,7 +80,7 @@ class Hg(Vcs):
         if filelist:
             args += filelist
         else:
-            args += self.rootvcs.status_subpaths.keys()
+            args += self.rootvcs.status_subpaths.keys()  # pylint: disable=no-member
         self._run(args, catchout=False)
 
     # Data interface
@@ -117,7 +117,7 @@ class Hg(Vcs):
         return 'unknown'
 
     def data_branch(self):
-        return self._run(['branch']).rstrip('\n') or None
+        return self._run(['branch']) or None
 
     def data_info(self, rev=None):
         if rev is None:
diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py
index 1813f857..1f09cd52 100644
--- a/ranger/ext/vcs/svn.py
+++ b/ranger/ext/vcs/svn.py
@@ -36,7 +36,7 @@ class SVN(Vcs):
             args += ['--'] + filelist
 
         try:
-            output = self._run(args).rstrip('\n')
+            output = self._run(args)
         except VcsError:
             return None
         if not output:
@@ -66,7 +66,7 @@ class SVN(Vcs):
     def _remote_url(self):
         """Remote url"""
         try:
-            output = self._run(['info', '--xml']).rstrip('\n')
+            output = self._run(['info', '--xml'])
         except VcsError:
             return None
         if not output:
@@ -86,7 +86,7 @@ class SVN(Vcs):
         if filelist:
             args += filelist
         else:
-            args += self.rootvcs.status_subpaths.keys()
+            args += self.rootvcs.status_subpaths.keys()  # pylint: disable=no-member
         self._run(args, catchout=False)
 
     # Data Interface
@@ -95,10 +95,10 @@ class SVN(Vcs):
         statuses = set()
 
         # Paths with status
-        output = self._run(['status']).rstrip('\n')
-        if not output:
+        lines = self._run(['status']).split('\n')
+        if not lines:
             return 'sync'
-        for line in output.split('\n'):
+        for line in lines:
             code = line[0]
             if code == ' ':
                 continue
@@ -113,13 +113,12 @@ class SVN(Vcs):
         statuses = {}
 
         # Paths with status
-        output = self._run(['status']).rstrip('\n')
-        if output:
-            for line in output.split('\n'):
-                code, path = line[0], line[8:]
-                if code == ' ':
-                    continue
-                statuses[os.path.normpath(path)] = self._status_translate(code)
+        lines = self._run(['status']).split('\n')
+        for line in lines:
+            code, path = line[0], line[8:]
+            if code == ' ':
+                continue
+            statuses[os.path.normpath(path)] = self._status_translate(code)
 
         return statuses
 
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 487c9405..fa00777a 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -7,6 +7,9 @@ import os
 import subprocess
 import threading
 import time
+from logging import getLogger
+
+from ranger.ext import spawn
 
 # Python2 compatibility
 try:
@@ -19,6 +22,9 @@ except NameError:
     FileNotFoundError = OSError  # pylint: disable=redefined-builtin
 
 
+LOG = getLogger(__name__)
+
+
 class VcsError(Exception):
     """VCS exception"""
     pass
@@ -109,21 +115,24 @@ class Vcs(object):  # pylint: disable=too-many-instance-attributes
 
     # Generic
 
-    def _run(self, args, path=None, catchout=True, retbytes=False):
+    def _run(self, args, path=None,  # pylint: disable=too-many-arguments
+             catchout=True, retbytes=False, rstrip_newline=True):
         """Run a command"""
         cmd = [self.repotype] + args
         if path is None:
             path = self.path
 
-        with open(os.devnull, 'w') as devnull:
-            try:
-                if catchout:
-                    output = subprocess.check_output(cmd, cwd=path, stderr=devnull)
-                    return output if retbytes else output.decode('UTF-8')
-                else:
-                    subprocess.check_call(cmd, cwd=path, stdout=devnull, stderr=devnull)
-            except (subprocess.CalledProcessError, FileNotFoundError):
-                raise VcsError('{0:s}: {1:s}'.format(str(cmd), path))
+        try:
+            if catchout:
+                output = spawn.check_output(cmd, cwd=path, decode=not retbytes)
+                if not retbytes and rstrip_newline and output.endswith('\n'):
+                    return output[:-1]
+                return output
+            else:
+                with open(os.devnull, mode='w') as fd_devnull:
+                    subprocess.check_call(cmd, cwd=path, stdout=fd_devnull, stderr=fd_devnull)
+        except (subprocess.CalledProcessError, FileNotFoundError):
+            raise VcsError('{0:s}: {1:s}'.format(str(cmd), path))
 
     def _get_repotype(self, path):
         """Get type for path"""
@@ -227,7 +236,9 @@ class VcsRoot(Vcs):  # pylint: disable=abstract-method
             self.branch = self.data_branch()
             self.obj.vcsremotestatus = self.data_status_remote()
             self.obj.vcsstatus = self.data_status_root()
-        except VcsError:
+        except VcsError as error:
+            LOG.exception(error)
+            self.obj.fm.notify('VCS Exception: View log for more info', bad=True)
             return False
         self.rootinit = True
         return True
@@ -240,7 +251,9 @@ class VcsRoot(Vcs):  # pylint: disable=abstract-method
             self.status_subpaths = self.data_status_subpaths()
             self.obj.vcsremotestatus = self.data_status_remote()
             self.obj.vcsstatus = self._status_root()
-        except VcsError:
+        except VcsError as error:
+            LOG.exception(error)
+            self.obj.fm.notify('VCS Exception: View log for more info', bad=True)
             return False
         self.rootinit = True
         self.updatetime = time.time()
@@ -453,15 +466,19 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
             self.paused.clear()
             self.awoken.clear()
 
-            self._queue_process()
-
-            if self.redraw:
-                self.redraw = False
-                for column in self.ui.browser.columns:
-                    if column.target and column.target.is_directory:
-                        column.need_redraw = True
-                self.ui.status.need_redraw = True
-                self.ui.redraw()
+            try:
+                self._queue_process()
+
+                if self.redraw:
+                    self.redraw = False
+                    for column in self.ui.browser.columns:
+                        if column.target and column.target.is_directory:
+                            column.need_redraw = True
+                    self.ui.status.need_redraw = True
+                    self.ui.redraw()
+            except Exception as error:  # pylint: disable=broad-except
+                LOG.exception(error)
+                self.ui.fm.notify('VCS Exception: View log for more info', bad=True)
 
     def pause(self):
         """Pause thread"""
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py
index e761c379..091406e9 100644
--- a/ranger/gui/ansi.py
+++ b/ranger/gui/ansi.py
@@ -8,7 +8,7 @@ from ranger.gui import color
 import re
 
 ansi_re = re.compile('(\x1b' + r'\[\d*(?:;\d+)*?[a-zA-Z])')
-codesplit_re = re.compile('38;5;(\d+);|48;5;(\d+);|(\d*);')
+codesplit_re = re.compile(r'38;5;(\d+);|48;5;(\d+);|(\d*);')
 reset = '\x1b[0m'
 
 
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
index 7b5aa954..62eb5300 100644
--- a/ranger/gui/displayable.py
+++ b/ranger/gui/displayable.py
@@ -211,6 +211,7 @@ class DisplayableContainer(Displayable):
     New methods:
 
     add_child(object) -- add the object to the container.
+    replace_child(old_obj, new_obj) -- replaces old object with new object.
     remove_child(object) -- remove the object from the container.
 
     New attributes:
@@ -290,6 +291,11 @@ class DisplayableContainer(Displayable):
         self.container.append(obj)
         obj.parent = self
 
+    def replace_child(self, old_obj, new_obj):
+        """Replace the old object with the new instance in the container."""
+        self.container[self.container.index(old_obj)] = new_obj
+        new_obj.parent = self
+
     def remove_child(self, obj):
         """Remove the object from the container."""
         try:
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 4c302e00..f10bc0f2 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -437,18 +437,18 @@ class UI(DisplayableContainer):
         if value in self.ALLOWED_VIEWMODES:
             if self._viewmode != value:
                 self._viewmode = value
-                resize = False
+                new_browser = self._viewmode_to_class(value)(self.win)
+
                 if hasattr(self, 'browser'):
                     old_size = self.browser.y, self.browser.x, self.browser.hei, self.browser.wid
-                    self.remove_child(self.browser)
+                    self.replace_child(self.browser, new_browser)
                     self.browser.destroy()
-                    resize = True
+                    new_browser.resize(*old_size)
+                else:
+                    self.add_child(new_browser)
 
-                self.browser = self._viewmode_to_class(value)(self.win)
+                self.browser = new_browser
                 self.redraw_window()
-                self.add_child(self.browser)
-                if resize:
-                    self.browser.resize(*old_size)
         else:
             raise ValueError("Attempting to set invalid viewmode `%s`, should "
                     "be one of `%s`." % (value, "`, `".join(self.ALLOWED_VIEWMODES)))
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index 58b791cd..0c81079b 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -231,7 +231,7 @@ class Pager(Widget):
                     line = ansi.char_slice(line, startx, self.wid) + ansi.reset
                 else:
                     line = line[startx:self.wid + startx]
-                yield line.rstrip()
+                yield line.rstrip().replace('\r\n', '\n')
             except IndexError:
                 raise StopIteration
             i += 1
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 4eb06692..0ac62d86 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -258,7 +258,7 @@ class StatusBar(Widget):
             right.add("', ", "space")
 
         if target.marked_items:
-            if len(target.marked_items) == len(target.files):
+            if len(target.marked_items) == target.size:
                 right.add(human_readable(target.disk_usage, separator=''))
             else:
                 sumsize = sum(f.size for f in target.marked_items if not
@@ -290,7 +290,7 @@ class StatusBar(Widget):
             elif pos >= max_pos:
                 right.add('Bot', base, 'bot')
             else:
-                right.add('{:0.0%}'.format(float(pos) / max_pos),
+                right.add('{0:0.0%}'.format(float(pos) / max_pos),
                         base, 'percentage')
         else:
             right.add('0/0  All', base, 'all')