summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG275
-rw-r--r--CHANGELOG.md14
-rw-r--r--ranger/__init__.py2
-rw-r--r--ranger/container/file.py2
-rw-r--r--ranger/container/fsobject.py2
-rw-r--r--ranger/container/settings.py1
-rw-r--r--ranger/core/actions.py30
-rw-r--r--ranger/core/loader.py20
-rwxr-xr-xranger/data/scope.sh30
-rw-r--r--ranger/ext/shutil_generatorized.py33
-rw-r--r--ranger/ext/vcs/svn.py274
-rw-r--r--ranger/ext/vcs/vcs.py3
-rw-r--r--ranger/gui/widgets/browsercolumn.py18
-rw-r--r--ranger/gui/widgets/browserview.py7
-rwxr-xr-xsetup.py11
16 files changed, 386 insertions, 337 deletions
diff --git a/.gitignore b/.gitignore
index 5357ead2..09f1a7e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *~
+*.egg-info
 *.pyc
 *.pyo
 stuff/*
diff --git a/CHANGELOG b/CHANGELOG
deleted file mode 100644
index 207e311f..00000000
--- a/CHANGELOG
+++ /dev/null
@@ -1,275 +0,0 @@
-This log documents changes between stable versions.
-
-2015-10-04: version 1.7.2
-* Fixed file name arguments passed to "sxiv" and "feh" when using ":flat"
-* Fixed removal of empty directories when using ":rename"
-* Fixed free disk space display on Mac OS X
-* Fixed `examples/vim_file_chooser` to work with gvim too
-* Fixed some other rare crashes and bugs
-* Fixed downward mouse wheel scrolling
-* Fixed warning about regex splits being drawin in title bar since python3.5
-* Really fixed "S" key binding not working when SHELL=fish
-* Improved doc/cheatsheet.svg
-* Added some entries to rifle.conf
-* Added key bindings pO and pP which work like po and pp but queue the operation
-  in a first-in-first-out order.
-
-2015-05-04: version 1.7.1
-* Added doc/cheatsheet.svg
-* Added examples/rc_emacs.conf, a config file adding emacs-like key bindings
-* Added "env" keyword in rifle.conf
-* Fixed ":bulkrename" command in python3
-* Fixed "S" key binding not working when SHELL=fish
-
-2015-04-13: version 1.7.0
-* The default editor is now "vim" instead of "nano"
-* Added automatic updates of tags when a file is renamed from within ranger
-* Added "preview_images_method" which can be set to "iterm2" to use native
-  iTerm2 image previews
-* Added ":rename_append" command to rename files without the file extension
-* Added ":linemode" command to change the way the files are displayed
-  Try this out by pressing M followed by one of the suggested keys.
-  New linemodes can be added with ranger.api.register_linemode().
-* Added ":filter_inode_type" command to only show directories, files or links
-* Added ":meta" command for managing custom file metadata
-* Added ":flat" command for displaying subdirectories
-* Added "solarized" colorscheme
-* Added generic ability to use scope.sh for image previews
-* Added video previews in scope.sh
-* Added option "sort_unicode" to sort according to unicode, not ASCII
-* ":mkdir" can now create multiple directory levels (like `mkdir -p`)
-* ":help" (key binding "?") is now interactive
-* ":find" (key binding "/") is now case insensitive by default
-* "ranger --copy-config=all" now copies a short sample commands.py rather than
-  the full one, so that you can update ranger without having broken commands.
-  The full commands.py is still copied to ~/.config/ranger/commands_full.py.
-* Fixed broken copying of symlinks
-
-2013-05-24: Version 1.6.1
-* Added support for version control systems, see:
-  http://lists.nongnu.org/archive/html/ranger-users/2013-03/msg00007.html
-* Added :scout command as a unified backend to :find, :search, etc
-* Added "open_all_images" setting to remove the need for external scripts
-  to handle opening of all images in a directory at once.
-* Now previewing with "i" uses the whole available width.
-
-2013-02-22: Version 1.6.0
-* Overhauled all config files.  Please update them or use the --clean switch
-* Added "examples/" directory to source code which contains sample programs or
-  plugins that can be used together with ranger
-* Added progress bars to copying, moving and directory loading processes
-* Added feature to draw images inside the console using w3mimgdisplay (you need
-  to add "set preview_images true" in rc.conf)
-* Added a plugin system like in the program "anki", i.e. place any python file
-  into ~/.config/ranger/plugins/ and it will be imported by ranger
-* Added a separate file launcher named "rifle" that is configured through
-  rifle.conf and is installed as a standalone program.
-  Using "ranger [filename]" from the shell for opening files is deprecated now,
-  please use "rifle [filename]" instead.
-* Added "uq" keybinding to undo closed tabs
-* Added :setlocal command to change settings for specific directories only
-* Added :travel command to move more quickly to your destination
-* Added 256 color support for scope.sh
-* Added a real yes/no prompt for :delete command
-* Added settings: confirm_on_delete, draw_progress_bar_in_status_bar,
-  preview_images, status_bar_on_top, update_tmux_title
-* Added commands: :mark_tag, :unmark_tag
-* Added BSD-friendly setsid implementation
-* Added as-you-type filtering for :filter command
-* Replaced "options.py" file by :set commands in rc.conf
-* Replaced "apps.py" file with rifle.conf
-* Improved "r" key to interface with rifle
-* Rewritten "scope.sh" in POSIX shell
-* Changed copying/moving code to work without GNU coreutils
-* Changed key to untag files from "T" to "ut"
-* Changed the flag "d" (for detached) to "f" (for fork) in program launcher
-* Changed appearance of keybinding-hints and bookmarks
-* Changed tabs with 4 spaces in the source code (see PEP 8)
-* Removed ranger.core.environment class
-* Removed settings: colorscheme_overlay, draw_bookmark_borders, init_function,
-  load_default_rc
-* Fixed zombie process apocalypse
-* Fixed draw_borders=true in combination with padding_right=false
-
-2012-08-10: Version 1.5.5
-* Ensure that detached programs continue to run when ranger is killed
-
-2012-05-03: Version 1.5.4
-* Added exiftool to scope.sh by default
-* Fixed a crash when entering a directory with a unicode name
-* Speedup in ranger.ext.get_executables
-
-2012-03-05: Version 1.5.3
-* Added --selectfile option that selects a certain file on startup
-* Added --list-tagged-files option
-* Added --cmd option to run commands on startup
-* Added --profile option for additional debug information on exit
-* Added a visual mode (activate with "V", deactivate with Esc)
-* Added a reversed visual mode (activate with "uV")
-* Added $RANGER_LEVEL environment variable which ranger sets to "1" or higher
-  so programs can know that they were spawned from ranger
-* Added run flag "r" for running with root privileges (needs sudo)
-* Added run flag "t" for running in a new terminal (as specified in $TERMCMD)
-* Added :relink command to change destinations of symlinks
-* Added "dc" binding for getting the cumulative size of a directory
-* Added "autoupdate_cumulative_size" option
-* Added "pht" binding to Paste Hardlinked subTrees (like cp -l)
-* Improved sorting speed of signals (noticable when caching many directories)
-* Improved drawing speed
-* Fixed unexpected behavior when displaying nonprintable characters
-* Fixed :bulkrename to work with files starting with a minus sign
-* Fixed RangerChooser example in man page
-* Fixed crash when opening images with sxiv/feh by running "ranger <image>"
-
-2011-10-23: Version 1.5.2
-* Fixed graphical bug that appears in certian cases when drawing
-  characters at the right edge.
-
-2011-10-23: Version 1.5.1
-* Added fm.select_file(path)
-* Added --choosefiles option (like --choosefile, but chooses multiple files)
-* Fixed --list-unused-keys
-* Fixed Zombie processes
-* Fixed handling of filenames with undecodable bytes (unicode surrogates)
-* Fixed crashes due to incomplete loading of directories
-* Fixed tab completion of the command "shell"
-* Fixed "ot" and "oT" keys in rc.conf
-* Fixed parsing of chained commands (like in the binding "om")
-
-2011-10-11: Version 1.5.0
-* Full python3.2 compatibility
-* Added new configuration file "rc.conf" which contains a list
-  of commands that are executed on startup - mainly used for keybindings
-* Added --list-unused-keys
-* Added new program handlers to apps.py
-* Added pop-up window for keychains and bookmarks
-* Added load_default_rc option
-* Fixed all known unicode issues
-* Fixed crash when $TERM is unknown to the system
-* Fixed scrolling in colored preview
-* Changed the default column_ratios to 1/3/4 and sorting method to "natural"
-* Changed :rename so it doesn't overwrite existing files
-* Internal actions are now accessible as commands
-* Replaced unittests by doctests
-* Replaced integrated help with an extended man page and dynamic lists
-  of keybindings, commands and settings.
-* Removed "keys.py" configuration file in favor of "rc.conf"
-* Removed "texas" colorscheme
-* apps.py: Now able to define programs that only run with Xorg
-* commands.py: Using parse(self.line) to parse the line is unnecessary now.
-  parse(self.line).rest(n) is now written as self.rest(n).
-  However, parse(self.line).chunk(n) has been renamed to self.arg(n).
-* commands.py: parse(self.line) + X is now self.firstpart + X
-* commands.py: New special attribute "resolve_macros" which decides whether
-  strings like %f should be expanded to the name of the current file, etc.
-* commands.py: New special attribute "escape_macros_for_shell" to toggle
-  whether or not macros should be escaped, so you can use them in other
-  commands than :shell, for example :edit %f
-* Countless small fixes and improvements
-
-2011-10-02: Version 1.4.4
-* Added keys for chmod (like +ow for "chmod o+w", etc)
-* Added "c" flag for running files
-* Added various key bindings
-* Added wavpack and webm types to mime.types
-* Added option "display_tags_in_all_columns"
-* Added command.cancel method which is called when pressing ESC in console
-* Added sorting and cycling by ctime and atime
-* Added custom tags (press "x)
-* Added bittorrent preview
-* Fixed blocking when using interactive scripts in scope.sh
-* Fixed issues with ALT key
-* Fixed pager crash when trying to read non-readable file
-* Forbid piping things into ranger
-* Improved hints
-
-2011-04-05: Version 1.4.3
-* Fixed mimetype checking when invoking ranger with a filename
-* Fixed loss of bookmarks when disk is full
-* Minor improvements
-
-2011-03-05: Version 1.4.2
-* Added --choosefile and --choosedir flag
-* Added use of bookmarks in tab completion of the :cd command
-* Fixed bug with detached programs and python 3.2
-
-2011-01-04: Version 1.4.1
-* Fixed crash when preview failed under some circumstances
-* Fixed graphical bug when pressing i
-
-2010-12-22: Version 1.4.0
-* Added option to use any external scripts for previews (see scope.sh)
-* Added key: zv to toggle the use of the external script
-* Added indicator for the used filter (type "zf")
-* Added option padding_right to remove whitespace if theres no preview
-* Added command :search_inc for incremental search
-* Added commands :save_copy_buffer and :load_copy_buffer to share
-  the copied files between ranger instances
-* Added mimeopen as a fallback if no useful application can be found
-* Added natural sort, sorts 1foo before 10foo. (type "on")
-* Added keys: yp, yd and yb to copy path, dirname or basename to seleciton
-* Let open_with use the selection, not just one file
-* Run files with right mouse click
-* Implemented copying via coreutils rather than internal python code
-* Improved handling of unicode
-* Some restructuration of the source code
-
-2010-12-13: Version 1.2.3
-* Enable binding to alt-keys
-* Fixed memory leak in garbage collecting of old, unused directory objects
-* Fixed python3 incompatibilities
-* Fixed problems with identifying changes of files
-* Fixed lazy lookup of some FSObject attributes
-
-2010-10-10: Version 1.2.2
-* Prevent currently used directories from being garbage collected
-* Disable mouse buttons when console is open
-* Fixed :cd command: Without arguments, cd's into $HOME
-* Fixed bug which prevented pydoc to work on some config files
-* Fixed some bugs in "snow" and "jungle" colorschemes
-* Several other clean-ups and fixes
-
-2010-09-16: Version 1.2.1
-* Fixed yy/pp bug when yanking multiple directories
-
-2010-09-13: Version 1.2.0
-* !!! Changed the default configuration directory to ~/.config/ranger !!!
-* Removed "Console Modes", each old mode is now a simple command
-* Disabled file previews by default if ranger is used by root
-* Allow to jump to specific help sections by typing two numbers, e.g. 13?
-* Added keys: da, dr, ya, yr for adding and removing files from copy buffer
-* Added keys: gl and gL to resolve links, see 11?
-* Added key: pL to create a relative symlink
-* Added %<LETTER> and %<N><LETTER> macros for the console, see 33?
-* Fixed ansi codes for colors in the pager
-* Use the file ~/.mime.types for mime type detection
-* Several clean-ups and fixes
-
-2010-07-17: Version 1.1.2
-* Fix crash when using scrollwheel to scroll down in some cases
-* The command "ranger dir1 dir2 ..." opens multiple directories in tabs
-* Removed pydoc html documentation by default, re-create it with "make doc"
-* Minor fixes
-
-2010-06-18: Version 1.1.1
-* New install script, "setup.py"
-* New flag for running programs: "w" (waits for enter press)
-* Minor fixes
-
-2010-06-09: Version 1.1.0
-* Added a man page
-* Tab support
-* Improved directory loading performance
-* Commands are definable in ~/.ranger/commands.py
-* Case insensitive sorting (type zs)
-* Better UTF support
-* Possibility to turn off previews (zp and zP)
-* Changing options with :set (e.g. :set column_ratios=1,2,3,4)
-* Ask for confirmation when using :delete
-* New invocation flag: --fail-unless-cd
-* New hotkeys, commands, options.
-* New syntax for ~/.ranger/keys.py
-* Several user contributions
-* And tons of general improvements
-
-NOTE: The syntax for configuration is still subject to change.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd7bd32f..bff84808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
 This log documents changes between stable versions.
 
+# 2015-10-04: version 1.7.2
+* Fixed file name arguments passed to `sxiv` and `feh` when using `:flat`
+* Fixed removal of empty directories when using `:rename`
+* Fixed free disk space display on Mac OS X
+* Fixed `examples/vim_file_chooser` to work with gvim too
+* Fixed some other rare crashes and bugs
+* Fixed downward mouse wheel scrolling
+* Fixed warning about regex splits being drawin in title bar since python3.5
+* Really fixed `S` key binding not working when SHELL=fish
+* Improved `doc/cheatsheet.svg`
+* Added some entries to rifle.conf
+* Added key bindings `pO` and `pP` which work like `po` and `pp` but queue the
+  operation in a first-in-first-out order.
+
 # 2015-05-04: version 1.7.1
 * Added `doc/cheatsheet.svg`
 * Added `examples/rc_emacs.conf`, a config file adding emacs-like key bindings
diff --git a/ranger/__init__.py b/ranger/__init__.py
index 2d3f04d6..20c8ab35 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -28,7 +28,7 @@ DEFAULT_PAGER = 'less'
 LOGFILE = tempfile.gettempdir()+'/ranger_errorlog'
 CACHEDIR = os.path.expanduser("~/.cache/ranger")
 USAGE = '%prog [options] [path]'
-VERSION = 'ranger-stable %s\n\nPython %s' % (__version__, sys.version)
+VERSION = 'ranger-master %s\n\nPython %s' % (__version__, sys.version)
 
 # If the environment variable XDG_CONFIG_HOME is non-empty, CONFDIR is ignored
 # and the configuration directory will be $XDG_CONFIG_HOME/ranger instead.
diff --git a/ranger/container/file.py b/ranger/container/file.py
index 6be60f85..a3db63a5 100644
--- a/ranger/container/file.py
+++ b/ranger/container/file.py
@@ -76,8 +76,6 @@ class File(FileSystemObject):
         if self.fm.settings.preview_script and \
                 self.fm.settings.use_preview_script:
             return True
-        if self.image and self.fm.settings.preview_images:
-            return True
         if self.container:
             return False
         if PREVIEW_WHITELIST.search(self.basename):
diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py
index b63781de..aa848b7a 100644
--- a/ranger/container/fsobject.py
+++ b/ranger/container/fsobject.py
@@ -281,6 +281,8 @@ class FileSystemObject(FileManagerAware, SettingsAware):
                 backend_state = self.settings.vcs_backend_hg
             elif self.vcs.vcsname == 'bzr':
                 backend_state = self.settings.vcs_backend_bzr
+            elif self.vcs.vcsname == 'svn':
+                backend_state = self.settings.vcs_backend_svn
             else:
                 backend_state = 'disabled'
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 1d91495f..db8ca45c 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -60,6 +60,7 @@ ALLOWED_SETTINGS = {
     'vcs_backend_bzr': str,
     'vcs_backend_git': str,
     'vcs_backend_hg': str,
+    'vcs_backend_svn': str,
     'xterm_alt_key': bool,
 }
 
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index e3dba313..8167dbcf 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -799,14 +799,11 @@ class Actions(FileManagerAware, SettingsAware):
             return
 
         pager = self.ui.open_pager()
-        if self.settings.preview_images and self.thisfile.image:
-            pager.set_image(self.thisfile.realpath)
+        f = self.thisfile.get_preview_source(pager.wid, pager.hei)
+        if self.thisfile.is_image_preview():
+            pager.set_image(f)
         else:
-            f = self.thisfile.get_preview_source(pager.wid, pager.hei)
-            if self.thisfile.is_image_preview():
-                pager.set_image(f)
-            else:
-                pager.set_source(f)
+            pager.set_source(f)
 
     # --------------------------
     # -- Previews
@@ -835,10 +832,6 @@ class Actions(FileManagerAware, SettingsAware):
         if not path or not os.path.exists(path):
             return None
 
-        if self.settings.preview_images and file.image:
-            pager.set_image(path)
-            return None
-
         if self.settings.preview_script and self.settings.use_preview_script:
             # self.previews is a 2 dimensional dict:
             # self.previews['/tmp/foo.jpg'][(80, 24)] = "the content..."
@@ -874,6 +867,13 @@ class Actions(FileManagerAware, SettingsAware):
 
                 data['loading'] = True
 
+                if 'directimagepreview' in data:
+                    data['foundpreview'] = True
+                    data['imagepreview'] = True
+                    pager.set_image(path)
+                    data['loading'] = False
+                    return path
+
                 cacheimg = os.path.join(ranger.CACHEDIR, self.sha1_encode(path))
                 if (os.path.isfile(cacheimg) and os.path.getmtime(cacheimg) > os.path.getmtime(path)):
                     data['foundpreview'] = True
@@ -883,7 +883,8 @@ class Actions(FileManagerAware, SettingsAware):
                     return cacheimg
 
                 loadable = CommandLoader(args=[self.settings.preview_script,
-                    path, str(width), str(height), cacheimg], read=True,
+                    path, str(width), str(height), cacheimg,
+                    str(self.settings.preview_images)], read=True,
                     silent=True, descr="Getting preview of %s" % path)
                 def on_after(signal):
                     exit = signal.process.poll()
@@ -899,6 +900,8 @@ class Actions(FileManagerAware, SettingsAware):
                         data[(-1, -1)] = content
                     elif exit == 6:
                         data['imagepreview'] = True
+                    elif exit == 7:
+                        data['directimagepreview'] = True
                     elif exit == 1:
                         data[(-1, -1)] = None
                         data['foundpreview'] = False
@@ -922,6 +925,9 @@ class Actions(FileManagerAware, SettingsAware):
                         if 'imagepreview' in data:
                             pager.set_image(cacheimg)
                             return cacheimg
+                        elif 'directimagepreview' in data:
+                            pager.set_image(path)
+                            return path
                         else:
                             pager.set_source(self.thisfile.get_preview_source(
                                 pager.wid, pager.hei))
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 1f9ec9cf..411b16ec 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -77,7 +77,7 @@ class CopyLoader(Loadable, FileManagerAware):
             # TODO: Don't calculate size when renaming (needs detection)
             bytes_per_tick = shutil_g.BLOCK_SIZE
             size = max(1, self._calculate_size(bytes_per_tick))
-            bar_tick = 100.0 / (float(size) / bytes_per_tick)
+            done = 0
             if self.do_cut:
                 self.original_copy_buffer.clear()
                 if len(self.copy_buffer) == 1:
@@ -92,11 +92,13 @@ class CopyLoader(Loadable, FileManagerAware):
                             self.fm.tags.tags[tf.replace(f.path, self.original_path \
                                     + '/' + f.basename)] = tag
                             self.fm.tags.dump()
-                    for _ in shutil_g.move(src=f.path,
+                    d = 0
+                    for d in shutil_g.move(src=f.path,
                             dst=self.original_path,
                             overwrite=self.overwrite):
-                        self.percent += bar_tick
+                        self.percent = float(done + d) / size * 100.
                         yield
+                    done += d
             else:
                 if len(self.copy_buffer) == 1:
                     self.description = "copying: " + self.one_file.path
@@ -104,18 +106,22 @@ class CopyLoader(Loadable, FileManagerAware):
                     self.description = "copying files from: " + self.one_file.dirname
                 for f in self.copy_buffer:
                     if os.path.isdir(f.path) and not os.path.islink(f.path):
-                        for _ in shutil_g.copytree(src=f.path,
+                        d = 0
+                        for d in shutil_g.copytree(src=f.path,
                                 dst=os.path.join(self.original_path, f.basename),
                                 symlinks=True,
                                 overwrite=self.overwrite):
-                            self.percent += bar_tick
+                            self.percent = float(done + d) / size * 100.
                             yield
+                        done += d
                     else:
-                        for _ in shutil_g.copy2(f.path, self.original_path,
+                        d = 0
+                        for d in shutil_g.copy2(f.path, self.original_path,
                                 symlinks=True,
                                 overwrite=self.overwrite):
-                            self.percent += bar_tick
+                            self.percent = float(done + d) / size * 100.
                             yield
+                        done += d
             cwd = self.fm.get_directory(self.original_path)
             cwd.load_content()
 
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 3611c961..19404019 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -17,12 +17,14 @@
 # 4    | fix height | success. Don't reload when height changes
 # 5    | fix both   | success. Don't ever reload
 # 6    | image      | success. display the image $cached points to as an image preview
+# 7    | image      | success. display the file directly as an image
 
 # Meaningful aliases for arguments:
-path="$1"    # Full path of the selected file
-width="$2"   # Width of the preview pane (number of fitting characters)
-height="$3"  # Height of the preview pane (number of fitting characters)
-cached="$4"  # Path that should be used to cache image previews
+path="$1"            # Full path of the selected file
+width="$2"           # Width of the preview pane (number of fitting characters)
+height="$3"          # Height of the preview pane (number of fitting characters)
+cached="$4"          # Path that should be used to cache image previews
+preview_images="$5"  # "True" if image previews are enabled, "False" otherwise.
 
 maxln=200    # Stop after $maxln lines.  Can be used like ls | head -n $maxln
 
@@ -44,6 +46,23 @@ trim() { head -n "$maxln"; }
 # wraps highlight to treat exit code 141 (killed by SIGPIPE) as success
 highlight() { command highlight "$@"; test $? = 0 -o $? = 141; }
 
+# Image previews, if enabled in ranger.
+if [ "$preview_images" = "True" ]; then
+    case "$mimetype" in
+        # Image previews for SVG files, disabled by default.
+        ###image/svg+xml)
+        ###   convert "$path" "$cached" && exit 6 || exit 1;;
+        # Image previews for image files. w3mimgdisplay will be called for all
+        # image files (unless overriden as above), but might fail for
+        # unsupported types.
+        image/*)
+            exit 7;;
+        # Image preview for video, disabled by default.:
+        ###video/*)
+        ###    ffmpegthumbnailer -i "$path" -o "$cached" -s 0 && exit 6 || exit 1;;
+    esac
+fi
+
 case "$extension" in
     # Archive extensions:
     7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
@@ -76,9 +95,6 @@ case "$mimetype" in
     # Ascii-previews of images:
     image/*)
         img2txt --gamma=0.6 --width="$width" "$path" && exit 4 || exit 1;;
-    # Image preview for videos, disabled by default:
-    # video/*)
-    #     ffmpegthumbnailer -i "$path" -o "$cached" -s 0 && exit 6 || exit 1;;
     # Display information about media files:
     video/* | audio/*)
         exiftool "$path" && exit 5
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
index b20d5cef..6b63f4c7 100644
--- a/ranger/ext/shutil_generatorized.py
+++ b/ranger/ext/shutil_generatorized.py
@@ -30,12 +30,14 @@ except NameError:
 
 def copyfileobj(fsrc, fdst, length=BLOCK_SIZE):
     """copy data from file-like object fsrc to file-like object fdst"""
+    done = 0
     while 1:
         buf = fsrc.read(length)
         if not buf:
             break
         fdst.write(buf)
-        yield
+        done += len(buf)
+        yield done
 
 def _samefile(src, dst):
     # Macintosh, Unix.
@@ -69,8 +71,8 @@ def copyfile(src, dst):
     try:
         fsrc = open(src, 'rb')
         fdst = open(dst, 'wb')
-        for _ in copyfileobj(fsrc, fdst):
-            yield
+        for done in copyfileobj(fsrc, fdst):
+            yield done
     finally:
         if fdst:
             fdst.close()
@@ -107,8 +109,8 @@ def copy2(src, dst, overwrite=False, symlinks=False):
             os.unlink(dst)
         os.symlink(linkto, dst)
     else:
-        for _ in copyfile(src, dst):
-            yield
+        for done in copyfile(src, dst):
+            yield done
         copystat(src, dst)
 
 def get_safe_path(dst):
@@ -165,6 +167,7 @@ def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
         if not overwrite:
             dst = get_safe_path(dst)
             os.makedirs(dst)
+    done = 0
     for name in names:
         if name in ignored_names:
             continue
@@ -178,14 +181,18 @@ def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
                 os.symlink(linkto, dstname)
                 copystat(srcname, dstname)
             elif os.path.isdir(srcname):
-                for _ in copytree(srcname, dstname, symlinks,
+                d = 0
+                for d in copytree(srcname, dstname, symlinks,
                         ignore, overwrite):
-                    yield
+                    yield done + d
+                done += d
             else:
                 # Will raise a SpecialFileError for unsupported file types
-                for _ in copy2(srcname, dstname,
+                d = 0
+                for d in copy2(srcname, dstname,
                         overwrite=overwrite, symlinks=symlinks):
-                    yield
+                    yield done + d
+                done += d
         # catch the Error from the recursive copytree so that we can
         # continue with other files
         except Error as err:
@@ -283,12 +290,12 @@ def move(src, dst, overwrite=False):
         if os.path.isdir(src):
             if _destinsrc(src, dst):
                 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
-            for _ in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
-                yield
+            for done in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
+                yield done
             rmtree(src)
         else:
-            for _ in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
-                yield
+            for done in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
+                yield done
             os.unlink(src)
 
 def _destinsrc(src, dst):
diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py
new file mode 100644
index 00000000..9bf8698c
--- /dev/null
+++ b/ranger/ext/vcs/svn.py
@@ -0,0 +1,274 @@
+# -*- coding: utf-8 -*-
+# This file is part of ranger, the console file manager.
+# License: GNU GPL version 3, see the file "AUTHORS" for details.
+# Author: Abdó Roig-Maranges <abdo.roig@gmail.com>, 2011-2012
+#         Ryan Burns <rdburns@gmail.com>, 2015
+#
+# R. Burns start with Abdó's Hg module and modified it for Subversion.
+#
+# vcs - a python module to handle various version control systems
+
+from __future__ import with_statement
+import os
+import re
+import shutil
+import logging
+from datetime import datetime
+from .vcs import Vcs, VcsError
+
+#logging.basicConfig(filename='rangersvn.log',level=logging.DEBUG,
+#                    filemode='w')
+
+class SVN(Vcs):
+    vcsname = 'svn'
+    HEAD = 'HEAD'
+    # Auxiliar stuff
+    #---------------------------
+
+    def _svn(self, path, args, silent=True, catchout=False, bytes=False):
+        return self._vcs(path, 'svn', args, silent=silent, catchout=catchout, bytes=bytes)
+
+
+    def _has_head(self):
+        """Checks whether repo has head"""
+        return True
+
+
+    def _sanitize_rev(self, rev):
+        if rev == None: return None
+        rev = rev.strip()
+        if len(rev) == 0: return None
+        if rev[-1] == '+': rev = rev[:-1]
+
+        try:
+            if int(rev) == 0: return None
+        except:
+            pass
+
+        return rev
+
+
+    def _log(self, refspec=None, maxres=None, filelist=None):
+        """ Retrieves log message and parses it.
+        """
+        args = ['log']
+
+        if refspec:  args = args + ['--limit', '1', '-r', refspec]
+        elif maxres: args = args + ['--limit', str(maxres)]
+
+        if filelist: args = args + filelist
+        logging.debug('Running svn log')
+        logging.debug(args)
+
+        raw = self._svn(self.path, args, catchout=True)
+        logging.debug(raw)
+        L = re.findall(r"""^[-]*\s*            # Dash line
+                       r([0-9]*)\s\|\s         # Revision          [0]
+                       (.*)\s\|\s              # Author            [1]
+                       (.*?)\s                 # Date              [2]
+                       (.*?)\s                 # Time              [3]
+                       .*?\|\s*?               # Dump rest of date string
+                       ([0-9])+\sline(?:s)?\s* # Number of line(s) [4]
+                       (.*)\s                  # Log Message       [5]
+                       [-]+\s*                 # Dash line
+                       $""", raw, re.MULTILINE | re.VERBOSE)
+
+        log = []
+        for t in L:
+            logging.debug(t)
+            dt = {}
+            dt['short'] = t[0].strip()
+            dt['revid'] = t[0].strip()
+            dt['author'] = t[1].strip()
+            dt['date'] = datetime.strptime(t[2]+'T'+t[3], "%Y-%m-%dT%H:%M:%S")
+            dt['summary'] = t[5].strip()
+            log.append(dt)
+            logging.debug(log)
+        return log
+
+
+    def _svn_file_status(self, st):
+        if len(st) != 1: raise VcsError("Wrong hg file status string: %s" % st)
+        if   st in "A":  return 'staged'
+        elif st in "D":  return 'deleted'
+        elif st in "I":  return 'ignored'
+        elif st in "?":  return 'untracked'
+        elif st in "C":  return 'conflict'
+        else:            return 'unknown'
+
+
+
+    # Repo creation
+    #---------------------------
+
+    def init(self):
+        """Initializes a repo in current path"""
+        self._svn(self.path, ['init'])
+        self.update()
+
+
+    def clone(self, src):
+        """Checks out SVN repo"""
+        name = os.path.basename(self.path)
+        path = os.path.dirname(self.path)
+        try:
+            os.rmdir(self.path)
+        except OSError:
+            raise VcsError("Can't clone to %s. It is not an empty directory" % self.path)
+
+        self._svn(path, ['co', src, name])
+        self.update()
+
+
+
+    # Action Interface
+    #---------------------------
+
+    def commit(self, message):
+        """Commits with a given message"""
+        self._svn(self.path, ['commit', '-m', message])
+
+
+    def add(self, filelist=None):
+        """Adds files to the index, preparing for commit"""
+        if filelist != None: self._svn(self.path, ['add'] + filelist)
+        else:                self._svn(self.path, ['add'])
+
+
+    def reset(self, filelist=None):
+        """Equivalent to svn revert"""
+        if filelist == None: filelist = self.get_status_allfiles().keys()
+        self._svn(self.path, ['revert'] + filelist)
+
+
+    def pull(self):
+        """Executes SVN Update"""
+        self._svn(self.path, ['update'])
+
+
+    def push(self):
+        """Push doesn't have an SVN analog."""
+        raise NotImplementedError
+
+
+    def checkout(self, rev):
+        """Checks out a branch or revision"""
+        raise NotImplementedError
+        self._svn(self.path, ['update', rev])
+
+
+    def extract_file(self, rev, name, dest):
+        """Extracts a file from a given revision and stores it in dest dir"""
+        if rev == self.INDEX:
+            shutil.copyfile(os.path.join(self.path, name), dest)
+        else:
+            file_contents = self._svn(self.path, ['cat', '-r', rev, name], catchout=True)
+            with open(dest, 'w') as f:
+                f.write(file_contents)
+
+
+    # Data Interface
+    #---------------------------
+
+    def get_status_allfiles(self):
+        """Returns a dict indexed by files not in sync their status as values.
+           Paths are given relative to the root. Strips trailing '/' from dirs."""
+        raw = self._svn(self.path, ['status'], catchout=True, bytes=True)
+#        logging.debug(raw)
+        L = re.findall(r'^(.)\s*(.*?)\s*$', raw.decode('utf-8'), re.MULTILINE)
+        ret = {}
+        for st, p in L:
+            # Detect conflict by the existence of .orig files
+            if st == '?' and re.match(r'^.*\.orig\s*$', p): st = 'X'
+            sta = self._svn_file_status(st)
+            ret[os.path.normpath(p.strip())] = sta
+        return ret
+
+
+    def get_ignore_allfiles(self):
+        """Returns a set of all the ignored files in the repo"""
+        raw = self._svn(self.path, ['status'], catchout=True, bytes=True)
+#        logging.debug(raw)
+        L = re.findall(r'^I\s*(.*?)\s*$', raw.decode('utf-8'), re.MULTILINE)
+        return set(L)
+
+
+    def get_remote_status(self):
+        """Checks the status of the repo regarding sync state with remote branch.
+
+        I'm not sure this make sense for SVN so we're just going to return 'sync'"""
+        return 'sync'
+
+    def get_branch(self):
+        """Returns the current named branch, if this makes sense for the backend. None otherwise"""
+        return None
+        branch = self._svn(self.path, ['branch'], catchout=True)
+        return branch or None
+
+
+    def get_log(self, filelist=None, maxres=None):
+        """Get the entire log for the current HEAD"""
+        if not self._has_head(): return []
+        return self._log(refspec=None, maxres=maxres, filelist=filelist)
+
+
+    def get_raw_log(self, filelist=None):
+        """Gets the raw log as a string"""
+        if not self._has_head(): return []
+        args = ['log']
+        if filelist: args = args + filelist
+        return self._svn(self.path, args, catchout=True)
+
+
+    def get_raw_diff(self, refspec=None, filelist=None):
+        """Gets the raw diff as a string"""
+        args = ['diff', '--git']
+        if refspec:  args = args + [refspec]
+        if filelist: args = args + filelist
+        return self._svn(self.path, args, catchout=True)
+
+
+    def get_remote(self, rev=None):
+        """Returns the url for the remote repo attached to head"""
+        raw = self.get_info(rev=rev)
+        L = re.findall('URL: (.*)\n', raw.decode('utf-8'))
+
+        return L[0][0]
+
+
+    def get_revision_id(self, rev=None):
+        """Get a canonical key for the revision rev.
+
+        This is just returning the rev for SVN"""
+        if rev == None: rev = self.HEAD
+        elif rev == self.INDEX: return None
+        rev = self._sanitize_rev(rev)
+        return rev
+
+
+    def get_info(self, rev=None):
+        """Gets info about the given revision rev"""
+        if rev == None: rev = self.HEAD
+        rev = self._sanitize_rev(rev)
+        if rev == self.HEAD and not self._has_head(): return None
+        logging.debug('refspec is ' + str(rev))
+        L = self._log(refspec=rev)
+        logging.debug(len(L))
+        if len(L) == 0:
+            raise VcsError("Revision %s does not exist" % rev)
+        elif len(L) > 1:
+            raise VcsError("More than one instance of revision %s ?!?" % rev)
+        else:
+            return L[0]
+
+
+    def get_files(self, rev=None):
+        """Gets a list of files in revision rev"""
+        if rev == None: rev = self.HEAD
+        rev = self._sanitize_rev(rev)
+
+        raw = self._svn(self.path, ['ls', "-r", rev], catchout=True)
+        return raw.split('\n')
+
+
+# vim: expandtab:shiftwidth=4:tabstop=4:softtabstop=4:textwidth=80
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 9bbb1779..3f7236f5 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -51,7 +51,8 @@ class Vcs(object):
         from .git import Git
         from .hg  import Hg
         from .bzr import Bzr
-        self.repo_types  = {'git': Git, 'hg': Hg, 'bzr': Bzr}
+        from .svn import SVN
+        self.repo_types  = {'git': Git, 'hg': Hg, 'bzr': Bzr, 'svn': SVN}
 
         self.path = os.path.expanduser(path)
         self.status = {}
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index 5d055a0b..b38d0b5b 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -180,19 +180,15 @@ class BrowserColumn(Pager):
             Pager.close(self)
             return
 
-        if self.fm.settings.preview_images and self.target.image:
-            self.set_image(self.target.realpath)
-            Pager.draw(self)
+        f = self.target.get_preview_source(self.wid, self.hei)
+        if f is None:
+            Pager.close(self)
         else:
-            f = self.target.get_preview_source(self.wid, self.hei)
-            if f is None:
-                Pager.close(self)
+            if self.target.is_image_preview():
+                self.set_image(f)
             else:
-                if self.target.is_image_preview():
-                    self.set_image(f)
-                else:
-                    self.set_source(f)
-                Pager.draw(self)
+                self.set_source(f)
+            Pager.draw(self)
 
     def _draw_directory(self):
         """Draw the contents of a directory"""
diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py
index eb99369b..e9640208 100644
--- a/ranger/gui/widgets/browserview.py
+++ b/ranger/gui/widgets/browserview.py
@@ -258,9 +258,7 @@ class BrowserView(Widget, DisplayableContainer):
         result = not self.columns[-1].has_preview()
         target = self.columns[-1].target
         if not result and target and target.is_file:
-            if target.image and self.fm.settings.preview_images:
-                result = False  # don't collapse when drawing images
-            elif self.fm.settings.preview_script and \
+            if self.fm.settings.preview_script and \
                     self.fm.settings.use_preview_script:
                 try:
                     result = not self.fm.previews[target.realpath]['foundpreview']
@@ -355,8 +353,7 @@ class BrowserView(Widget, DisplayableContainer):
             self.columns[-1].visible = True
 
         if self.preview and self.is_collapsed != self._collapse():
-            if (self.fm.settings.preview_images and
-                self.fm.settings.preview_files):
+            if self.fm.settings.preview_files:
                 # force clearing the image when resizing preview column
                 self.columns[-1].clear_image(force=True)
             self.resize(self.y, self.x, self.hei, self.wid)
diff --git a/setup.py b/setup.py
index 1e074f8d..4fbbccba 100755
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,12 @@
 # This file is part of ranger, the console file manager.
 # License: GNU GPL version 3, see the file "AUTHORS" for details.
 
-import distutils.core
+# Setuptools is superior but not available in the stdlib
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
 import os.path
 import ranger
 
@@ -11,7 +16,7 @@ def _findall(directory):
             if os.path.isfile(os.path.join(directory, f))]
 
 if __name__ == '__main__':
-    distutils.core.setup(
+    setup(
         name='ranger',
         description='Vim-like file manager',
         long_description=ranger.__doc__,
@@ -27,7 +32,7 @@ if __name__ == '__main__':
                  'doc/rifle.1']),
             ('share/doc/ranger',
                 ['README.md',
-                 'CHANGELOG',
+                 'CHANGELOG.md',
                  'HACKING.md',
                  'doc/colorschemes.txt']),
             ('share/doc/ranger/config/colorschemes',