summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorWojciech Siewierski <wojciech.siewierski@onet.pl>2018-12-26 16:14:19 +0100
committerGitHub <noreply@github.com>2018-12-26 16:14:19 +0100
commitaecce4a251910c9da2d58017eccaefbd22edc4cb (patch)
tree397d68f4219a210eb41615c2ea9b75956a3fd40d
parent71c7564107a7e7ae22a55190d16c5eba4615f4a0 (diff)
parent7f20f31379eec35b97c138186d11d0265c9429f3 (diff)
downloadranger-aecce4a251910c9da2d58017eccaefbd22edc4cb.tar.gz
Merge branch 'master' into anypathmacro
-rw-r--r--.travis.yml5
-rw-r--r--CHANGELOG.md39
-rw-r--r--Pipfile15
-rw-r--r--Pipfile.lock175
-rw-r--r--README.md2
-rw-r--r--doc/howto-publish-a-release.md4
-rw-r--r--doc/ranger.134
-rw-r--r--doc/ranger.pod32
-rw-r--r--doc/rifle.122
-rw-r--r--examples/plugin_pmount_dynamic.py70
-rw-r--r--ranger/__init__.py2
-rw-r--r--ranger/colorschemes/default.py25
-rw-r--r--ranger/colorschemes/snow.py4
-rw-r--r--ranger/config/rc.conf23
-rw-r--r--ranger/config/rifle.conf28
-rw-r--r--ranger/container/bookmarks.py8
-rw-r--r--ranger/container/settings.py8
-rw-r--r--ranger/core/actions.py106
-rw-r--r--ranger/core/filter_stack.py17
-rw-r--r--ranger/core/fm.py6
-rw-r--r--ranger/core/loader.py15
-rwxr-xr-xranger/data/scope.sh42
-rw-r--r--ranger/ext/human_readable.py5
-rw-r--r--ranger/ext/img_display.py71
-rwxr-xr-xranger/ext/rifle.py87
-rw-r--r--ranger/gui/color.py5
-rw-r--r--ranger/gui/ui.py3
-rw-r--r--ranger/gui/widgets/browsercolumn.py6
-rw-r--r--ranger/gui/widgets/pager.py10
-rw-r--r--ranger/gui/widgets/view_miller.py92
-rw-r--r--requirements.txt3
-rw-r--r--tests/ranger/container/test_bookmarks.py17
32 files changed, 666 insertions, 315 deletions
diff --git a/.travis.yml b/.travis.yml
index 0efd09fc..7a156b2b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,12 +3,11 @@ dist: 'xenial'
 language: 'python'
 python:
   - '2.7'
-  - '3.4'
   - '3.5'
+  - '3.6'
 
 install:
-  - 'pip install pipenv'
-  - 'pipenv update --dev'
+  - 'pip install -r requirements.txt'
 
 script:
   - 'make test'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 346b18dd..e8eb913c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,44 @@
 This log documents changes between stable versions.
 
+# 2018-09-09: version 1.9.2
+* Added a `hint_collapse_threshold` setting
+* Added a `traverse_backwards` command analogous to `traverse`
+* Added a command to shift tabs
+* Added a normal mode mapping to quickly enter the console and scroll through
+  the history `C-p`
+* Added a section to `scope.sh` for image previews of archives
+* Added an avfs plugin
+* Added an option to the move command to enable launching the selected file
+  instead of the marked files
+* Added filtering functionality inspired by dired's filter stack, `.n, .| ...`
+* Added image preview method for Kitty
+* Added option to disable the display of free space for high latency situations
+* Added section to `scope.sh` for pdf previews with mutool
+* Added several emacs/readline-inspired keybindings, `C-g` for `ESC`, `alt-f/b`
+* Added systemwide `rc.conf` and `commands.py` in `/etc/ranger`
+* Added the `%any_path` macro to allow bookmarks to be used with commands that
+  need a path and are unaware of bookmarks
+* Added versioning logic to include extra info in unreleased versions
+* Change tab saving to save all tabs, not just the active tab
+* Changed `draw_borders` setting to enable drawing only borders or seperators
+* Changed behavior of positional arguments to the ranger command, if you
+  specify a path to a file ranger will open with that file selected
+* Changed the `tilde_in_titlebar` setting to influence the window titlebar too
+* Changed the default colorscheme to work properly in terminals that don't
+  equate bold and bright
+* Fixed StopIteration errors
+* Fixed embedded null errors
+* Fixed issues reported by coverity scan
+* Fixed running ranger as root on Mac OS
+* Fixed unicode issue for python2
+* Fixed w3m preview issues with black stripes
+* Improved PEP8 adherence
+* Improved VCS symbols
+* Improved `--cmd` functionality
+* Improved file encoding detection by using chardet if it's available
+* Rifle's flag t should now work with more terminals than xterm and urxvt
+* Update colorscheme documentation
+
 # 2018-02-22: version 1.9.1
 * Fixed the rifle config backwards compatibility (regression in 1.9.0)
 * Fixed the POSIX compatibility of `Makefile`
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index a927408c..00000000
--- a/Pipfile
+++ /dev/null
@@ -1,15 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[requires]
-python_version = "3.5"
-
-[dev-packages]
-pytest = "*"
-"flake8" = "*"
-pylint = "<2.0.0"
-"enum34" = "*"
-
-[packages]
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index be0d4a62..00000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,175 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "ccd51f0d238502cb3001a4b709d4455134eeaebb96800ebaad364567ba1ba784"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.5"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {},
-    "develop": {
-        "astroid": {
-            "hashes": [
-                "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a",
-                "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a"
-            ],
-            "version": "==1.6.5"
-        },
-        "atomicwrites": {
-            "hashes": [
-                "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
-                "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
-            ],
-            "version": "==1.1.5"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
-                "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
-            ],
-            "version": "==18.1.0"
-        },
-        "enum34": {
-            "hashes": [
-                "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
-                "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
-                "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
-                "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
-            ],
-            "index": "pypi",
-            "version": "==1.1.6"
-        },
-        "flake8": {
-            "hashes": [
-                "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
-                "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
-            ],
-            "index": "pypi",
-            "version": "==3.5.0"
-        },
-        "isort": {
-            "hashes": [
-                "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
-                "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
-                "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
-            ],
-            "version": "==4.3.4"
-        },
-        "lazy-object-proxy": {
-            "hashes": [
-                "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
-                "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
-                "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
-                "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
-                "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
-                "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
-                "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
-                "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
-                "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
-                "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
-                "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
-                "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
-                "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
-                "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
-                "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
-                "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
-                "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
-                "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
-                "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
-                "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
-                "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
-                "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
-                "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
-                "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
-                "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
-                "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
-                "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
-                "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
-                "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
-            ],
-            "version": "==1.3.1"
-        },
-        "mccabe": {
-            "hashes": [
-                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
-                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
-            ],
-            "version": "==0.6.1"
-        },
-        "more-itertools": {
-            "hashes": [
-                "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
-                "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
-                "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
-            ],
-            "version": "==4.2.0"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
-                "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
-                "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
-            ],
-            "version": "==0.6.0"
-        },
-        "py": {
-            "hashes": [
-                "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
-                "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
-            ],
-            "version": "==1.5.4"
-        },
-        "pycodestyle": {
-            "hashes": [
-                "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
-                "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
-            ],
-            "version": "==2.3.1"
-        },
-        "pyflakes": {
-            "hashes": [
-                "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
-                "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
-            ],
-            "version": "==1.6.0"
-        },
-        "pylint": {
-            "hashes": [
-                "sha256:a48070545c12430cfc4e865bf62f5ad367784765681b3db442d8230f0960aa3c",
-                "sha256:fff220bcb996b4f7e2b0f6812fd81507b72ca4d8c4d05daf2655c333800cb9b3"
-            ],
-            "index": "pypi",
-            "version": "==1.9.2"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752",
-                "sha256:4b208614ae6d98195430ad6bde03641c78553acee7c83cec2e85d613c0cd383d"
-            ],
-            "index": "pypi",
-            "version": "==3.6.3"
-        },
-        "six": {
-            "hashes": [
-                "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
-                "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
-            ],
-            "version": "==1.11.0"
-        },
-        "wrapt": {
-            "hashes": [
-                "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
-            ],
-            "version": "==1.10.11"
-        }
-    }
-}
diff --git a/README.md b/README.md
index e14bab46..d7ca0493 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-ranger 1.9.1
+ranger 1.9.2
 ============
 
 [![Build Status](https://travis-ci.org/ranger/ranger.svg?branch=master)](https://travis-ci.org/ranger/ranger)
diff --git a/doc/howto-publish-a-release.md b/doc/howto-publish-a-release.md
index e8d669ce..6bd70047 100644
--- a/doc/howto-publish-a-release.md
+++ b/doc/howto-publish-a-release.md
@@ -37,7 +37,7 @@ Make a release commit
 * [ ] Write changelog entry
 * [ ] Think of a witty commit message
 * [ ] Commit
-* [ ] Tag the signed release with `git tag -a <commit-id>`, using the same
+* [ ] Tag the signed release with `git tag -as vX.Y.Z`, using the same
       commit message as annotation
 * [ ] Push release and tag
 
@@ -73,4 +73,4 @@ Announce the update
 
 Change back to before
 ---------------------
-* [ ] Change `VERSION` in `ranger/__init__.py` back to `master`
+* [ ] Change `__release__` in `ranger/__init__.py` back to `False`
diff --git a/doc/ranger.1 b/doc/ranger.1
index ea010eac..5a159e12 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.1" "2018-07-15" "ranger manual"
+.TH RANGER 1 "ranger-1.9.2" "2018-10-26" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -299,6 +299,9 @@ This does not work over ssh, requires certain terminals (tested on \*(L"xterm\*(
 To enable this feature, install the program \*(L"w3m\*(R" and set the option
 \&\f(CW\*(C`preview_images_method\*(C'\fR to w3m.
 .PP
+When using a terminal with a nonzero border which is not automatically detected, the w3m preview will be misaligned.
+Use the \f(CW\*(C`w3m_offset\*(C'\fR option to manually adjust the image offset. This should be the same value as the terminal's border value.
+.PP
 \fIiTerm2\fR
 .IX Subsection "iTerm2"
 .PP
@@ -667,6 +670,9 @@ the current directory.  For a more permanent solution, use the command
 .IP ".n" 14
 .IX Item ".n"
 Apply a new filename filter.
+.IP ".m" 14
+.IX Item ".m"
+Apply a new mimetype filter.
 .IP ".d" 14
 .IX Item ".d"
 Apply the typefilter \*(L"directory\*(R".
@@ -840,9 +846,16 @@ Display the free disk space in the status bar?
 .IP "display_tags_in_all_columns [bool]" 4
 .IX Item "display_tags_in_all_columns [bool]"
 Display tags in all columns?
-.IP "draw_borders [bool]" 4
-.IX Item "draw_borders [bool]"
-Draw borders around columns?
+.IP "draw_borders [string]" 4
+.IX Item "draw_borders [string]"
+Draw borders around or between the columns? Possible values are:
+.Sp
+.Vb 4
+\& none           no borders of any sort
+\& outline        draw an outline around all the columns
+\& separators     draw only vertical lines between columns
+\& both           both of the above
+.Ve
 .IP "draw_progress_bar_in_status_bar [bool]" 4
 .IX Item "draw_progress_bar_in_status_bar [bool]"
 Draw a progress bar in the status bar which displays the average state of all
@@ -880,6 +893,9 @@ Otherwise the submaps are replaced with \*(L"...\*(R".
 .IP "hostname_in_titlebar [bool]" 4
 .IX Item "hostname_in_titlebar [bool]"
 Show hostname in titlebar?
+.IP "size_in_bytes [bool]" 4
+.IX Item "size_in_bytes [bool]"
+Print file sizes in bytes instead of the default human-readable format.
 .IP "idle_delay [integer]" 4
 .IX Item "idle_delay [integer]"
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -1057,6 +1073,11 @@ traditional miller column view that shows multiple levels of the hierarchy, or
 .IX Item "w3m_delay [float]"
 Delay in seconds before displaying an image with the w3m method.
 Increase it in case of experiencing display corruption.
+.IP "w3m_offset [int]" 4
+.IX Item "w3m_offset [int]"
+Offset in pixels for the inner border of the terminal. Some terminals require
+the offset to be specified explicitly, among others st and UXterm, some don't
+like urxvt.
 .IP "wrap_scroll [bool]" 4
 .IX Item "wrap_scroll [bool]"
 Enable scroll wrapping \- moving down while on the last item will wrap around to
@@ -1463,6 +1484,9 @@ This can be used to re-evaluate the rc.conf file after changing it:
 .Vb 1
 \& map X chain shell vim \-p %confdir/rc.conf %rangerdir/config/rc.conf; source %confdir/rc.conf
 .Ve
+.IP "scroll_preview \fIvalue\fR" 2
+.IX Item "scroll_preview value"
+Scroll the file preview by \fIvalue\fR lines.
 .IP "terminal" 2
 .IX Item "terminal"
 Spawns the \fIx\-terminal-emulator\fR starting in the current directory.
@@ -1641,7 +1665,7 @@ ranger is maintained with the git version control system.  To fetch a fresh
 copy, run:
 .PP
 .Vb 1
-\& git clone git://git.savannah.nongnu.org/ranger.git
+\& git clone git@github.com:ranger/ranger.git
 .Ve
 .SH "SEE ALSO"
 .IX Header "SEE ALSO"
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 5c668235..8fbc8826 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -209,6 +209,9 @@ This does not work over ssh, requires certain terminals (tested on "xterm" and
 To enable this feature, install the program "w3m" and set the option
 C<preview_images_method> to w3m.
 
+When using a terminal with a nonzero border which is not automatically detected, the w3m preview will be misaligned.
+Use the C<w3m_offset> option to manually adjust the image offset. This should be the same value as the terminal's border value.
+
 =head3 iTerm2
 
 This only works in iTerm2 compiled with image preview support, but works over
@@ -628,6 +631,10 @@ the current directory.  For a more permanent solution, use the command
 
 Apply a new filename filter.
 
+=item .m
+
+Apply a new mimetype filter.
+
 =item .d
 
 Apply the typefilter "directory".
@@ -852,10 +859,15 @@ Display the free disk space in the status bar?
 
 Display tags in all columns?
 
-=item draw_borders [bool]
+=item draw_borders [string]
 
-Draw borders around columns?
+Draw borders around or between the columns? Possible values are:
 
+ none           no borders of any sort
+ outline        draw an outline around all the columns
+ separators     draw only vertical lines between columns
+ both           both of the above
+ 
 =item draw_progress_bar_in_status_bar [bool]
 
 Draw a progress bar in the status bar which displays the average state of all
@@ -896,6 +908,10 @@ Otherwise the submaps are replaced with "...".
 
 Show hostname in titlebar?
 
+=item size_in_bytes [bool]
+
+Print file sizes in bytes instead of the default human-readable format.
+
 =item idle_delay [integer]
 
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -1110,6 +1126,12 @@ B<multipane> to use multiple panes (one per tab) similar to midnight-commander.
 Delay in seconds before displaying an image with the w3m method.
 Increase it in case of experiencing display corruption.
 
+=item w3m_offset [int]
+
+Offset in pixels for the inner border of the terminal. Some terminals require
+the offset to be specified explicitly, among others st and UXterm, some don't
+like urxvt.
+
 =item wrap_scroll [bool]
 
 Enable scroll wrapping - moving down while on the last item will wrap around to
@@ -1563,6 +1585,10 @@ This can be used to re-evaluate the rc.conf file after changing it:
 
  map X chain shell vim -p %confdir/rc.conf %rangerdir/config/rc.conf; source %confdir/rc.conf
 
+=item scroll_preview I<value>
+
+Scroll the file preview by I<value> lines.
+
 =item terminal
 
 Spawns the I<x-terminal-emulator> starting in the current directory.
@@ -1803,7 +1829,7 @@ GNU General Public License 3 or (at your option) any later version.
 ranger is maintained with the git version control system.  To fetch a fresh
 copy, run:
 
- git clone git://git.savannah.nongnu.org/ranger.git
+ git clone git@github.com:ranger/ranger.git
 
 
 
diff --git a/doc/rifle.1 b/doc/rifle.1
index c8c679ec..9ed1a145 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
+.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -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 "RIFLE 1"
-.TH RIFLE 1 "rifle-1.9.1" "2018-06-07" "rifle manual"
+.TH RIFLE 1 "rifle-1.9.2" "2018-09-09" "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/plugin_pmount_dynamic.py b/examples/plugin_pmount_dynamic.py
new file mode 100644
index 00000000..1448e15b
--- /dev/null
+++ b/examples/plugin_pmount_dynamic.py
@@ -0,0 +1,70 @@
+# Tested with ranger 1.7.2
+#
+# This plugin creates a bunch of keybindings used to mount and unmount
+# the devices using pmount(1).
+#
+# (multiple partitions): alt+m <letter> <digit>  : mount /dev/sd<letter><digit>
+# (one partition):       alt+m <letter>          : mount /dev/sd<letter>1
+# (no partitions):       alt+m <letter>          : mount /dev/sd<letter>
+#
+# (multiple partitions): alt+M <letter> <digit>  : unmount /dev/sd<letter><digit>
+# (one partition):       alt+M <letter>          : unmount /dev/sd<letter>1
+# (no partitions):       alt+M <letter>          : unmount /dev/sd<letter>
+#
+# alt+n : list the devices
+
+from __future__ import (absolute_import, division, print_function)
+
+import subprocess
+import ranger.api
+
+MOUNT_KEY = '<alt>m'
+UMOUNT_KEY = '<alt>M'
+LIST_MOUNTS_KEY = '<alt>n'
+HOOK_INIT_OLD = ranger.api.hook_init
+
+
+def hook_init(fm):
+    fm.execute_console("map {key} shell -p lsblk".format(key=LIST_MOUNTS_KEY))
+
+    diskcmd = "lsblk -lno NAME | awk '!/[1-9]/ {sub(/sd/, \"\"); print}'"
+    disks = subprocess.check_output(
+        diskcmd, shell=True).decode('utf-8').replace('\r', '').replace('\n', '')
+
+    for disk in disks:
+        partcmd = "lsblk -lno NAME /dev/sd{0} | sed 's/sd{0}//' | tail -n 1".format(disk)
+
+        try:
+            numparts = int(subprocess.check_output(
+                partcmd, shell=True).decode('utf-8').replace('\r', '').replace('\n', ''))
+        except ValueError:
+            numparts = 0
+
+        if numparts == 0:
+            # no partition, mount the whole device
+            fm.execute_console("map {key}{0} chain shell pmount sd{0}; cd /media/sd{0}".format(
+                disk, key=MOUNT_KEY))
+            fm.execute_console("map {key}{0} chain cd; chain shell pumount sd{0}".format(
+                disk, key=UMOUNT_KEY))
+
+        elif numparts == 1:
+            # only one partition, mount the partition
+            fm.execute_console(
+                "map {key}{0} chain shell pmount sd{0}1; cd /media/sd{0}1".format(
+                    disk, key=MOUNT_KEY))
+            fm.execute_console("map {key}{0} chain cd; shell pumount sd{0}1".format(
+                disk, key=UMOUNT_KEY))
+
+        else:
+            # use range start 1, /dev/sd{device}0 doesn't exist
+            for part in range(1, numparts + 1):
+                fm.execute_console(
+                    "map {key}{0}{1} chain shell pmount sd{0}{1}; cd /media/sd{0}{1}".format(
+                        disk, part, key=MOUNT_KEY))
+                fm.execute_console("map {key}{0}{1} chain cd; shell pumount sd{0}{1}".format(
+                    disk, part, key=UMOUNT_KEY))
+
+    return HOOK_INIT_OLD(fm)
+
+
+ranger.api.hook_init = hook_init
diff --git a/ranger/__init__.py b/ranger/__init__.py
index ae1ecc48..2228d40e 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -32,7 +32,7 @@ def version_helper():
 
 # Information
 __license__ = 'GPL3'
-__version__ = '1.9.1'
+__version__ = '1.9.2'
 __release__ = False
 __author__ = __maintainer__ = 'Roman Zimbelmann'
 __email__ = 'hut@hut.pm'
diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py
index 350c8359..a9e01e0c 100644
--- a/ranger/colorschemes/default.py
+++ b/ranger/colorschemes/default.py
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
 from ranger.gui.colorscheme import ColorScheme
 from ranger.gui.color import (
     black, blue, cyan, green, magenta, red, white, yellow, default,
-    normal, bold, reverse,
+    normal, bold, reverse, dim, BRIGHT,
     default_colors,
 )
 
@@ -39,18 +39,22 @@ class Default(ColorScheme):
             if context.directory:
                 attr |= bold
                 fg = blue
+                fg += BRIGHT
             elif context.executable and not \
                     any((context.media, context.container,
                          context.fifo, context.socket)):
                 attr |= bold
                 fg = green
+                fg += BRIGHT
             if context.socket:
-                fg = magenta
                 attr |= bold
+                fg = magenta
+                fg += BRIGHT
             if context.fifo or context.device:
                 fg = yellow
                 if context.device:
                     attr |= bold
+                    fg += BRIGHT
             if context.link:
                 fg = cyan if context.good else magenta
             if context.tag_marker and not context.selected:
@@ -59,10 +63,19 @@ class Default(ColorScheme):
                     fg = white
                 else:
                     fg = red
+                fg += BRIGHT
             if not context.selected and (context.cut or context.copied):
-                fg = black
                 attr |= bold
+                fg = black
+                fg += BRIGHT
+                # If the terminal doesn't support bright colors, use dim white
+                # instead of black.
+                if BRIGHT == 0:
+                    attr |= dim
+                    fg = white
             if context.main_column:
+                # Doubling up with BRIGHT here causes issues because it's
+                # additive not idempotent.
                 if context.selected:
                     attr |= bold
                 if context.marked:
@@ -78,7 +91,6 @@ class Default(ColorScheme):
                 fg = cyan
 
         elif context.in_titlebar:
-            attr |= bold
             if context.hostname:
                 fg = red if context.bad else green
             elif context.directory:
@@ -88,6 +100,8 @@ class Default(ColorScheme):
                     bg = green
             elif context.link:
                 fg = cyan
+            attr |= bold
+            fg += BRIGHT
 
         elif context.in_statusbar:
             if context.permissions:
@@ -98,13 +112,16 @@ class Default(ColorScheme):
             if context.marked:
                 attr |= bold | reverse
                 fg = yellow
+                fg += BRIGHT
             if context.frozen:
                 attr |= bold | reverse
                 fg = cyan
+                fg += BRIGHT
             if context.message:
                 if context.bad:
                     attr |= bold
                     fg = red
+                    fg += BRIGHT
             if context.loaded:
                 bg = self.progress_bar_color
             if context.vcsinfo:
diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py
index 8e9686a8..c5f23c1c 100644
--- a/ranger/colorschemes/snow.py
+++ b/ranger/colorschemes/snow.py
@@ -4,7 +4,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 from ranger.gui.colorscheme import ColorScheme
-from ranger.gui.color import default_colors, reverse, bold
+from ranger.gui.color import default_colors, reverse, bold, BRIGHT
 
 
 class Snow(ColorScheme):
@@ -20,6 +20,7 @@ class Snow(ColorScheme):
                 attr = reverse
             if context.directory:
                 attr |= bold
+                fg += BRIGHT
 
         elif context.highlight:
             attr |= reverse
@@ -36,6 +37,7 @@ class Snow(ColorScheme):
         elif context.in_taskview:
             if context.selected:
                 attr |= bold
+                fg += BRIGHT
             if context.loaded:
                 attr |= reverse
 
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 9c1a6a83..98a059d4 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -106,12 +106,20 @@ set preview_images false
 #   while slower, this allows remote previews,
 #   for example during an ssh session.
 #   Tmux is unsupported.
+#
+# * ueberzug:
+#   Preview images in full color with the external command "ueberzug".
+#   Images are shown by using a child window.
+#   Only for users who run X11 in GNU/Linux.
 set preview_images_method w3m
 
 # Delay in seconds before displaying an image with the w3m method.
 # Increase it in case of experiencing display corruption.
 set w3m_delay 0.02
 
+# Manually adjust the w3mimg offset when using a terminal which needs this
+set w3m_offset 0
+
 # Default iTerm2 font size (see: preview_images_method: iterm2)
 set iterm2_font_width 8
 set iterm2_font_height 11
@@ -146,8 +154,11 @@ set status_bar_on_top false
 # currently running tasks which support progress bars?
 set draw_progress_bar_in_status_bar true
 
-# Draw borders around columns?
-set draw_borders false
+# Draw borders around columns? (separators, outline, both, or none)
+# Separators are vertical lines between columns.
+# Outline draws a box around all the columns.
+# Both combines the two.
+set draw_borders none
 
 # Display the directory name in tabs?
 set dirname_in_tabs false
@@ -287,6 +298,9 @@ set global_inode_type_filter
 # should be 'false' during start-up, but you can toggle it by pressing F.
 set freeze_files false
 
+# Print file sizes in bytes instead of the default human-readable format.
+set size_in_bytes false
+
 # ===================================================================
 # == Local Options
 # ===================================================================
@@ -334,6 +348,8 @@ map <esc> change_mode normal
 map ~ set viewmode!
 
 map i display_file
+map <A-j> scroll_preview 1
+map <A-k> scroll_preview -1
 map ? help
 map W display_log
 map w taskview_open
@@ -517,6 +533,8 @@ map <a-6>     tab_open 6
 map <a-7>     tab_open 7
 map <a-8>     tab_open 8
 map <a-9>     tab_open 9
+map <a-r>     tab_shift 1
+map <a-l>     tab_shift -1
 
 # Sorting
 map or set sort_reverse!
@@ -561,6 +579,7 @@ copymap zf zz
 
 # Filter stack
 map .n console filter_stack add name%space
+map .m console filter_stack add mime%space
 map .d filter_stack add type d
 map .f filter_stack add type f
 map .l filter_stack add type l
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 5a87c1a5..babdcda7 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -212,6 +212,34 @@ ext rar, has unrar = unrar l "$1" | less
 ext rar, has unrar = for file in "$@"; do unrar x "$file"; done
 
 #-------------------------------------------
+# Flag t fallback terminals
+#-------------------------------------------
+# Rarely installed terminal emulators get higher priority; It is assumed that
+# if you install a rare terminal emulator, you probably use it.
+# gnome-terminal/konsole/xterm on the other hand are often installed as part of
+# a desktop environment or as fallback terminal emulators.
+mime ^ranger/x-terminal-emulator, has terminology = terminology -e "$@"
+mime ^ranger/x-terminal-emulator, has kitty = kitty -- "$@"
+mime ^ranger/x-terminal-emulator, has alacritty = alacritty -e "$@"
+mime ^ranger/x-terminal-emulator, has sakura = sakura -e "$@"
+mime ^ranger/x-terminal-emulator, has lilyterm = lilyterm -e "$@"
+#mime ^ranger/x-terminal-emulator, has cool-retro-term = cool-retro-term -e "$@"
+mime ^ranger/x-terminal-emulator, has termite = termite -x '"$@"'
+#mime ^ranger/x-terminal-emulator, has yakuake = yakuake -e "$@"
+mime ^ranger/x-terminal-emulator, has guake = guake -ne "$@"
+mime ^ranger/x-terminal-emulator, has tilda = tilda -c "$@"
+mime ^ranger/x-terminal-emulator, has st = st -e "$@"
+mime ^ranger/x-terminal-emulator, has terminator = terminator -x "$@"
+mime ^ranger/x-terminal-emulator, has urxvt = urxvt -e "$@"
+mime ^ranger/x-terminal-emulator, has pantheon-terminal = pantheon-terminal -e "$@"
+mime ^ranger/x-terminal-emulator, has lxterminal = lxterminal -e "$@"
+mime ^ranger/x-terminal-emulator, has mate-terminal = mate-terminal -x "$@"
+mime ^ranger/x-terminal-emulator, has xfce4-terminal = xfce4-terminal -x "$@"
+mime ^ranger/x-terminal-emulator, has konsole = konsole -e "$@"
+mime ^ranger/x-terminal-emulator, has gnome-terminal = gnome-terminal -- "$@"
+mime ^ranger/x-terminal-emulator, has xterm = xterm -e "$@"
+
+#-------------------------------------------
 # Misc
 #-------------------------------------------
 label wallpaper, number 11, mime ^image, has feh, X = feh --bg-scale "$1"
diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py
index 59838c00..cbfc541a 100644
--- a/ranger/container/bookmarks.py
+++ b/ranger/container/bookmarks.py
@@ -185,7 +185,13 @@ class Bookmarks(FileManagerAware):
             old_perms = os.stat(self.path)
             os.chown(path_new, old_perms.st_uid, old_perms.st_gid)
             os.chmod(path_new, old_perms.st_mode)
-            os.rename(path_new, self.path)
+
+            if os.path.islink(self.path):
+                target_path = os.path.realpath(self.path)
+                os.rename(path_new, target_path)
+            else:
+                os.rename(path_new, self.path)
+
         except OSError as ex:
             self.fm.notify('Bookmarks error: {0}'.format(str(ex)), bad=True)
             return
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 94e455a5..478f6124 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -41,7 +41,7 @@ ALLOWED_SETTINGS = {
     'display_size_in_status_bar': bool,
     "display_free_space_in_status_bar": bool,
     'display_tags_in_all_columns': bool,
-    'draw_borders': bool,
+    'draw_borders': str,
     'draw_progress_bar_in_status_bar': bool,
     'flushinput': bool,
     'freeze_files': bool,
@@ -49,6 +49,7 @@ ALLOWED_SETTINGS = {
     'hidden_filter': str,
     'hint_collapse_threshold': int,
     'hostname_in_titlebar': bool,
+    'size_in_bytes': bool,
     'idle_delay': int,
     'iterm2_font_width': int,
     'iterm2_font_height': int,
@@ -94,6 +95,7 @@ ALLOWED_SETTINGS = {
     'vcs_backend_svn': str,
     'viewmode': str,
     'w3m_delay': float,
+    'w3m_offset': int,
     'wrap_scroll': bool,
     'xterm_alt_key': bool,
 }
@@ -101,10 +103,12 @@ ALLOWED_SETTINGS = {
 ALLOWED_VALUES = {
     'cd_tab_case': ['sensitive', 'insensitive', 'smart'],
     'confirm_on_delete': ['multiple', 'always', 'never'],
+    'draw_borders': ['none', 'both', 'outline', 'separators'],
     'line_numbers': ['false', 'absolute', 'relative'],
     'one_indexed': [False, True],
     'preview_images_method': ['w3m', 'iterm2', 'terminology',
-                              'urxvt', 'urxvt-full', 'kitty'],
+                              'urxvt', 'urxvt-full', 'kitty',
+                              'ueberzug'],
     'vcs_backend_bzr': ['disabled', 'local', 'enabled'],
     'vcs_backend_git': ['enabled', 'disabled', 'local'],
     'vcs_backend_hg': ['disabled', 'local', 'enabled'],
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index c2726015..6f4b76d0 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -211,7 +211,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         self.ui.redraw_main_column()
 
     def redraw_window(self):
-        """:redraw
+        """:redraw_window
 
         Redraw the window.
         """
@@ -420,7 +420,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             are multiple choices
         label: a string to select an opening method by its label
         flags: a string specifying additional options, see `man rifle`
-        mimetyle: pass the mimetype to rifle, overriding its own guess
+        mimetype: pass the mimetype to rifle, overriding its own guess
         """
 
         mode = kw['mode'] if 'mode' in kw else 0
@@ -502,7 +502,10 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             if narg is not None:
                 mode = narg
             tfile = self.thisfile
-            selection = self.thistab.get_selection()
+            if kw.get('selection', True):
+                selection = self.thistab.get_selection()
+            else:
+                selection = [tfile]
             if tfile.is_directory:
                 self.thistab.enter_dir(tfile)
             elif selection:
@@ -990,6 +993,17 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         else:
             pager.set_source(fobj)
 
+    def scroll_preview(self, lines, narg=None):
+        """:scroll_preview <lines>
+
+        Scroll the file preview by <lines> lines.
+        """
+        preview_column = self.ui.browser.columns[-1]
+        if preview_column.target and preview_column.target.is_file:
+            if narg is not None:
+                lines = narg
+            preview_column.scrollbit(lines)
+
     # --------------------------
     # -- Previews
     # --------------------------
@@ -1151,31 +1165,34 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
     @staticmethod
     def read_text_file(path, count=None):
         """Encoding-aware reading of a text file."""
+        # Guess encoding ourselves.
+        # These should be the most frequently used ones.
+        # latin-1 as the last resort
+        encodings = [('utf-8', 'strict'), ('utf-16', 'strict'),
+                     ('latin-1', 'replace')]
+
+        with open(path, 'rb') as fobj:
+            data = fobj.read(count)
+
         try:
             import chardet
         except ImportError:
-            # Guess encoding ourselves. These should be the most frequently used ones.
-            encodings = ('utf-8', 'utf-16')
-            for encoding in encodings:
-                try:
-                    with codecs.open(path, 'r', encoding=encoding) as fobj:
-                        text = fobj.read(count)
-                except UnicodeDecodeError:
-                    pass
-                else:
-                    LOG.debug("guessed encoding of '%s' as %r", path, encoding)
-                    return text
+            pass
         else:
-            with open(path, 'rb') as fobj:
-                data = fobj.read(count)
             result = chardet.detect(data)
-            LOG.debug("chardet guess for '%s': %s", path, result)
             guessed_encoding = result['encoding']
-            return codecs.decode(data, guessed_encoding, 'replace')
+            if guessed_encoding is not None:
+                # Add chardet's guess before our own.
+                encodings.insert(0, (guessed_encoding, 'replace'))
 
-        # latin-1 as the last resort
-        with codecs.open(path, 'r', encoding='latin-1', errors='replace') as fobj:
-            return fobj.read(count)
+        for (encoding, error_scheme) in encodings:
+            try:
+                text = codecs.decode(data, encoding, error_scheme)
+            except UnicodeDecodeError:
+                pass
+            else:
+                LOG.debug("Guessed encoding of '%s' as %s", path, encoding)
+                return text
 
     # --------------------------
     # -- Tabs
@@ -1257,6 +1274,53 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             i += 1
         return self.tab_open(i, path)
 
+    def tab_shift(self, offset=0, to=None):  # pylint: disable=invalid-name
+        """Shift the tab left/right
+
+        Shift the current tab to the left or right by either:
+        offset - changes the tab number by offset
+        to - shifts the tab to the specified tab number
+        """
+
+        oldtab_index = self.current_tab
+        if to is None:
+            assert isinstance(offset, int)
+            # enumerated index (1 to 9)
+            newtab_index = oldtab_index + offset
+        else:
+            assert isinstance(to, int)
+            newtab_index = to
+        # shift tabs without enumerating, preserve tab numbers when can
+        if newtab_index != oldtab_index:
+            # the other tabs shift in the opposite direction
+            if (newtab_index - oldtab_index) > 0:
+                direction = -1
+            else:
+                direction = 1
+
+            def tabshiftreorder(source_index):
+                # shift the tabs to make source_index empty
+                if source_index in self.tabs:
+                    target_index = source_index + direction
+                    # make the target_index empty recursively
+                    tabshiftreorder(target_index)
+                    # shift the source to target
+                    source_tab = self.tabs[source_index]
+                    self.tabs[target_index] = source_tab
+                    del self.tabs[source_index]
+
+            # first remove the current tab from the dict
+            oldtab = self.tabs[oldtab_index]
+            del self.tabs[oldtab_index]
+            # make newtab_index empty by shifting
+            tabshiftreorder(newtab_index)
+            self.tabs[newtab_index] = oldtab
+            self.current_tab = newtab_index
+            self.thistab = oldtab
+            self.ui.titlebar.request_redraw()
+            self.signal_emit('tab.layoutchange')
+        return None
+
     def tab_switch(self, path, create_directory=False):
         """Switches to tab of given path, opening a new tab as necessary.
 
diff --git a/ranger/core/filter_stack.py b/ranger/core/filter_stack.py
index ff9f4080..2ca2b1c5 100644
--- a/ranger/core/filter_stack.py
+++ b/ranger/core/filter_stack.py
@@ -6,6 +6,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 import re
+import mimetypes
 
 from ranger.container.directory import accept_file, InodeFilterConstants
 
@@ -48,6 +49,22 @@ class NameFilter(BaseFilter):
         return "<Filter: name =~ /{}/>".format(self.pattern)
 
 
+@stack_filter("mime")
+class MimeFilter(BaseFilter):
+    def __init__(self, pattern):
+        self.pattern = pattern
+        self.regex = re.compile(pattern)
+
+    def __call__(self, fobj):
+        mimetype, _ = mimetypes.guess_type(fobj.relative_path)
+        if mimetype is None:
+            return False
+        return self.regex.search(mimetype)
+
+    def __str__(self):
+        return "<Filter: mimetype =~ /{}/>".format(self.pattern)
+
+
 @stack_filter("type")
 class TypeFilter(BaseFilter):
     type_to_function = {
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 61b3cb11..43001e8b 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -25,7 +25,7 @@ from ranger.core.runner import Runner
 from ranger.ext.img_display import (W3MImageDisplayer, ITerm2ImageDisplayer,
                                     TerminologyImageDisplayer,
                                     URXVTImageDisplayer, URXVTImageFSDisplayer,
-                                    KittyImageDisplayer,
+                                    KittyImageDisplayer, UeberzugImageDisplayer,
                                     ImageDisplayer)
 from ranger.core.metadata import MetadataManager
 from ranger.ext.rifle import Rifle
@@ -102,6 +102,8 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
         self.rifle.reload_config()
 
         def set_image_displayer():
+            if self.image_displayer:
+                self.image_displayer.quit()
             self.image_displayer = self._get_image_displayer()
         set_image_displayer()
         self.settings.signal_bind('setopt.preview_images_method', set_image_displayer,
@@ -238,6 +240,8 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             return URXVTImageFSDisplayer()
         elif self.settings.preview_images_method == "kitty":
             return KittyImageDisplayer()
+        elif self.settings.preview_images_method == "ueberzug":
+            return UeberzugImageDisplayer()
         return ImageDisplayer()
 
     def _get_thisfile(self):
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 274dc610..9f32535f 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -194,6 +194,7 @@ class CommandLoader(  # pylint: disable=too-many-instance-attributes
                 selectlist.append(process.stdout)
             if not self.silent:
                 selectlist.append(process.stderr)
+            read_stdout = None
             while process.poll() is None:
                 yield
                 if self.finished:
@@ -210,10 +211,11 @@ class CommandLoader(  # pylint: disable=too-many-instance-attributes
                                 self.fm.notify(read, bad=True)
                         elif robjs == process.stdout:
                             read = robjs.read(512)
-                            if py3:
-                                read = safe_decode(read)
                             if read:
-                                self.stdout_buffer += read
+                                if read_stdout is None:
+                                    read_stdout = read
+                                else:
+                                    read_stdout += read
                 except select.error:
                     sleep(0.03)
             if not self.silent:
@@ -223,9 +225,12 @@ class CommandLoader(  # pylint: disable=too-many-instance-attributes
                     self.fm.notify(line, bad=True)
             if self.read:
                 read = process.stdout.read()
+                if read:
+                    read_stdout += read
+            if read_stdout:
                 if py3:
-                    read = safe_decode(read)
-                self.stdout_buffer += read
+                    read_stdout = safe_decode(read_stdout)
+                self.stdout_buffer += read_stdout
         self.finished = True
         self.signal_emit('after', process=process, loader=self)
 
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 25251533..8e0a0f6d 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -83,6 +83,11 @@ handle_extension() {
             lynx -dump -- "${FILE_PATH}" && exit 5
             elinks -dump "${FILE_PATH}" && exit 5
             ;; # Continue with next handler on failure
+        # JSON
+        json)
+            jq --color-output . "${FILE_PATH}" && exit 5
+            python -m json.tool -- "${FILE_PATH}" && exit 5
+            ;;
     esac
 }
 
@@ -123,6 +128,43 @@ handle_image() {
         #              -jpeg -tiffcompression jpeg \
         #              -- "${FILE_PATH}" "${IMAGE_CACHE_PATH%.*}" \
         #         && exit 6 || exit 1;;
+
+        # Preview archives using the first image inside.
+        # (Very useful for comic book collections for example.)
+        # application/zip|application/x-rar|application/x-7z-compressed|\
+        #     application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar)
+        #     local fn=""; local fe=""
+        #     local zip=""; local rar=""; local tar=""; local bsd=""
+        #     case "${mimetype}" in
+        #         application/zip) zip=1 ;;
+        #         application/x-rar) rar=1 ;;
+        #         application/x-7z-compressed) ;;
+        #         *) tar=1 ;;
+        #     esac
+        #     { [ "$tar" ] && fn=$(tar --list --file "${FILE_PATH}"); } || \
+        #     { fn=$(bsdtar --list --file "${FILE_PATH}") && bsd=1 && tar=""; } || \
+        #     { [ "$rar" ] && fn=$(unrar lb -p- -- "${FILE_PATH}"); } || \
+        #     { [ "$zip" ] && fn=$(zipinfo -1 -- "${FILE_PATH}"); } || return
+        #
+        #     fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \
+        #             [ print(l, end='') for l in sys.stdin if \
+        #               (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\
+        #         sort -V | head -n 1)
+        #     [ "$fn" = "" ] && return
+        #     [ "$bsd" ] && fn=$(printf '%b' "$fn")
+        #
+        #     [ "$tar" ] && tar --extract --to-stdout \
+        #         --file "${FILE_PATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6
+        #     fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g')
+        #     [ "$bsd" ] && bsdtar --extract --to-stdout \
+        #         --file "${FILE_PATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6
+        #     [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}"
+        #     [ "$rar" ] && unrar p -p- -inul -- "${FILE_PATH}" "$fn" > \
+        #         "${IMAGE_CACHE_PATH}" && exit 6
+        #     [ "$zip" ] && unzip -pP "" -- "${FILE_PATH}" "$fe" > \
+        #         "${IMAGE_CACHE_PATH}" && exit 6
+        #     [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}"
+        #     ;;
     esac
 }
 
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index f365e594..385e56f4 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -3,6 +3,8 @@
 
 from __future__ import (absolute_import, division, print_function)
 
+from ranger.core.shared import SettingsAware
+
 
 def human_readable(byte, separator=' '):  # pylint: disable=too-many-return-statements
     """Convert a large number of bytes to an easily readable format.
@@ -19,6 +21,9 @@ def human_readable(byte, separator=' '):  # pylint: disable=too-many-return-stat
     if byte is None:
         return ''
 
+    if SettingsAware.settings.size_in_bytes:
+        return format(byte, 'n')  # 'n' = locale-aware separator.
+
     # I know this can be written much shorter, but this long version
     # performs much better than what I had before.  If you attempt to
     # shorten this code, take performance into consideration.
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index f78e170b..9c84ce5e 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -20,6 +20,8 @@ import os
 import struct
 import sys
 import warnings
+import json
+import threading
 from subprocess import Popen, PIPE
 
 import termios
@@ -219,9 +221,12 @@ class W3MImageDisplayer(ImageDisplayer, FileManagerAware):
             width = (width * max_height_pixels) // height
             height = max_height_pixels
 
+        start_x = int((start_x - 0.2) * fontw) + self.fm.settings.w3m_offset
+        start_y = (start_y * fonth) + self.fm.settings.w3m_offset
+
         return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;\n".format(
-            x=int((start_x - 0.2) * fontw),
-            y=start_y * fonth,
+            x=start_x,
+            y=start_y,
             # y = (start_y + 1) * fonth, # (for tmux top status bar)
             w=width,
             h=height,
@@ -251,6 +256,9 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware):
         self.fm.ui.win.redrawwin()
         self.fm.ui.win.refresh()
 
+    def quit(self):
+        self.clear(0, 0, 0, 0)
+
     def _generate_iterm2_input(self, path, max_cols, max_rows):
         """Prepare the image content of path for image display in iTerm2"""
         image_width, image_height = self._get_image_dimensions(path)
@@ -376,6 +384,9 @@ class TerminologyImageDisplayer(ImageDisplayer, FileManagerAware):
         self.fm.ui.win.redrawwin()
         self.fm.ui.win.refresh()
 
+    def quit(self):
+        self.clear(0, 0, 0, 0)
+
 
 class URXVTImageDisplayer(ImageDisplayer, FileManagerAware):
     """Implementation of ImageDisplayer working by setting the urxvt
@@ -588,14 +599,14 @@ class KittyImageDisplayer(ImageDisplayer):
             image = image.resize((int(scale * image.width), int(scale * image.height)),
                                  self.backend.LANCZOS)
 
+        if image.mode != 'RGB' and image.mode != 'RGBA':
+            image = image.convert('RGB')
         # start_x += ((box[0] - image.width) // 2) // self.pix_row
         # start_y += ((box[1] - image.height) // 2) // self.pix_col
         if self.stream:
             # encode the whole image as base64
             # TODO: implement z compression
             # to possibly increase resolution in sent image
-            if image.mode != 'RGB' and image.mode != 'RGBA':
-                image = image.convert('RGB')
             # t: transmissium medium, 'd' for embedded
             # f: size of a pixel fragment (8bytes per color)
             # s, v: size of the image to recompose the flattened data
@@ -664,3 +675,55 @@ class KittyImageDisplayer(ImageDisplayer):
         #         os.remove(self.temp_paths[k])
         #     except (OSError, IOError):
         #         continue
+
+
+class UeberzugImageDisplayer(ImageDisplayer):
+    """Implementation of ImageDisplayer using ueberzug.
+    Ueberzug can display images in a Xorg session.
+    Does not work over ssh.
+    """
+    IMAGE_ID = 'preview'
+    is_initialized = False
+
+    def __init__(self):
+        self.process = None
+
+    def initialize(self):
+        """start ueberzug"""
+        if (self.is_initialized and self.process.poll() is None
+                and not self.process.stdin.closed):
+            return
+
+        self.process = Popen(['ueberzug', 'layer', '--silent'],
+                             stdin=PIPE, universal_newlines=True)
+        self.is_initialized = True
+
+    def _execute(self, **kwargs):
+        self.initialize()
+        self.process.stdin.write(json.dumps(kwargs) + '\n')
+        self.process.stdin.flush()
+
+    def draw(self, path, start_x, start_y, width, height):
+        self._execute(
+            action='add',
+            identifier=self.IMAGE_ID,
+            x=start_x,
+            y=start_y,
+            max_width=width,
+            max_height=height,
+            path=path
+        )
+
+    def clear(self, start_x, start_y, width, height):
+        if self.process and not self.process.stdin.closed:
+            self._execute(action='remove', identifier=self.IMAGE_ID)
+
+    def quit(self):
+        if self.is_initialized and self.process.poll() is None:
+            timer_kill = threading.Timer(1, self.process.kill, [])
+            try:
+                self.process.terminate()
+                timer_kill.start()
+                self.process.communicate()
+            finally:
+                timer_kill.cancel()
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 07a76488..377f9b8a 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -21,7 +21,7 @@ import re
 from subprocess import Popen, PIPE
 import sys
 
-__version__ = 'rifle 1.9.1'
+__version__ = 'rifle 1.9.2'
 
 # Options and constants that a user might want to change:
 DEFAULT_PAGER = 'less'
@@ -162,9 +162,7 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
             config_file = self.config_file
         fobj = open(config_file, 'r')
         self.rules = []
-        lineno = 0
         for line in fobj:
-            lineno += 1
             line = line.strip()
             if line.startswith('#') or line == '':
                 continue
@@ -363,23 +361,74 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
 
                 cmd = prefix + [command]
                 if 't' in flags:
-                    if 'TERMCMD' not in os.environ:
-                        term = os.environ['TERM']
-                        if term.startswith('rxvt-unicode'):
+                    term = os.environ.get('TERMCMD', os.environ['TERM'])
+
+                    # Handle aliases of xterm and urxvt, rxvt and st and
+                    # termite
+                    # Match 'xterm', 'xterm-256color'
+                    if term in ['xterm', 'xterm-256color']:
+                        term = 'xterm'
+                    if term in ['xterm-kitty']:
+                        term = 'kitty'
+                    if term in ['xterm-termite']:
+                        term = 'termite'
+                    if term in ['st', 'st-256color']:
+                        term = 'st'
+                    if term in ['urxvt', 'rxvt-unicode',
+                                'rxvt-unicode-256color']:
+                        term = 'urxvt'
+                    if term in ['rxvt', 'rxvt-256color']:
+                        if 'rxvt' in get_executables():
+                            term = 'rxvt'
+                        else:
                             term = 'urxvt'
-                        elif term.startswith('rxvt-'):
-                            # Sometimes urxvt calls itself "rxvt-256color"
-                            if 'rxvt' in get_executables():
-                                term = 'rxvt'
-                            else:
-                                term = 'urxvt'
-                        if term not in get_executables():
-                            self.hook_logger("Can not determine terminal command.  "
-                                             "Please set $TERMCMD manually.")
-                            # A fallback terminal that is likely installed:
-                            term = 'xterm'
-                        os.environ['TERMCMD'] = term
-                    cmd = [os.environ['TERMCMD'], '-e'] + cmd
+
+                    if term not in get_executables():
+                        self.hook_logger("Can not determine terminal command, "
+                                         "using rifle to determine fallback.  "
+                                         "Please set $TERMCMD manually or "
+                                         "change fallbacks in rifle.conf.")
+                        self._mimetype = 'ranger/x-terminal-emulator'
+                        self.execute(
+                            files=[command.split(';')[1].split('--')[0].strip()]
+                            + files, flags='f',
+                            mimetype='ranger/x-terminal-emulator')
+                        return None
+
+                    # Choose correct cmdflag accordingly
+                    if term in ['xfce4-terminal', 'mate-terminal',
+                                'terminator']:
+                        cmdflag = '-x'
+                    elif term in ['xterm', 'urxvt', 'rxvt', 'lxterminal',
+                                  'konsole', 'lilyterm', 'cool-retro-term',
+                                  'terminology', 'pantheon-terminal', 'termite',
+                                  'st', 'stterm']:
+                        cmdflag = '-e'
+                    elif term in ['gnome-terminal', 'kitty']:
+                        cmdflag = '--'
+                    elif term in ['tilda', ]:
+                        cmdflag = '-c'
+                    else:
+                        cmdflag = '-e'
+
+                    os.environ['TERMCMD'] = term
+
+                    # These terms don't work with the '/bin/sh set --' scheme.
+                    # A temporary fix.
+                    if term in ['tilda', 'pantheon-terminal', 'terminology',
+                                'termite']:
+
+                        target = command.split(';')[0].split('--')[1].strip()
+                        app = command.split(';')[1].split('--')[0].strip()
+                        cmd = [os.environ['TERMCMD'], cmdflag, '%s %s'
+                               % (app, target)]
+                    elif term in ['guake']:
+                        cmd = [os.environ['TERMCMD'], '-n', '${PWD}', cmdflag] + cmd
+                    else:
+                        cmd = [os.environ['TERMCMD'], cmdflag] + cmd
+
+                    # self.hook_logger('cmd: %s' %cmd)
+
                 if 'f' in flags or 't' in flags:
                     Popen_forked(cmd, env=self.hook_environment(os.environ))
                 else:
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 45f983e8..8f6439c7 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -66,6 +66,11 @@ blink      = curses.A_BLINK
 reverse    = curses.A_REVERSE
 underline  = curses.A_UNDERLINE
 invisible  = curses.A_INVIS
+dim = curses.A_DIM
 
 default_colors = (default, default, normal)
 # pylint: enable=invalid-name,bad-whitespace
+
+curses.setupterm()
+# Adding BRIGHT to a color achieves what `bold` was used for.
+BRIGHT = 8 if curses.tigetnum('colors') >= 16 else 0
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 441e9032..9eab4c87 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -136,6 +136,9 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
             self.vcsthread.pause()
             self.vcsthread.paused.wait()
 
+        if self.fm.image_displayer:
+            self.fm.image_displayer.quit()
+
         self.win.keypad(0)
         curses.nocbreak()
         curses.echo()
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index bc6f7b1b..ecc66f44 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -60,7 +60,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
     def request_redraw(self):
         self.need_redraw = True
 
-    def click(self, event):
+    def click(self, event):     # pylint: disable=too-many-branches
         """Handle a MouseEvent"""
         direction = event.mouse_wheel_direction()
         if not (event.pressed(1) or event.pressed(3) or direction):
@@ -95,7 +95,8 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
                         elif self.level == 0:
                             self.fm.thisdir.move_to_obj(clicked_file)
                             self.fm.execute_file(clicked_file)
-
+        elif self.target.is_file:
+            self.scrollbit(direction)
         else:
             if self.level > 0 and not direction:
                 self.fm.move(right=0)
@@ -155,6 +156,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
         if target != self.old_dir:
             self.need_redraw = True
             self.old_dir = target
+            self.scroll_extra = 0  # reset scroll start
 
         if target:
             target.use()
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index d64d4ac1..c1aa2765 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -34,6 +34,7 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
         Widget.__init__(self, win)
         self.embedded = embedded
         self.scroll_begin = 0
+        self.scroll_extra = 0
         self.startx = 0
         self.markup = None
         self.lines = []
@@ -74,6 +75,12 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
     def finalize(self):
         self.fm.ui.win.move(self.y, self.x)
 
+    def scrollbit(self, lines):
+        target_scroll = self.scroll_extra + lines
+        max_scroll = len(self.lines) - self.hei
+        self.scroll_extra = max(0, min(target_scroll, max_scroll))
+        self.need_redraw = True
+
     def draw(self):
         if self.need_clear_image:
             self.need_redraw = True
@@ -94,8 +101,9 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
             self.clear_image()
 
             if not self.image:
+                scroll_pos = self.scroll_begin + self.scroll_extra
                 line_gen = self._generate_lines(
-                    starty=self.scroll_begin, startx=self.startx)
+                    starty=scroll_pos, startx=self.startx)
 
                 for line, i in zip(line_gen, range(self.hei)):
                     self._draw_line(i, line)
diff --git a/ranger/gui/widgets/view_miller.py b/ranger/gui/widgets/view_miller.py
index e138ee4b..55d401a0 100644
--- a/ranger/gui/widgets/view_miller.py
+++ b/ranger/gui/widgets/view_miller.py
@@ -99,7 +99,12 @@ class ViewMiller(ViewBase):  # pylint: disable=too-many-ancestors,too-many-insta
                 directory.use()
         DisplayableContainer.draw(self)
         if self.settings.draw_borders:
-            self._draw_borders()
+            draw_borders = self.settings.draw_borders.lower()
+            if draw_borders in ['both', 'true']:   # 'true' for backwards compat.
+                border_types = ['separators', 'outline']
+            else:
+                border_types = [draw_borders]
+            self._draw_borders(border_types)
         if self.draw_bookmarks:
             self._draw_bookmarks()
         elif self.draw_hints:
@@ -107,8 +112,9 @@ class ViewMiller(ViewBase):  # pylint: disable=too-many-ancestors,too-many-insta
         elif self.draw_info:
             self._draw_info(self.draw_info)
 
-    def _draw_borders(self):
+    def _draw_borders(self, border_types):  # pylint: disable=too-many-branches
         win = self.win
+
         self.color('in_browser', 'border')
 
         left_start = 0
@@ -129,49 +135,57 @@ class ViewMiller(ViewBase):  # pylint: disable=too-many-ancestors,too-many-insta
                 right_end = self.wid - 1
 
         # Draw horizontal lines and the leftmost vertical line
-        try:
-            # pylint: disable=no-member
-            win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
-            win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start)
-            win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
-            # pylint: enable=no-member
-        except curses.error:
-            pass
+        if 'outline' in border_types:
+            try:
+                # pylint: disable=no-member
+                win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
+                win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start)
+                win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
+                # pylint: enable=no-member
+            except curses.error:
+                pass
 
         # Draw the vertical lines in the middle
-        for child in self.columns[:-1]:
-            if not child.has_preview():
-                continue
-            if child.main_column and self.pager.visible:
-                # If we "zoom in" with the pager, we have to
-                # skip the between main_column and pager.
-                break
-            x = child.x + child.wid
-            y = self.hei - 1
+        if 'separators' in border_types:
+            for child in self.columns[:-1]:
+                if not child.has_preview():
+                    continue
+                if child.main_column and self.pager.visible:
+                    # If we "zoom in" with the pager, we have to
+                    # skip the between main_column and pager.
+                    break
+                x = child.x + child.wid
+                y = self.hei - 1
+                try:
+                    # pylint: disable=no-member
+                    win.vline(1, x, curses.ACS_VLINE, y - 1)
+                    if 'outline' in border_types:
+                        self.addch(0, x, curses.ACS_TTEE, 0)
+                        self.addch(y, x, curses.ACS_BTEE, 0)
+                    else:
+                        self.addch(0, x, curses.ACS_VLINE, 0)
+                        self.addch(y, x, curses.ACS_VLINE, 0)
+                    # pylint: enable=no-member
+                except curses.error:
+                    # in case it's off the boundaries
+                    pass
+
+        if 'outline' in border_types:
+            # Draw the last vertical line
             try:
                 # pylint: disable=no-member
-                win.vline(1, x, curses.ACS_VLINE, y - 1)
-                self.addch(0, x, curses.ACS_TTEE, 0)
-                self.addch(y, x, curses.ACS_BTEE, 0)
+                win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
                 # pylint: enable=no-member
             except curses.error:
-                # in case it's off the boundaries
                 pass
 
-        # Draw the last vertical line
-        try:
+        if 'outline' in border_types:
             # pylint: disable=no-member
-            win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
+            self.addch(0, left_start, curses.ACS_ULCORNER)
+            self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
+            self.addch(0, right_end, curses.ACS_URCORNER)
+            self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
             # pylint: enable=no-member
-        except curses.error:
-            pass
-
-        # pylint: disable=no-member
-        self.addch(0, left_start, curses.ACS_ULCORNER)
-        self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
-        self.addch(0, right_end, curses.ACS_URCORNER)
-        self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
-        # pylint: enable=no-member
 
     def _collapse(self):
         # Should the last column be cut off? (Because there is no preview)
@@ -195,10 +209,12 @@ class ViewMiller(ViewBase):  # pylint: disable=too-many-ancestors,too-many-insta
         """Resize all the columns according to the given ratio"""
         ViewBase.resize(self, y, x, hei, wid)
 
-        borders = self.settings.draw_borders
-        pad = 1 if borders else 0
+        border_type = self.settings.draw_borders.lower()
+        if border_type in ['outline', 'both', 'true']:
+            pad = 1
+        else:
+            pad = 0
         left = pad
-
         self.is_collapsed = self._collapse()
         if self.is_collapsed:
             generator = enumerate(self.stretch_ratios)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..411a2a97
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+flake8
+pylint<2
+pytest
diff --git a/tests/ranger/container/test_bookmarks.py b/tests/ranger/container/test_bookmarks.py
index 6fba2a3d..64192c06 100644
--- a/tests/ranger/container/test_bookmarks.py
+++ b/tests/ranger/container/test_bookmarks.py
@@ -56,3 +56,20 @@ def testbookmarks(tmpdir):
         secondstore.update_if_outdated()
     secondstore.update = origupdate
     secondstore.update_if_outdated()
+
+
+def test_bookmark_symlink(tmpdir):
+    # Initialize plain file and symlink paths
+    bookmarkfile_link = tmpdir.join("bookmarkfile")
+    bookmarkfile_orig = tmpdir.join("bookmarkfile.orig")
+
+    # Create symlink pointing towards the original plain file.
+    os.symlink(str(bookmarkfile_orig), str(bookmarkfile_link))
+
+    # Initialize the bookmark file and save the file.
+    bmstore = Bookmarks(str(bookmarkfile_link))
+    bmstore.save()
+
+    # Once saved, the bookmark file should still be a symlink pointing towards the plain file.
+    assert os.path.islink(str(bookmarkfile_link))
+    assert not os.path.islink(str(bookmarkfile_orig))