summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/ranger.142
-rw-r--r--doc/ranger.pod16
-rw-r--r--examples/rc_emacs.conf8
-rw-r--r--ranger/config/rc.conf8
-rw-r--r--ranger/config/rifle.conf5
-rw-r--r--ranger/container/settings.py2
-rw-r--r--ranger/core/fm.py19
-rw-r--r--ranger/core/linemode.py5
-rw-r--r--ranger/core/main.py11
-rwxr-xr-xranger/data/scope.sh6
-rw-r--r--ranger/ext/img_display.py78
-rw-r--r--ranger/ext/spawn.py49
-rw-r--r--ranger/ext/vcs/vcs.py35
-rw-r--r--ranger/gui/widgets/pager.py2
-rw-r--r--ranger/gui/widgets/statusbar.py2
15 files changed, 240 insertions, 48 deletions
diff --git a/doc/ranger.1 b/doc/ranger.1
index 184fa7e5..26a78a49 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -46,7 +46,7 @@
 .ie \n(.g .ds Aq \(aq
 .el       .ds Aq '
 .\"
-.\" If the F register is >0, we'll generate index entries on stderr for
+.\" If the F register is turned on, 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,16 +54,20 @@
 .\" Avoid warning from groff about undefined register 'F'.
 .de IX
 ..
-.if !\nF .nr F 0
-.if \nF>0 \{\
-.    de IX
-.    tm Index:\\$1\t\\n%\t"\\$2"
+.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==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.
@@ -129,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.7.2" "09/03/2016" "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
@@ -283,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
diff --git a/doc/ranger.pod b/doc/ranger.pod
index e8252dcb..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
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/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/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/fm.py b/ranger/core/fm.py
index 523239c1..a08de2e1 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -15,6 +15,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
@@ -49,7 +50,7 @@ class FM(Actions, SignalDispatcher):
             self.ui = ui
         self.start_paths = paths
         self.directories = dict()
-        self.log = deque(maxlen=20)
+        self.log = deque(maxlen=1000)
         self.bookmarks = bookmarks
         self.current_tab = 1
         self.tabs = {}
@@ -70,9 +71,9 @@ class FM(Actions, SignalDispatcher):
         self.hostname = socket.gethostname()
         self.home_path = os.path.expanduser('~')
 
-        self.log.append('ranger {0} started! Process ID is {1}.'
+        self.log.appendleft('ranger {0} started! Process ID is {1}.'
                 .format(__version__, os.getpid()))
-        self.log.append('Running on Python ' + sys.version.replace('\n', ''))
+        self.log.appendleft('Running on Python ' + sys.version.replace('\n', ''))
 
         mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
         mimetypes.knownfiles.append(self.relpath('data/mime.types'))
@@ -97,7 +98,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'))
@@ -203,6 +210,10 @@ class FM(Actions, SignalDispatcher):
             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..a8ce4e6d 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.spawn 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(["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..93c71e81 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -112,7 +112,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.")
+            fm.log.appendleft("Running as root, disabling the file previews.")
         if not arg.debug:
             from ranger.ext import curses_interrupt_handler
             curses_interrupt_handler.install_interrupt_handler()
@@ -313,12 +313,11 @@ 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)
-                except Exception as e:
-                    fm.log.append("Error in plugin '%s'" % plugin)
+                    fm.log.appendleft("Loaded plugin '%s'." % plugin)
+                except Exception:
                     import traceback
-                    for line in traceback.format_exception_only(type(e), e):
-                        fm.log.append(line)
+                    fm.log.extendleft(reversed(traceback.format_exc().splitlines()))
+                    fm.notify("Error in plugin '%s'" % plugin, bad=True)
             ranger.fm = None
 
         # COMPAT: Load the outdated options.py
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 44fcec2b..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" - && \
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/spawn.py b/ranger/ext/spawn.py
index 7c5c921c..393d48d9 100644
--- a/ranger/ext/spawn.py
+++ b/ranger/ext/spawn.py
@@ -1,18 +1,57 @@
 # 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 subprocess import Popen, PIPE, CalledProcessError
 ENCODING = 'utf-8'
 
 
-def spawn(*args):
-    """Runs a program, waits for its termination and returns its stdout"""
+def spawn(*args, **kwargs):
+    """Runs a program, waits for its termination and returns its stdout
+
+    This function is similar to python 2.7's subprocess.check_output,
+    but is favored due to python 2.6 compatibility.
+
+    The arguments may be:
+
+        spawn(string)
+        spawn(command, arg1, arg2...)
+        spawn([command, arg1, arg2])
+
+    The string will be run through a shell, otherwise the command is executed
+    directly.
+
+    The keyword argument "decode" determines if the output shall be decoded
+    with the encoding '%s'.
+
+    Further keyword arguments are passed to Popen.
+    """ % (ENCODING, )
+
     if len(args) == 1:
         popen_arguments = args[0]
         shell = isinstance(popen_arguments, str)
     else:
         popen_arguments = args
         shell = False
-    process = Popen(popen_arguments, stdout=PIPE, shell=shell)
+
+    if 'decode' in kwargs:
+        do_decode = kwargs['decode']
+        del kwargs['decode']
+    else:
+        do_decode = True
+    if 'stdout' not in kwargs:
+        kwargs['stdout'] = PIPE
+    if 'shell' not in kwargs:
+        kwargs['shell'] = shell
+
+    process = Popen(popen_arguments, **kwargs)
     stdout, stderr = process.communicate()
-    return stdout.decode(ENCODING)
+    return_value = process.poll()
+    if return_value:
+        error = CalledProcessError(return_value, popen_arguments[0])
+        error.output = stdout
+        raise error
+
+    if do_decode:
+        return stdout.decode(ENCODING)
+    else:
+        return stdout
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 9c7be653..cfdc1e3b 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -7,6 +7,7 @@ import os
 import subprocess
 import threading
 import time
+from ranger.ext.spawn import spawn
 
 # Python2 compatibility
 try:
@@ -119,14 +120,13 @@ class Vcs(object):  # pylint: disable=too-many-instance-attributes
         with open(os.devnull, 'w') as devnull:
             try:
                 if catchout:
-                    output = subprocess.check_output(cmd, cwd=path, stderr=devnull)
-                    if retbytes:
-                        return output
-                    else:
-                        output = output.decode('UTF-8')
+                    output = spawn(cmd, cwd=path, stderr=devnull,
+                            decode=not retbytes)
+                    if (not retbytes and rstrip_newline and
+                            output.endswith('\n')):
                         if rstrip_newline and output.endswith('\n'):
                             return output[:-1]
-                        return output
+                    return output
                 else:
                     subprocess.check_call(cmd, cwd=path, stdout=devnull, stderr=devnull)
             except (subprocess.CalledProcessError, FileNotFoundError):
@@ -460,15 +460,20 @@ 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:  # pylint: disable=broad-except
+                import traceback
+                self.ui.fm.log.extendleft(reversed(traceback.format_exc().splitlines()))
+                self.ui.fm.notify('VCS Exception', bad=True)
 
     def pause(self):
         """Pause thread"""
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 0828c372..0ac62d86 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -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')