summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml13
-rw-r--r--Makefile22
-rw-r--r--Pipfile15
-rw-r--r--Pipfile.lock175
-rw-r--r--README.md2
-rw-r--r--doc/colorschemes.md140
-rw-r--r--doc/colorschemes.txt92
-rw-r--r--doc/howto-publish-a-release.md2
-rw-r--r--doc/ranger.1113
-rw-r--r--doc/ranger.pod104
-rw-r--r--doc/rifle.122
-rw-r--r--examples/bash_automatic_cd.sh4
-rw-r--r--examples/plugin_pmount_dynamic.py70
-rw-r--r--ranger/__init__.py21
-rw-r--r--ranger/colorschemes/default.py25
-rw-r--r--ranger/colorschemes/snow.py4
-rwxr-xr-xranger/config/commands.py55
-rw-r--r--ranger/config/rc.conf62
-rw-r--r--ranger/config/rifle.conf1
-rw-r--r--ranger/container/directory.py3
-rw-r--r--ranger/container/settings.py10
-rw-r--r--ranger/core/actions.py148
-rw-r--r--ranger/core/filter_stack.py134
-rw-r--r--ranger/core/fm.py12
-rw-r--r--ranger/core/main.py2
-rw-r--r--ranger/core/runner.py2
-rwxr-xr-xranger/data/scope.sh37
-rw-r--r--ranger/ext/img_display.py289
-rwxr-xr-xranger/ext/rifle.py2
-rw-r--r--ranger/gui/color.py5
-rw-r--r--ranger/gui/ui.py4
-rw-r--r--ranger/gui/widgets/browsercolumn.py24
-rw-r--r--ranger/gui/widgets/pager.py7
-rw-r--r--ranger/gui/widgets/titlebar.py5
-rw-r--r--ranger/gui/widgets/view_miller.py92
-rwxr-xr-xsetup.py2
36 files changed, 1455 insertions, 265 deletions
diff --git a/.travis.yml b/.travis.yml
index f9e31256..0efd09fc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,14 @@
-dist: 'trusty'
+dist: 'xenial'
 
 language: 'python'
 python:
-    - '2.7'
-    - '3.4'
-    - '3.5'
+  - '2.7'
+  - '3.4'
+  - '3.5'
 
 install:
-    - 'pip install pytest pylint flake8'
+  - 'pip install pipenv'
+  - 'pipenv update --dev'
 
 script:
-    - 'make test'
+  - 'make test'
diff --git a/Makefile b/Makefile
index 47e2fa01..79b4f9d4 100644
--- a/Makefile
+++ b/Makefile
@@ -7,9 +7,16 @@ NAME_RIFLE = rifle
 VERSION_RIFLE = $(VERSION)
 SNAPSHOT_NAME ?= $(NAME)-$(VERSION)-$(shell git rev-parse HEAD | cut -b 1-8).tar.gz
 # Find suitable python version (need python >= 2.6 or 3.1):
-PYTHON ?= $(shell python -c 'import sys; sys.exit(sys.version < "2.6")' && \
-	which python || which python3.3 || which python3.2 || which python3.1 || \
-	which python3 || which python2.7 || which python2.6)
+PYTHON ?= $(shell \
+	     (python -c 'import sys; sys.exit(sys.version < "2.6")' && \
+	      which python) \
+	     || (which python3) \
+	     || (python2 -c 'import sys; sys.exit(sys.version < "2.6")' && \
+	         which python2) \
+	   )
+ifeq ($(PYTHON),)
+  $(error No suitable python found.)
+endif
 SETUPOPTS ?= '--record=install_log.txt'
 DOCDIR ?= doc/pydoc
 DESTDIR ?= /
@@ -98,7 +105,11 @@ test_pytest:
 	@echo "Running py.test tests..."
 	py.test tests
 
-test: test_pylint test_flake8 test_doctest test_pytest
+test_other:
+	@echo "Checking completeness of man page..."
+	@tests/manpage_completion_test.py
+
+test: test_pylint test_flake8 test_doctest test_pytest test_other
 	@echo "Finished testing: All tests passed!"
 
 man:
@@ -122,4 +133,5 @@ todo:
 	@grep --color -Ion '\(TODO\|XXX\).*' -r ranger
 
 .PHONY: clean cleandoc compile default dist doc help install man manhtml \
-	options snapshot test test_pylint test_flake8 test_doctest test_pytest todo pypi_sdist
+	options snapshot test test_pylint test_flake8 test_doctest test_pytest \
+	test_other todo pypi_sdist
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 00000000..a927408c
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,15 @@
+[[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
new file mode 100644
index 00000000..be0d4a62
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,175 @@
+{
+    "_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 df17f731..e14bab46 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,7 @@ Optional:
 * The python module `chardet`, in case of encoding detection problems
 * `sudo` to use the "run as root"-feature
 * `w3m` for the `w3mimgdisplay` program to preview images
+* `python-bidi` for correct display of RTL file names (Hebrew, Arabic)
 
 Optional, for enhanced file previews (with `scope.sh`):
 
@@ -79,6 +80,7 @@ Optional, for enhanced file previews (with `scope.sh`):
 * `transmission-show` for viewing bit-torrent information
 * `mediainfo` or `exiftool` for viewing information about media files
 * `odt2txt` for OpenDocument text files (`odt`, `ods`, `odp` and `sxw`)
+* `chardet` (Python package) for improved encoding detection of text files
 
 
 Installing
diff --git a/doc/colorschemes.md b/doc/colorschemes.md
new file mode 100644
index 00000000..458cfc53
--- /dev/null
+++ b/doc/colorschemes.md
@@ -0,0 +1,140 @@
+Colorschemes
+============
+
+This text explains colorschemes and how they work.
+
+Context Tags
+------------
+
+Context tags provide information about the context and are Boolean values (`True`
+or `False`). For example, if the tag `in_titlebar` is set, you probably want to
+know about the color of a part of the titlebar now.
+
+The default context tags are specified in `/ranger/gui/context.py` in the
+constant `CONTEXT_KEYS`. Custom tags can be specified in a custom plugin file in
+`~/.config/ranger/plugins/`. The code to use follows.
+
+```python
+# Import the class
+import ranger.gui.context
+
+# Add your key names
+ranger.gui.context.CONTEXT_KEYS.append('my_key')
+
+# Set it to False (the default value)
+ranger.gui.context.Context.my_key = False
+
+# Or use an array for multiple names
+my_keys = ['key_one', 'key_two']
+ranger.gui.context.CONTEXT_KEYS.append(my_keys)
+
+# Set them to False
+for key in my_keys:
+    code = 'ranger.gui.context.Context.' + key + ' = False'
+    exec(code)
+```
+
+As you may or may not have guessed, this only tells ranger that they exist, not
+what they mean. To do this, you'll have to dig around in the source code. As an
+example, let's walk through adding a key that highlights `README.md` files
+differently. All the following code will be written in a standalone plugin file.
+
+First, from above, we'll add the key `readme` and set it to `False`.
+
+```python
+import ranger.gui.context
+
+ranger.gui.context.CONTEXT_KEYS.append('readme')
+ranger.gui.context.Context.readme = False
+```
+
+Then we'll use the hook `hook_before_drawing` to tell ranger that our key is
+talking about `README.md` files.
+
+```python
+import ranger.gui.widgets.browsercolumn
+
+OLD_HOOK_BEFORE_DRAWING = ranger.gui.widgets.browsercolumn.hook_before_drawing
+
+def new_hook_before_drawing(fsobject, color_list):
+    if fsobject.basename === 'README.md':
+        color_list.append('readme')
+
+    return OLD_HOOK_BEFORE_DRAWING(fsobject, color_list)
+
+ranger.gui.widgets.browsercolumn.hook_before_drawing = new_hook_before_drawing
+```
+
+Notice we call the old `hook_before_drawing`. This makes sure that we don't
+overwrite another plugin's code, we just append our own to it.
+
+To highlight it a different color, just [add it to your colorscheme][1]
+
+[1]:#adapt-a-colorscheme
+
+Implementation in the GUI Classes
+---------------------------------
+
+The class `CursesShortcuts` in the file `/ranger/gui/curses_shortcuts.py` defines
+the methods `color(*tags)`, `color_at(y, x, wid, *tags)` and `color_reset()`.
+This class is a superclass of `Displayable`, so these methods are available almost
+everywhere.
+
+Something like `color("in_titlebar", "directory")` will be called to get the
+color of directories in the titlebar. This creates a `ranger.gui.context.Context`
+object, sets its attributes `in_titlebar` and `directory` to True, leaves the
+others as `False`, and passes it to the colorscheme's `use(context)` method.
+
+The Color Scheme
+----------------
+
+A colorscheme should be a subclass of `ranger.gui.ColorScheme` and define the
+method `use(context)`. By looking at the context, this use-method has to
+determine a 3-tuple of integers: `(foreground, background, attribute)` and return
+it.
+
+`foreground` and `background` are integers representing colors, `attribute` is
+another integer with each bit representing one attribute. These integers are
+interpreted by the terminal emulator in use.
+
+Abbreviations for colors and attributes are defined in `ranger.gui.color`. Two
+attributes can be combined via bitwise OR: `bold | reverse`
+
+Once the color for a set of tags is determined, it will be cached by default. If
+you want more dynamic colorschemes (such as a different color for very large
+files), you will need to dig into the source code, perhaps add a custom tag and
+modify the draw-method of the widget to use that tag.
+
+Run `tc_colorscheme` to check if your colorschemes are valid.
+
+Specify a Colorscheme
+---------------------
+
+Colorschemes are searched for in these directories:
+
+- `~/.config/ranger/colorschemes/`
+- `/path/to/ranger/colorschemes/`
+
+To specify which colorscheme to use, change the option `colorscheme` in your
+rc.conf: `set colorscheme default`.
+
+This means, use the colorscheme contained in either
+`~/.config/ranger/colorschemes/default.py` or
+`/path/to/ranger/colorschemes/default.py`.
+
+Adapt a colorscheme
+-------------------
+
+You may want to adapt a colorscheme to your needs without having a complete copy
+of it, but rather the changes only. Say, you want the exact same colors as in
+the default colorscheme, but the directories to be green rather than blue,
+because you find the blue hard to read.
+
+This is done in the jungle colorscheme `ranger/colorschemes/jungle`, check it
+out for implementation details. In short, I made a subclass of the default
+scheme, set the initial colors to the result of the default `use()` method and
+modified the colors how I wanted.
+
+This has the obvious advantage that you need to write less, which results in
+less maintenance work and a greater chance that your colorscheme will work with
+future versions of ranger.
diff --git a/doc/colorschemes.txt b/doc/colorschemes.txt
deleted file mode 100644
index 145cc94e..00000000
--- a/doc/colorschemes.txt
+++ /dev/null
@@ -1,92 +0,0 @@
-Colorschemes
-============
-
-This text explains colorschemes and how they work.
-
-
-Context Tags
-------------
-
-Context Tags provide information about the context.  If the tag
-"in_titlebar" is set, you probably want to know about the color
-of a part of the titlebar now.
-
-There are a number of context tags, specified in /ranger/gui/context.py
-in the constant CONTEXT_KEYS.
-
-A Context object, defined in the same file, contains attributes with
-the names of all tags, whose values are either True or False.
-
-
-Implementation in the GUI Classes
----------------------------------
-
-The class CursesShortcuts in the file /ranger/gui/curses_shortcuts.py
-defines the methods color(*tags), color_at(y, x, wid, *tags) and
-color_reset().  This class is a superclass of Displayable, so these
-methods are available almost everywhere.
-
-Something like color("in_titlebar", "directory") will be called to
-get the color of directories in the titlebar.  This creates a
-ranger.gui.context.Context object, sets its attributes "in_titlebar" and
-"directory" to True, leaves the others as False, and passes it to the
-colorscheme's use(context) method.
-
-
-The Color Scheme
-----------------
-
-A colorscheme should be a subclass of ranger.gui.ColorScheme and
-define the method use(context).  By looking at the context, this use-method
-has to determine a 3-tuple of integers: (foreground, background, attribute)
-and return it.
-
-foreground and background are integers representing colors,
-attribute is another integer with each bit representing one attribute.
-These integers are interpreted by the used terminal emulator.
-
-Abbreviations for colors and attributes are defined in ranger.gui.color.
-Two attributes can be combined via bitwise OR: bold | reverse
-
-Once the color for a set of tags is determined, it will be cached by
-default.  If you want more dynamic colorschemes (such as a different
-color for very large files), you will need to dig into the source code,
-perhaps add an own tag and modify the draw-method of the widget to use
-that tag.
-
-Run tc_colorscheme to check if your colorschemes are valid.
-
-
-Specify a Colorscheme
----------------------
-
-Colorschemes are searched for in these directories:
-~/.config/ranger/colorschemes/
-/path/to/ranger/colorschemes/
-
-To specify which colorscheme to use, change the option "colorscheme"
-in your rc.conf:
-set colorscheme default
-
-This means, use the colorscheme contained in
-either ~/.config/ranger/colorschemes/default.py or
-/path/to/ranger/colorschemes/default.py.
-
-
-Adapt a colorscheme
--------------------
-
-You may want to adapt a colorscheme to your needs without having
-a complete copy of it, but rather the changes only.  Say, you
-want the exact same colors as in the default colorscheme, but
-the directories to be green rather than blue, because you find the
-blue hard to read.
-
-This is done in the jungle colorscheme ranger/colorschemes/jungle,
-check it out for implementation details.  In short, I made a subclass
-of the default scheme, set the initial colors to the result of the
-default use() method and modified the colors how I wanted.
-
-This has the obvious advantage that you need to write less, which
-results in less maintenance work and a greater chance that your colorscheme
-will work with future versions of ranger.
diff --git a/doc/howto-publish-a-release.md b/doc/howto-publish-a-release.md
index 572a2eb7..e8d669ce 100644
--- a/doc/howto-publish-a-release.md
+++ b/doc/howto-publish-a-release.md
@@ -31,7 +31,7 @@ Test everything
 Make a release commit
 ---------------------
 * [ ] Update the number in the `README`
-* [ ] Update `__version__` and `VERSION` in `ranger/__init__.py`
+* [ ] Update `__version__` and `__release__` in `ranger/__init__.py`
 * [ ] Update `__version__` in `ranger/ext/rifle.py`
 * [ ] `make man`
 * [ ] Write changelog entry
diff --git a/doc/ranger.1 b/doc/ranger.1
index 1290cb58..acf3f29a 100644
--- a/doc/ranger.1
+++ b/doc/ranger.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 "RANGER 1"
-.TH RANGER 1 "ranger-1.9.1" "2018-05-14" "ranger manual"
+.TH RANGER 1 "ranger-1.9.1" "2018-09-08" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -169,7 +173,7 @@ plugins, sample configuration files and some programs for integrating ranger
 with other software.  They are usually installed to
 \&\fI/usr/share/doc/ranger/examples\fR.
 .PP
-The man page of \fIrifle\fR\|(1) describes the functions of the file opener
+The man page of \fBrifle\fR\|(1) describes the functions of the file opener
 .PP
 The section \fI\s-1LINKS\s0\fR of this man page contains further resources.
 .SH "POSITIONAL ARGUMENTS"
@@ -307,6 +311,13 @@ This feature relies on the dimensions of the terminal's font.  By default, a
 width of 8 and height of 11 are used.  To use other values, set the options
 \&\f(CW\*(C`iterm2_font_width\*(C'\fR and \f(CW\*(C`iterm2_font_height\*(C'\fR to the desired values.
 .PP
+\fIterminology\fR
+.IX Subsection "terminology"
+.PP
+This only works in terminology. It can render vector graphics, but works only locally.
+.PP
+To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to terminology.
+.PP
 \fIurxvt\fR
 .IX Subsection "urxvt"
 .PP
@@ -324,6 +335,14 @@ The same as urxvt but utilizing not only the preview pane but the whole terminal
 window.
 .PP
 To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to urxvt-full.
+.PP
+\fIkitty\fR
+.IX Subsection "kitty"
+.PP
+This only works on Kitty. It requires \s-1PIL\s0 (or pillow) to work.
+Allows remote image previews, for example in an ssh session.
+.PP
+To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to kitty.
 .SS "\s-1SELECTION\s0"
 .IX Subsection "SELECTION"
 The \fIselection\fR is defined as \*(L"All marked files \s-1IF THERE ARE ANY,\s0 otherwise
@@ -624,6 +643,9 @@ to use to open the current file selection.
 .IP "cd" 14
 .IX Item "cd"
 Open the console with the content \*(L"cd \*(R"
+.IP "^P" 14
+.IX Item "^P"
+Open the console with the most recent command.
 .IP "Alt\-\fIN\fR" 14
 .IX Item "Alt-N"
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -642,6 +664,41 @@ Close the current tab.  The last tab cannot be closed this way.
 A key chain that allows you to quickly change the line mode of all the files of
 the current directory.  For a more permanent solution, use the command
 \&\*(L"default_linemode\*(R" in your rc.conf.
+.IP ".n" 14
+.IX Item ".n"
+Apply a new filename filter.
+.IP ".d" 14
+.IX Item ".d"
+Apply the typefilter \*(L"directory\*(R".
+.IP ".f" 14
+.IX Item ".f"
+Apply the typefilter \*(L"file\*(R".
+.IP ".l" 14
+.IX Item ".l"
+Apply the typefilter \*(L"symlink\*(R".
+.IP ".|" 14
+Combine the two topmost filters from the filter stack in the \*(L"\s-1OR\*(R"\s0
+relationship, instead of the \*(L"\s-1AND\*(R"\s0 used implicitly.
+.IP ".&" 14
+Explicitly combine the two topmost filters in the \*(L"\s-1AND\*(R"\s0
+relationship. Usually not needed though might be useful in more
+complicated scenarios.
+.IP ".!" 14
+Negate the topmost filter.
+.IP ".r" 14
+.IX Item ".r"
+Rotate the filter stack by N elements. Just confirm with enter to
+rotate by 1, i.e. move the topmost element to the bottom of the stack.
+.IP ".c" 14
+.IX Item ".c"
+Clear the filter stack.
+.IP ".*" 14
+Decompose the topmost filter combinator (e.g. \f(CW\*(C`.!\*(C'\fR, \f(CW\*(C`.|\*(C'\fR).
+.IP ".p" 14
+.IX Item ".p"
+Pop the topmost filter from the filter stack.
+.IP ".." 14
+Show the current filter stack state.
 .SS "READLINE-LIKE \s-1BINDINGS IN THE CONSOLE\s0"
 .IX Subsection "READLINE-LIKE BINDINGS IN THE CONSOLE"
 .IP "^B, ^F" 14
@@ -653,6 +710,12 @@ Move up and down (P for previous, N for Next)
 .IP "^A, ^E" 14
 .IX Item "^A, ^E"
 Move to the start or to the end
+.IP "Alt-B, Alt-LEFT" 14
+.IX Item "Alt-B, Alt-LEFT"
+Move backwards by words.
+.IP "Alt-F, Alt-RIGHT" 14
+.IX Item "Alt-F, Alt-RIGHT"
+Move forwards by words.
 .IP "^D" 14
 .IX Item "^D"
 Delete the current character.
@@ -675,7 +738,7 @@ scrolling to switch directories.
 .SH "SETTINGS"
 .IX Header "SETTINGS"
 This section lists all built-in settings of ranger.  The valid types for the
-value are in [brackets].  The hotkey to toggle the setting is in <brokets>, if
+value are in [brackets].  The hotkey to toggle the setting is in <brakets>, if
 a hotkey exists.
 .PP
 Settings can be changed in the file \fI~/.config/ranger/rc.conf\fR or on the
@@ -777,9 +840,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
@@ -893,6 +963,10 @@ to disable this feature.
 Which script should handle generating previews?  If the file doesn't exist, or
 use_preview_script is off, ranger will handle previews itself by just printing
 the content.
+.IP "relative_current_zero [bool]" 4
+.IX Item "relative_current_zero [bool]"
+When line_numbers is set to relative, show 0 on the current line if
+true or show the absolute number of the current line when false.
 .IP "save_backtick_bookmark [bool]" 4
 .IX Item "save_backtick_bookmark [bool]"
 Save the \f(CW\*(C`\`\*(C'\fR bookmark to disk.  This bookmark is used to switch to the last
@@ -955,6 +1029,11 @@ Abbreviate \f(CW$HOME\fR with ~ in the titlebar (first line) of ranger?
 .IP "unicode_ellipsis [bool]" 4
 .IX Item "unicode_ellipsis [bool]"
 Use a unicode \*(L"...\*(R" character instead of \*(L"~\*(R" to mark cut-off filenames?
+.IP "bidi_support [bool]" 4
+.IX Item "bidi_support [bool]"
+Try to properly display file names in \s-1RTL\s0 languages (Hebrew, Arabic) by using
+a \s-1BIDI\s0 algorithm to reverse the relevant parts of the text.
+Requires the python-bidi pip package.
 .IP "update_title [bool]" 4
 .IX Item "update_title [bool]"
 Set a window title?
@@ -981,6 +1060,10 @@ Sets the state for the version control backend. The possible values are:
 Sets the view mode, which can be \fBmiller\fR to display the files in the
 traditional miller column view that shows multiple levels of the hierarchy, or
 \&\fBmultipane\fR to use multiple panes (one per tab) similar to midnight-commander.
+.IP "w3m_delay [float]" 4
+.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 "wrap_scroll [bool]" 4
 .IX Item "wrap_scroll [bool]"
 Enable scroll wrapping \- moving down while on the last item will wrap around to
@@ -1569,7 +1652,7 @@ copy, run:
 .Ve
 .SH "SEE ALSO"
 .IX Header "SEE ALSO"
-\&\fIrifle\fR\|(1)
+\&\fBrifle\fR\|(1)
 .SH "BUGS"
 .IX Header "BUGS"
 Report bugs here: <https://github.com/ranger/ranger/issues>
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 4cac8ef9..4fac5d50 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -220,6 +220,12 @@ This feature relies on the dimensions of the terminal's font.  By default, a
 width of 8 and height of 11 are used.  To use other values, set the options
 C<iterm2_font_width> and C<iterm2_font_height> to the desired values.
 
+=head3 terminology
+
+This only works in terminology. It can render vector graphics, but works only locally.
+
+To enable this feature, set the option C<preview_images_method> to terminology.
+
 =head3 urxvt
 
 This only works in urxvt compiled with pixbuf support. Does not work over ssh.
@@ -236,6 +242,13 @@ window.
 
 To enable this feature, set the option C<preview_images_method> to urxvt-full.
 
+=head3 kitty
+
+This only works on Kitty. It requires PIL (or pillow) to work.
+Allows remote image previews, for example in an ssh session.
+
+To enable this feature, set the option C<preview_images_method> to kitty.
+
 =head2 SELECTION
 
 The I<selection> is defined as "All marked files IF THERE ARE ANY, otherwise
@@ -584,6 +597,10 @@ to use to open the current file selection.
 
 Open the console with the content "cd "
 
+=item ^P
+
+Open the console with the most recent command.
+
 =item Alt-I<N>
 
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -607,6 +624,58 @@ A key chain that allows you to quickly change the line mode of all the files of
 the current directory.  For a more permanent solution, use the command
 "default_linemode" in your rc.conf.
 
+=item .n
+
+Apply a new filename filter.
+
+=item .d
+
+Apply the typefilter "directory".
+
+=item .f
+
+Apply the typefilter "file".
+
+=item .l
+
+Apply the typefilter "symlink".
+
+=item .|
+
+Combine the two topmost filters from the filter stack in the "OR"
+relationship, instead of the "AND" used implicitly.
+
+=item .&
+
+Explicitly combine the two topmost filters in the "AND"
+relationship. Usually not needed though might be useful in more
+complicated scenarios.
+
+=item .!
+
+Negate the topmost filter.
+
+=item .r
+
+Rotate the filter stack by N elements. Just confirm with enter to
+rotate by 1, i.e. move the topmost element to the bottom of the stack.
+
+=item .c
+
+Clear the filter stack.
+
+=item .*
+
+Decompose the topmost filter combinator (e.g. C<.!>, C<.|>).
+
+=item .p
+
+Pop the topmost filter from the filter stack.
+
+=item ..
+
+Show the current filter stack state.
+
 =back
 
 =head2 READLINE-LIKE BINDINGS IN THE CONSOLE
@@ -625,6 +694,14 @@ Move up and down (P for previous, N for Next)
 
 Move to the start or to the end
 
+=item Alt-B, Alt-LEFT
+
+Move backwards by words.
+
+=item Alt-F, Alt-RIGHT
+
+Move forwards by words.
+
 =item ^D
 
 Delete the current character.
@@ -662,7 +739,7 @@ scrolling to switch directories.
 =head1 SETTINGS
 
 This section lists all built-in settings of ranger.  The valid types for the
-value are in [brackets].  The hotkey to toggle the setting is in <brokets>, if
+value are in [brackets].  The hotkey to toggle the setting is in <brakets>, if
 a hotkey exists.
 
 Settings can be changed in the file F<~/.config/ranger/rc.conf> or on the
@@ -775,10 +852,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
@@ -908,6 +990,11 @@ Which script should handle generating previews?  If the file doesn't exist, or
 use_preview_script is off, ranger will handle previews itself by just printing
 the content.
 
+=item relative_current_zero [bool]
+
+When line_numbers is set to relative, show 0 on the current line if
+true or show the absolute number of the current line when false.
+
 =item save_backtick_bookmark [bool]
 
 Save the C<`> bookmark to disk.  This bookmark is used to switch to the last
@@ -987,6 +1074,12 @@ Abbreviate $HOME with ~ in the titlebar (first line) of ranger?
 
 Use a unicode "..." character instead of "~" to mark cut-off filenames?
 
+=item bidi_support [bool]
+
+Try to properly display file names in RTL languages (Hebrew, Arabic) by using
+a BIDI algorithm to reverse the relevant parts of the text.
+Requires the python-bidi pip package.
+
 =item update_title [bool]
 
 Set a window title?
@@ -1017,6 +1110,11 @@ Sets the view mode, which can be B<miller> to display the files in the
 traditional miller column view that shows multiple levels of the hierarchy, or
 B<multipane> to use multiple panes (one per tab) similar to midnight-commander.
 
+=item w3m_delay [float]
+
+Delay in seconds before displaying an image with the w3m method.
+Increase it in case of experiencing display corruption.
+
 =item wrap_scroll [bool]
 
 Enable scroll wrapping - moving down while on the last item will wrap around to
diff --git a/doc/rifle.1 b/doc/rifle.1
index ad32e4f4..be605753 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
+.\" 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" "05.03.2018" "rifle manual"
+.TH RIFLE 1 "rifle-1.9.1" "2018-09-08" "rifle manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff --git a/examples/bash_automatic_cd.sh b/examples/bash_automatic_cd.sh
index bdac5757..04b58e24 100644
--- a/examples/bash_automatic_cd.sh
+++ b/examples/bash_automatic_cd.sh
@@ -6,12 +6,10 @@
 # the last visited one after ranger quits.
 # To undo the effect of this function, you can type "cd -" to return to the
 # original directory.
-# 
-# On OS X 10 or later, replace `usr/bin/ranger` with `/usr/local/bin/ranger`.
 
 function ranger-cd {
     tempfile="$(mktemp -t tmp.XXXXXX)"
-    /usr/bin/ranger --choosedir="$tempfile" "${@:-$(pwd)}"
+    ranger --choosedir="$tempfile" "${@:-$(pwd)}"
     test -f "$tempfile" &&
     if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then
         cd -- "$(cat "$tempfile")"
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 0d7c8fdc..ae1ecc48 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -12,9 +12,28 @@ from __future__ import (absolute_import, division, print_function)
 
 import os
 
+
+# Version helper
+def version_helper():
+    if __release__:
+        version_string = 'ranger {0}'.format(__version__)
+    else:
+        import subprocess
+        version_string = 'ranger-master {0}'
+        try:
+            git_describe = subprocess.check_output(['git', 'describe'],
+                                                   universal_newlines=True,
+                                                   stderr=subprocess.PIPE)
+            version_string = version_string.format(git_describe.strip('\n'))
+        except (OSError, subprocess.CalledProcessError):
+            version_string = version_string.format(__version__)
+    return version_string
+
+
 # Information
 __license__ = 'GPL3'
 __version__ = '1.9.1'
+__release__ = False
 __author__ = __maintainer__ = 'Roman Zimbelmann'
 __email__ = 'hut@hut.pm'
 
@@ -27,7 +46,7 @@ MACRO_DELIMITER = '%'
 MACRO_DELIMITER_ESC = '%%'
 DEFAULT_PAGER = 'less'
 USAGE = '%prog [options] [path]'
-VERSION = 'ranger-master {0}'.format(__version__)
+VERSION = version_helper()
 
 # These variables are ignored if the corresponding
 # XDG environment variable is non-empty and absolute
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/commands.py b/ranger/config/commands.py
index 7f5fc764..d177203a 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1545,6 +1545,61 @@ class filter_inode_type(Command):
         self.fm.thisdir.refilter()
 
 
+class filter_stack(Command):
+    """
+    :filter_stack ...
+
+    Manages the filter stack.
+
+        filter_stack add FILTER_TYPE ARGS...
+        filter_stack pop
+        filter_stack decompose
+        filter_stack rotate [N=1]
+        filter_stack clear
+        filter_stack show
+    """
+    def execute(self):
+        from ranger.core.filter_stack import SIMPLE_FILTERS, FILTER_COMBINATORS
+
+        subcommand = self.arg(1)
+
+        if subcommand == "add":
+            try:
+                self.fm.thisdir.filter_stack.append(
+                    SIMPLE_FILTERS[self.arg(2)](self.rest(3))
+                )
+            except KeyError:
+                FILTER_COMBINATORS[self.arg(2)](self.fm.thisdir.filter_stack)
+        elif subcommand == "pop":
+            self.fm.thisdir.filter_stack.pop()
+        elif subcommand == "decompose":
+            inner_filters = self.fm.thisdir.filter_stack.pop().decompose()
+            if inner_filters:
+                self.fm.thisdir.filter_stack.extend(inner_filters)
+        elif subcommand == "clear":
+            self.fm.thisdir.filter_stack = []
+        elif subcommand == "rotate":
+            rotate_by = int(self.arg(2) or 1)
+            self.fm.thisdir.filter_stack = (
+                self.fm.thisdir.filter_stack[-rotate_by:]
+                + self.fm.thisdir.filter_stack[:-rotate_by]
+            )
+        elif subcommand == "show":
+            stack = list(map(str, self.fm.thisdir.filter_stack))
+            pager = self.fm.ui.open_pager()
+            pager.set_source(["Filter stack: "] + stack)
+            pager.move(to=100, percentage=True)
+            return
+        else:
+            self.fm.notify(
+                "Unknown subcommand: {}".format(subcommand),
+                bad=True
+            )
+            return
+
+        self.fm.thisdir.refilter()
+
+
 class grep(Command):
     """:grep <string>
 
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 6b0802bb..4da1ffc0 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -86,6 +86,10 @@ set preview_images false
 #   width of 8 and height of 11 are used.  To use other values, set the options
 #   iterm2_font_width and iterm2_font_height to the desired values.
 #
+# * terminology:
+#   Previews images in full color in the terminology terminal emulator.
+#   Supports a wide variety of formats, even vector graphics like svg.
+#
 # * urxvt:
 #   Preview images in full color using urxvt image backgrounds. This
 #   requires using urxvt compiled with pixbuf support.
@@ -93,8 +97,21 @@ set preview_images false
 # * urxvt-full:
 #   The same as urxvt but utilizing not only the preview pane but the
 #   whole terminal window.
+#
+# * kitty:
+#   Preview images in full color using kitty image protocol.
+#   Requires python PIL or pillow library.
+#   If ranger does not share the local filesystem with kitty
+#   the transfer method is changed to encode the whole image;
+#   while slower, this allows remote previews,
+#   for example during an ssh session.
+#   Tmux is unsupported.
 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
+
 # Default iTerm2 font size (see: preview_images_method: iterm2)
 set iterm2_font_width 8
 set iterm2_font_height 11
@@ -102,6 +119,10 @@ set iterm2_font_height 11
 # Use a unicode "..." character to mark cut-off filenames?
 set unicode_ellipsis false
 
+# BIDI support - try to properly display file names in RTL languages (Hebrew, Arabic).
+# Requires the python-bidi pip package
+set bidi_support false
+
 # Show dotfiles in the bookmark preview box?
 set show_hidden_bookmarks true
 
@@ -125,8 +146,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
@@ -240,9 +264,14 @@ set metadata_deep_search false
 # Clear all existing filters when leaving a directory
 set clear_filters_on_dir_change false
 
-# Disable displaying line numbers in main column
+# Disable displaying line numbers in main column.
+# Possible values: false, absolute, relative.
 set line_numbers false
 
+# When line_numbers=relative show the absolute line number in the
+# current line.
+set relative_current_zero false
+
 # Start line numbers from 1 instead of 0
 set one_indexed false
 
@@ -323,6 +352,8 @@ map r  chain draw_possible_programs; console open_with%%space
 map f  console find%space
 map cd console cd%space
 
+map <C-p> chain console; eval fm.ui.console.history_move(-1)
+
 # Change the line mode
 map Mf linemode filename
 map Mi linemode fileinfo
@@ -386,6 +417,7 @@ map L     history_go 1
 map ]     move_parent 1
 map [     move_parent -1
 map }     traverse
+map {     traverse_backwards
 map )     jump_non
 
 map gh cd ~
@@ -488,6 +520,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!
@@ -530,6 +564,20 @@ map zv    set use_preview_script!
 map zf    console filter%space
 copymap zf zz
 
+# Filter stack
+map .n console filter_stack add name%space
+map .d filter_stack add type d
+map .f filter_stack add type f
+map .l filter_stack add type l
+map .| filter_stack add or
+map .& filter_stack add and
+map .! filter_stack add not
+map .r console filter_stack rotate
+map .c filter_stack clear
+map .* filter_stack decompose
+map .p filter_stack pop
+map .. filter_stack show
+
 # Bookmarks
 map `<any>  enter_bookmark %any
 map '<any>  enter_bookmark %any
@@ -574,8 +622,11 @@ cmap <left>  eval fm.ui.console.move(left=1)
 cmap <right> eval fm.ui.console.move(right=1)
 cmap <home>  eval fm.ui.console.move(right=0, absolute=True)
 cmap <end>   eval fm.ui.console.move(right=-1, absolute=True)
-cmap <a-left>   eval fm.ui.console.move_word(left=1)
-cmap <a-right>  eval fm.ui.console.move_word(right=1)
+cmap <a-b> eval fm.ui.console.move_word(left=1)
+cmap <a-f> eval fm.ui.console.move_word(right=1)
+
+copycmap <a-b> <a-left>
+copycmap <a-f> <a-right>
 
 # Line Editing
 cmap <backspace>  eval fm.ui.console.delete(-1)
@@ -587,6 +638,7 @@ cmap <C-u>        eval fm.ui.console.delete_rest(-1)
 cmap <C-y>        eval fm.ui.console.paste()
 
 # And of course the emacs way
+copycmap <ESC>       <C-g>
 copycmap <up>        <C-p>
 copycmap <down>      <C-n>
 copycmap <left>      <C-b>
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 83254a87..babdcda7 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -164,6 +164,7 @@ ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has ooffice,     X, f
 ext djvu, has zathura,X, flag f = zathura -- "$@"
 ext djvu, has evince, X, flag f = evince -- "$@"
 ext djvu, has atril,  X, flag f = atril -- "$@"
+ext djvu, has djview, X, flag f = djview -- "$@"
 
 ext epub, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
 ext mobi, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
diff --git a/ranger/container/directory.py b/ranger/container/directory.py
index e281e6c9..81dabb24 100644
--- a/ranger/container/directory.py
+++ b/ranger/container/directory.py
@@ -155,6 +155,8 @@ class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public
 
         self.marked_items = []
 
+        self.filter_stack = []
+
         self._signal_functions = []
         func = self.signal_function_factory(self.sort)
         self._signal_functions += [func]
@@ -297,6 +299,7 @@ class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public
         if self.temporary_filter:
             temporary_filter_search = self.temporary_filter.search
             filters.append(lambda fobj: temporary_filter_search(fobj.basename))
+        filters.extend(self.filter_stack)
 
         self.files = [f for f in self.files_all if accept_file(f, filters)]
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 170ace5a..70a12b51 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -27,6 +27,7 @@ ALLOWED_SETTINGS = {
     'automatically_count_files': bool,
     'autosave_bookmarks': bool,
     'autoupdate_cumulative_size': bool,
+    'bidi_support': bool,
     'cd_bookmarks': bool,
     'cd_tab_case': str,
     'cd_tab_fuzzy': bool,
@@ -40,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,
@@ -65,6 +66,7 @@ ALLOWED_SETTINGS = {
     'preview_images_method': str,
     'preview_max_size': int,
     'preview_script': (str, type(None)),
+    'relative_current_zero': bool,
     'save_backtick_bookmark': bool,
     'save_console_history': bool,
     'save_tabs_on_exit': bool,
@@ -91,6 +93,7 @@ ALLOWED_SETTINGS = {
     'vcs_backend_hg': str,
     'vcs_backend_svn': str,
     'viewmode': str,
+    'w3m_delay': float,
     'wrap_scroll': bool,
     'xterm_alt_key': bool,
 }
@@ -98,9 +101,11 @@ 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', 'urxvt', 'urxvt-full'],
+    'preview_images_method': ['w3m', 'iterm2', 'terminology',
+                              'urxvt', 'urxvt-full', 'kitty'],
     'vcs_backend_bzr': ['disabled', 'local', 'enabled'],
     'vcs_backend_git': ['enabled', 'disabled', 'local'],
     'vcs_backend_hg': ['disabled', 'local', 'enabled'],
@@ -113,6 +118,7 @@ DEFAULT_VALUES = {
     type(None): None,
     str: "",
     int: 0,
+    float: 0.0,
     list: [],
     tuple: tuple([]),
 }
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 3e488159..40fd52a3 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -110,7 +110,8 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         self.settings.set(option_name, self._parse_option_value(option_name, value),
                           localpath, tags)
 
-    def _parse_option_value(self, name, value):
+    def _parse_option_value(  # pylint: disable=too-many-return-statements
+            self, name, value):
         types = self.fm.settings.types_of(name)
         if bool in types:
             if value.lower() in ('false', 'off', '0'):
@@ -124,6 +125,11 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 return int(value)
             except ValueError:
                 pass
+        if float in types:
+            try:
+                return float(value)
+            except ValueError:
+                pass
         if str in types:
             return value
         if list in types:
@@ -234,10 +240,25 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         cmd = cmd_class(string, quantifier=quantifier)
 
         if cmd.resolve_macros and _MacroTemplate.delimiter in cmd.line:
-            macros = dict(('any%d' % i, key_to_string(char))
-                          for i, char in enumerate(wildcards if wildcards is not None else []))
+            def any_macro(i, char):
+                return ('any{:d}'.format(i), key_to_string(char))
+
+            def anypath_macro(i, char):
+                try:
+                    val = self.fm.bookmarks[key_to_string(char)]
+                except KeyError:
+                    self.notify('No bookmark defined for `{}`'.format(
+                        key_to_string(char)), bad=True)
+                    val = MACRO_FAIL
+                return ('any_path{:d}'.format(i), val)
+
+            macros = dict(f(i, char) for f in (any_macro, anypath_macro)
+                          for i, char in enumerate(wildcards if wildcards
+                                                   is not None else []))
             if 'any0' in macros:
                 macros['any'] = macros['any0']
+                if 'any_path0' in macros:
+                    macros['any_path'] = macros['any_path0']
             try:
                 line = self.substitute_macros(cmd.line, additional=macros,
                                               escape=cmd.escape_macros_for_shell)
@@ -401,7 +422,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
@@ -409,7 +430,8 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         # ranger can act as a file chooser when running with --choosefile=...
         if mode == 0 and 'label' not in kw:
             if ranger.args.choosefile:
-                open(ranger.args.choosefile, 'w').write(self.fm.thisfile.path)
+                with open(ranger.args.choosefile, 'w') as fobj:
+                    fobj.write(self.fm.thisfile.path)
 
             if ranger.args.choosefiles:
                 paths = []
@@ -482,8 +504,13 @@ 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 not self.thistab.enter_dir(tfile) and 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:
                 result = self.execute_file(selection, mode=mode)
                 if result in (False, ASK_COMMAND):
                     self.open_console('open_with ')
@@ -605,6 +632,23 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             self.move(down=1)
             self.traverse()
 
+    def traverse_backwards(self):
+        self.change_mode('normal')
+        if self.thisdir.pointer == 0:
+            self.move(left=1)
+            if self.thisdir.pointer != 0:
+                self.traverse_backwards()
+        else:
+            self.move(up=1)
+            while True:
+                if self.thisfile is not None and self.thisfile.is_directory:
+                    self.enter_dir(self.thisfile.path)
+                    self.move(to=100, percentage=True)
+                elif self.thisdir.pointer == 0:
+                    break
+                else:
+                    self.move(up=1)
+
     # --------------------------
     # -- Shortcuts / Wrappers
     # --------------------------
@@ -978,6 +1022,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
 
         if not self.settings.preview_script or not self.settings.use_preview_script:
             try:
+                # XXX: properly determine file's encoding
                 return codecs.open(path, 'r', errors='ignore')
             # IOError for Python2, OSError for Python3
             except (IOError, OSError):
@@ -1063,14 +1108,11 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 data[(-1, -1)] = None
                 data['foundpreview'] = False
             elif rcode == 2:
-                fobj = codecs.open(path, 'r', errors='ignore')
-                try:
-                    data[(-1, -1)] = fobj.read(1024 * 32)
-                except UnicodeDecodeError:
-                    fobj.close()
-                    fobj = codecs.open(path, 'r', encoding='latin-1', errors='ignore')
-                    data[(-1, -1)] = fobj.read(1024 * 32)
-                fobj.close()
+                text = self.read_text_file(path, 1024 * 32)
+                if not isinstance(text, str):
+                    # Convert 'unicode' to 'str' in Python 2
+                    text = text.encode('utf-8')
+                data[(-1, -1)] = text
             else:
                 data[(-1, -1)] = None
 
@@ -1111,6 +1153,35 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
 
         return None
 
+    @staticmethod
+    def read_text_file(path, count=None):
+        """Encoding-aware reading of a text file."""
+        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
+        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')
+
+        # latin-1 as the last resort
+        with codecs.open(path, 'r', encoding='latin-1', errors='replace') as fobj:
+            return fobj.read(count)
+
     # --------------------------
     # -- Tabs
     # --------------------------
@@ -1191,6 +1262,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
new file mode 100644
index 00000000..ff9f4080
--- /dev/null
+++ b/ranger/core/filter_stack.py
@@ -0,0 +1,134 @@
+# -*- 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: Wojciech Siewierski <wojciech.siewierski@onet.pl>, 2018
+
+from __future__ import (absolute_import, division, print_function)
+
+import re
+
+from ranger.container.directory import accept_file, InodeFilterConstants
+
+# pylint: disable=too-few-public-methods
+
+
+class BaseFilter(object):
+    def decompose(self):
+        return [self]
+
+
+SIMPLE_FILTERS = {}
+FILTER_COMBINATORS = {}
+
+
+def stack_filter(filter_name):
+    def decorator(cls):
+        SIMPLE_FILTERS[filter_name] = cls
+        return cls
+    return decorator
+
+
+def filter_combinator(combinator_name):
+    def decorator(cls):
+        FILTER_COMBINATORS[combinator_name] = cls
+        return cls
+    return decorator
+
+
+@stack_filter("name")
+class NameFilter(BaseFilter):
+    def __init__(self, pattern):
+        self.pattern = pattern
+        self.regex = re.compile(pattern)
+
+    def __call__(self, fobj):
+        return self.regex.search(fobj.relative_path)
+
+    def __str__(self):
+        return "<Filter: name =~ /{}/>".format(self.pattern)
+
+
+@stack_filter("type")
+class TypeFilter(BaseFilter):
+    type_to_function = {
+        InodeFilterConstants.DIRS:
+        (lambda fobj: fobj.is_directory),
+        InodeFilterConstants.FILES:
+        (lambda fobj: fobj.is_file and not fobj.is_link),
+        InodeFilterConstants.LINKS:
+        (lambda fobj: fobj.is_link),
+    }
+
+    def __init__(self, filetype):
+        if filetype not in self.type_to_function:
+            raise KeyError(filetype)
+        self.filetype = filetype
+
+    def __call__(self, fobj):
+        return self.type_to_function[self.filetype](fobj)
+
+    def __str__(self):
+        return "<Filter: type == '{}'>".format(self.filetype)
+
+
+@filter_combinator("or")
+class OrFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilters = [stack[-2], stack[-1]]
+
+        stack.pop()
+        stack.pop()
+
+        stack.append(self)
+
+    def __call__(self, fobj):
+        # Turn logical AND (accept_file()) into a logical OR with the
+        # De Morgan's laws.
+        return not accept_file(
+            fobj,
+            ((lambda x, f=filt: not f(x))
+             for filt
+             in self.subfilters),
+        )
+
+    def __str__(self):
+        return "<Filter: {}>".format(" or ".join(map(str, self.subfilters)))
+
+    def decompose(self):
+        return self.subfilters
+
+
+@filter_combinator("and")
+class AndFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilters = [stack[-2], stack[-1]]
+
+        stack.pop()
+        stack.pop()
+
+        stack.append(self)
+
+    def __call__(self, fobj):
+        return accept_file(fobj, self.subfilters)
+
+    def __str__(self):
+        return "<Filter: {}>".format(" and ".join(map(str, self.subfilters)))
+
+    def decompose(self):
+        return self.subfilters
+
+
+@filter_combinator("not")
+class NotFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilter = stack.pop()
+        stack.append(self)
+
+    def __call__(self, fobj):
+        return not self.subfilter(fobj)
+
+    def __str__(self):
+        return "<Filter: not {}>".format(str(self.subfilter))
+
+    def decompose(self):
+        return [self.subfilter]
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index d85dd48c..61b3cb11 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -24,7 +24,9 @@ from ranger.container.bookmarks import Bookmarks
 from ranger.core.runner import Runner
 from ranger.ext.img_display import (W3MImageDisplayer, ITerm2ImageDisplayer,
                                     TerminologyImageDisplayer,
-                                    URXVTImageDisplayer, URXVTImageFSDisplayer, ImageDisplayer)
+                                    URXVTImageDisplayer, URXVTImageFSDisplayer,
+                                    KittyImageDisplayer,
+                                    ImageDisplayer)
 from ranger.core.metadata import MetadataManager
 from ranger.ext.rifle import Rifle
 from ranger.container.directory import Directory
@@ -223,7 +225,7 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             for line in entry.splitlines():
                 yield line
 
-    def _get_image_displayer(self):
+    def _get_image_displayer(self):  # pylint: disable=too-many-return-statements
         if self.settings.preview_images_method == "w3m":
             return W3MImageDisplayer()
         elif self.settings.preview_images_method == "iterm2":
@@ -234,6 +236,8 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             return URXVTImageDisplayer()
         elif self.settings.preview_images_method == "urxvt-full":
             return URXVTImageFSDisplayer()
+        elif self.settings.preview_images_method == "kitty":
+            return KittyImageDisplayer()
         return ImageDisplayer()
 
     def _get_thisfile(self):
@@ -424,5 +428,5 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             if not ranger.args.clean and self.settings.save_tabs_on_exit and len(self.tabs) > 1:
                 with open(self.datapath('tabs'), 'a') as fobj:
                     # Don't save active tab since launching ranger changes the active tab
-                    fobj.write('\0'.join(v.path for t, v in self.tabs.items()) +
-                               '\0\0')
+                    fobj.write('\0'.join(v.path for t, v in self.tabs.items())
+                               + '\0\0')
diff --git a/ranger/core/main.py b/ranger/core/main.py
index b5d0af77..598ce243 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -363,7 +363,7 @@ def load_settings(  # pylint: disable=too-many-locals,too-many-branches,too-many
 
         # Load custom commands
         def import_file(name, path):  # From https://stackoverflow.com/a/67692
-            # pragma pylint: disable=no-name-in-module,import-error,no-member
+            # pragma pylint: disable=no-name-in-module,import-error,no-member, deprecated-method
             if sys.version_info >= (3, 5):
                 import importlib.util as util
                 spec = util.spec_from_file_location(name, path)
diff --git a/ranger/core/runner.py b/ranger/core/runner.py
index bb4e512a..f38b026a 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -235,7 +235,7 @@ class Runner(object):  # pylint: disable=too-few-public-methods
             self.fm.signal_emit('runner.execute.before',
                                 popen_kws=popen_kws, context=context)
             try:
-                if 'f' in context.flags:
+                if 'f' in context.flags and 'r' not in context.flags:
                     # This can fail and return False if os.fork() is not
                     # supported, but we assume it is, since curses is used.
                     Popen_forked(**popen_kws)
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 25251533..13a25b45 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -123,6 +123,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/img_display.py b/ranger/ext/img_display.py
index 78d71cb2..f78e170b 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -19,9 +19,13 @@ import imghdr
 import os
 import struct
 import sys
+import warnings
 from subprocess import Popen, PIPE
 
 import termios
+from contextlib import contextmanager
+import codecs
+from tempfile import NamedTemporaryFile
 
 from ranger.core.shared import FileManagerAware
 
@@ -35,6 +39,28 @@ W3MIMGDISPLAY_PATHS = [
     '/usr/local/libexec/w3m/w3mimgdisplay',
 ]
 
+# Helper functions shared between the previewers (make them static methods of the base class?)
+
+
+@contextmanager
+def temporarily_moved_cursor(to_y, to_x):
+    """Common boilerplate code to move the cursor to a drawing area. Use it as:
+        with temporarily_moved_cursor(dest_y, dest_x):
+            your_func_here()"""
+    curses.putp(curses.tigetstr("sc"))
+    move_cur(to_y, to_x)
+    yield
+    curses.putp(curses.tigetstr("rc"))
+    sys.stdout.flush()
+
+
+# this is excised since Terminology needs to move the cursor multiple times
+def move_cur(to_y, to_x):
+    tparm = curses.tparm(curses.tigetstr("cup"), to_y, to_x)
+    # on python2 stdout is already in binary mode, in python3 is accessed via buffer
+    bin_stdout = getattr(sys.stdout, 'buffer', sys.stdout)
+    bin_stdout.write(tparm)
+
 
 class ImageDisplayError(Exception):
     pass
@@ -60,7 +86,7 @@ class ImageDisplayer(object):
         pass
 
 
-class W3MImageDisplayer(ImageDisplayer):
+class W3MImageDisplayer(ImageDisplayer, FileManagerAware):
     """Implementation of ImageDisplayer using w3mimgdisplay, an utilitary
     program from w3m (a text-based web browser). w3mimgdisplay can display
     images either in virtual tty (using linux framebuffer) or in a Xorg session.
@@ -119,6 +145,14 @@ class W3MImageDisplayer(ImageDisplayer):
             input_gen = self._generate_w3m_input(path, start_x, start_y, width, height)
         except ImageDisplayError:
             raise
+
+        # Mitigate the issue with the horizontal black bars when
+        # selecting some images on some systems. 2 milliseconds seems
+        # enough. Adjust as necessary.
+        if self.fm.settings.w3m_delay > 0:
+            from time import sleep
+            sleep(self.fm.settings.w3m_delay)
+
         self.process.stdin.write(input_gen)
         self.process.stdin.flush()
         self.process.stdout.readline()
@@ -210,15 +244,8 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware):
     """
 
     def draw(self, path, start_x, start_y, width, height):
-        curses.putp(curses.tigetstr("sc"))
-        tparm = curses.tparm(curses.tigetstr("cup"), start_y, start_x)
-        if sys.version_info[0] < 3:
-            sys.stdout.write(tparm)
-        else:
-            sys.stdout.buffer.write(tparm)  # pylint: disable=no-member
-        sys.stdout.write(self._generate_iterm2_input(path, width, height))
-        curses.putp(curses.tigetstr("rc"))
-        sys.stdout.flush()
+        with temporarily_moved_cursor(start_y, start_x):
+            sys.stdout.write(self._generate_iterm2_input(path, width, height))
 
     def clear(self, start_x, start_y, width, height):
         self.fm.ui.win.redrawwin()
@@ -327,44 +354,23 @@ class TerminologyImageDisplayer(ImageDisplayer, FileManagerAware):
         self.close_protocol = "\000"
 
     def draw(self, path, start_x, start_y, width, height):
-        # Save cursor
-        curses.putp(curses.tigetstr("sc"))
-
-        y = start_y
-        # Move to drawing zone
-        self._move_to(start_x, y)
-
-        # Write intent
-        sys.stdout.write("%s}ic#%d;%d;%s%s" % (
-            self.display_protocol,
-            width, height,
-            path,
-            self.close_protocol))
-
-        # Write Replacement commands ('#')
-        for _ in range(0, height):
-            sys.stdout.write("%s}ib%s%s%s}ie%s" % (
-                self.display_protocol,
-                self.close_protocol,
-                "#" * width,
+        with temporarily_moved_cursor(start_y, start_x):
+            # Write intent
+            sys.stdout.write("%s}ic#%d;%d;%s%s" % (
                 self.display_protocol,
+                width, height,
+                path,
                 self.close_protocol))
-            y = y + 1
-            self._move_to(start_x, y)
-
-        # Restore cursor
-        curses.putp(curses.tigetstr("rc"))
-
-        sys.stdout.flush()
 
-    @staticmethod
-    def _move_to(x, y):
-        # curses.move(y, x)
-        tparm = curses.tparm(curses.tigetstr("cup"), y, x)
-        if sys.version_info[0] < 3:
-            sys.stdout.write(tparm)
-        else:
-            sys.stdout.buffer.write(tparm)  # pylint: disable=no-member
+            # Write Replacement commands ('#')
+            for y in range(0, height):
+                move_cur(start_y + y, start_x)
+                sys.stdout.write("%s}ib%s%s%s}ie%s\n" % (  # needs a newline to work
+                    self.display_protocol,
+                    self.close_protocol,
+                    "#" * width,
+                    self.display_protocol,
+                    self.close_protocol))
 
     def clear(self, start_x, start_y, width, height):
         self.fm.ui.win.redrawwin()
@@ -465,3 +471,196 @@ class URXVTImageFSDisplayer(URXVTImageDisplayer):
     def _get_offsets(self):
         """Center the image."""
         return self._get_centered_offsets()
+
+
+class KittyImageDisplayer(ImageDisplayer):
+    """Implementation of ImageDisplayer for kitty (https://github.com/kovidgoyal/kitty/)
+    terminal. It uses the built APC to send commands and data to kitty,
+    which in turn renders the image. The APC takes the form
+    '\033_Gk=v,k=v...;bbbbbbbbbbbbbb\033\\'
+       |   ---------- --------------  |
+    escape code  |             |    escape code
+                 |  base64 encoded payload
+        key: value pairs as parameters
+    For more info please head over to :
+        https://github.com/kovidgoyal/kitty/blob/master/graphics-protocol.asciidoc"""
+    protocol_start = b'\x1b_G'
+    protocol_end = b'\x1b\\'
+    # we are going to use stdio in binary mode a lot, so due to py2 -> py3
+    # differnces is worth to do this:
+    stdbout = getattr(sys.stdout, 'buffer', sys.stdout)
+    stdbin = getattr(sys.stdin, 'buffer', sys.stdin)
+    # counter for image ids on kitty's end
+    image_id = 0
+    # we need to find out the encoding for a path string, ascii won't cut it
+    try:
+        fsenc = sys.getfilesystemencoding()  # returns None if standard utf-8 is used
+        # throws LookupError if can't find the codec, TypeError if fsenc is None
+        codecs.lookup(fsenc)
+    except (LookupError, TypeError):
+        fsenc = 'utf-8'
+
+    def __init__(self):
+        # the rest of the initializations that require reading stdio or raising exceptions
+        # are delayed to the first draw call, since curses
+        # and ranger exception handler are not online at __init__() time
+        self.needs_late_init = True
+        # to init in _late_init()
+        self.backend = None
+        self.stream = None
+        self.pix_row, self.pix_col = (0, 0)
+
+    def _late_init(self):
+        # tmux
+        if 'kitty' not in os.environ['TERM']:
+            # this doesn't seem to work, ranger freezes...
+            # commenting out the response check does nothing
+            # self.protocol_start = b'\033Ptmux;\033' + self.protocol_start
+            # self.protocol_end += b'\033\\'
+            raise ImgDisplayUnsupportedException(
+                'kitty previews only work in'
+                + ' kitty and outside tmux. '
+                + 'Make sure your TERM contains the string "kitty"')
+
+        # automatic check if we share the filesystem using a dummy file
+        with NamedTemporaryFile() as tmpf:
+            tmpf.write(bytearray([0xFF] * 3))
+            tmpf.flush()
+            for cmd in self._format_cmd_str(
+                    {'a': 'q', 'i': 1, 'f': 24, 't': 'f', 's': 1, 'v': 1, 'S': 3},
+                    payload=base64.standard_b64encode(tmpf.name.encode(self.fsenc))):
+                self.stdbout.write(cmd)
+            sys.stdout.flush()
+            resp = b''
+            while resp[-2:] != self.protocol_end:
+                resp += self.stdbin.read(1)
+        # set the transfer method based on the response
+        # if resp.find(b'OK') != -1:
+        if b'OK' in resp:
+            self.stream = False
+        elif b'EBADF' in resp:
+            self.stream = True
+        else:
+            raise ImgDisplayUnsupportedException(
+                'kitty replied an unexpected response: {}'.format(resp))
+
+        # get the image manipulation backend
+        try:
+            # pillow is the default since we are not going
+            # to spawn other processes, so it _should_ be faster
+            import PIL.Image
+            self.backend = PIL.Image
+        except ImportError:
+            raise ImageDisplayError("Image previews in kitty require PIL (pillow)")
+            # TODO: implement a wrapper class for Imagemagick process to
+            # replicate the functionality we use from im
+
+        # get dimensions of a cell in pixels
+        ret = fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
+                          struct.pack('HHHH', 0, 0, 0, 0))
+        n_cols, n_rows, x_px_tot, y_px_tot = struct.unpack('HHHH', ret)
+        self.pix_row, self.pix_col = x_px_tot // n_rows, y_px_tot // n_cols
+        self.needs_late_init = False
+
+    def draw(self, path, start_x, start_y, width, height):
+        self.image_id += 1
+        # dictionary to store the command arguments for kitty
+        # a is the display command, with T going for immediate output
+        # i is the id entifier for the image
+        cmds = {'a': 'T', 'i': self.image_id}
+        # sys.stderr.write('{}-{}@{}x{}\t'.format(start_x, start_y, width, height))
+
+        # finish initialization if it is the first call
+        if self.needs_late_init:
+            self._late_init()
+
+        with warnings.catch_warnings(record=True):  # as warn:
+            warnings.simplefilter('ignore', self.backend.DecompressionBombWarning)
+            image = self.backend.open(path)
+            # TODO: find a way to send a message to the user that
+            # doesn't stop the image from displaying
+            # if warn:
+            #     raise ImageDisplayError(str(warn[-1].message))
+        box = (width * self.pix_row, height * self.pix_col)
+
+        if image.width > box[0] or image.height > box[1]:
+            scale = min(box[0] / image.width, box[1] / image.height)
+            image = image.resize((int(scale * image.width), int(scale * image.height)),
+                                 self.backend.LANCZOS)
+
+        # 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
+            # c, r: size in cells of the viewbox
+            cmds.update({'t': 'd', 'f': len(image.getbands()) * 8,
+                         's': image.width, 'v': image.height, })
+            payload = base64.standard_b64encode(
+                bytearray().join(map(bytes, image.getdata())))
+        else:
+            # put the image in a temporary png file
+            # t: transmissium medium, 't' for temporary file (kitty will delete it for us)
+            # f: size of a pixel fragment (100 just mean that the file is png encoded,
+            #       the only format except raw RGB(A) bitmap that kitty understand)
+            # c, r: size in cells of the viewbox
+            cmds.update({'t': 't', 'f': 100, })
+            with NamedTemporaryFile(prefix='ranger_thumb_', suffix='.png', delete=False) as tmpf:
+                image.save(tmpf, format='png', compress_level=0)
+                payload = base64.standard_b64encode(tmpf.name.encode(self.fsenc))
+
+        with temporarily_moved_cursor(int(start_y), int(start_x)):
+            for cmd_str in self._format_cmd_str(cmds, payload=payload):
+                self.stdbout.write(cmd_str)
+        # catch kitty answer before the escape codes corrupt the console
+        resp = b''
+        while resp[-2:] != self.protocol_end:
+            resp += self.stdbin.read(1)
+        if b'OK' in resp:
+            return
+        else:
+            raise ImageDisplayError('kitty replied "{}"'.format(resp))
+
+    def clear(self, start_x, start_y, width, height):
+        # let's assume that every time ranger call this
+        # it actually wants just to remove the previous image
+        # TODO: implement this using the actual x, y, since the protocol supports it
+        cmds = {'a': 'd', 'i': self.image_id}
+        for cmd_str in self._format_cmd_str(cmds):
+            self.stdbout.write(cmd_str)
+        self.stdbout.flush()
+        # kitty doesn't seem to reply on deletes, checking like we do in draw()
+        # will slows down scrolling with timeouts from select
+        self.image_id -= 1
+
+    def _format_cmd_str(self, cmd, payload=None, max_slice_len=2048):
+        central_blk = ','.join(["{}={}".format(k, v) for k, v in cmd.items()]).encode('ascii')
+        if payload is not None:
+            # we add the m key to signal a multiframe communication
+            # appending the end (m=0) key to a single message has no effect
+            while len(payload) > max_slice_len:
+                payload_blk, payload = payload[:max_slice_len], payload[max_slice_len:]
+                yield self.protocol_start + \
+                    central_blk + b',m=1;' + payload_blk + \
+                    self.protocol_end
+            yield self.protocol_start + \
+                central_blk + b',m=0;' + payload + \
+                self.protocol_end
+        else:
+            yield self.protocol_start + central_blk + b';' + self.protocol_end
+
+    def quit(self):
+        # clear all remaining images, then check if all files went through or are orphaned
+        while self.image_id >= 1:
+            self.clear(0, 0, 0, 0)
+        # for k in self.temp_paths:
+        #     try:
+        #         os.remove(self.temp_paths[k])
+        #     except (OSError, IOError):
+        #         continue
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 6106656e..6832e2cf 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -357,7 +357,7 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
             self.hook_before_executing(command, self._mimetype, self._app_flags)
             try:
                 if 'r' in flags:
-                    prefix = ['sudo', '-E', 'su', '-mc']
+                    prefix = ['sudo', '-E', 'su', 'root', '-mc']
                 else:
                     prefix = ['/bin/sh', '-c']
 
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 4f76dfab..441e9032 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -365,7 +365,9 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
         DisplayableContainer.draw(self)
         if self._draw_title and self.settings.update_title:
             cwd = self.fm.thisdir.path
-            if cwd.startswith(self.fm.home_path):
+            if self.settings.tilde_in_titlebar \
+               and (cwd == self.fm.home_path
+                    or cwd.startswith(self.fm.home_path + "/")):
                 cwd = '~' + cwd[len(self.fm.home_path):]
             if self.settings.shorten_title:
                 split = cwd.rsplit(os.sep, self.settings.shorten_title)
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index cf322409..bc6f7b1b 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -10,6 +10,12 @@ import stat
 from time import time
 from os.path import splitext
 
+try:
+    from bidi.algorithm import get_display  # pylint: disable=import-error
+    HAVE_BIDI = True
+except ImportError:
+    HAVE_BIDI = False
+
 from ranger.ext.widestring import WideString
 from ranger.core import linemode
 
@@ -183,12 +189,12 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
     def _draw_file(self):
         """Draw a preview of the file, if the settings allow it"""
         self.win.move(0, 0)
-        if not self.target.accessible:
-            self.addnstr("not accessible", self.wid)
+        if self.target is None or not self.target.has_preview():
             Pager.close(self)
             return
 
-        if self.target is None or not self.target.has_preview():
+        if not self.target.accessible:
+            self.addnstr("not accessible", self.wid)
             Pager.close(self)
             return
 
@@ -206,7 +212,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
         line_number = i
         if self.settings.line_numbers == 'relative':
             line_number = abs(selected_i - i)
-            if line_number == 0:
+            if not self.settings.relative_current_zero and line_number == 0:
                 if self.settings.one_indexed:
                     line_number = selected_i + 1
                 else:
@@ -410,9 +416,15 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
     def _total_len(predisplay):
         return sum([len(WideString(s)) for s, _ in predisplay])
 
+    def _bidi_transpose(self, text):
+        if self.settings.bidi_support and HAVE_BIDI:
+            return get_display(text)
+        return text
+
     def _draw_text_display(self, text, space):
-        wtext = WideString(text)
-        wext = WideString(splitext(text)[1])
+        bidi_text = self._bidi_transpose(text)
+        wtext = WideString(bidi_text)
+        wext = WideString(splitext(bidi_text)[1])
         wellip = WideString(self.ellipsis[self.settings.unicode_ellipsis])
         if len(wtext) > space:
             wtext = wtext[:max(1, space - len(wext) - len(wellip))] + wellip + wext
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index 42adf1e9..d64d4ac1 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -109,8 +109,9 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
             try:
                 self.fm.image_displayer.draw(self.image, self.x, self.y,
                                              self.wid, self.hei)
-            except ImgDisplayUnsupportedException:
+            except ImgDisplayUnsupportedException as ex:
                 self.fm.settings.preview_images = False
+                self.fm.notify(ex, bad=True)
             except Exception as ex:  # pylint: disable=broad-except
                 self.fm.notify(ex, bad=True)
             else:
@@ -233,7 +234,7 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
     def _generate_lines(self, starty, startx):
         i = starty
         if not self.source:
-            raise StopIteration
+            return
         while True:
             try:
                 line = self._get_line(i).expandtabs(4)
@@ -243,5 +244,5 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
                     line = line[startx:self.wid + startx]
                 yield line.rstrip().replace('\r\n', '\n')
             except IndexError:
-                raise StopIteration
+                return
             i += 1
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index 042b4b04..765c1248 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -102,8 +102,9 @@ class TitleBar(Widget):
             bar.add(' ', 'hostname', clr, fixed=True)
 
         pathway = self.fm.thistab.pathway
-        if self.settings.tilde_in_titlebar and \
-                self.fm.thisdir.path.startswith(self.fm.home_path):
+        if self.settings.tilde_in_titlebar \
+           and (self.fm.thisdir.path.startswith(self.fm.home_path + "/")
+                or self.fm.thisdir.path == self.fm.home_path):
             pathway = pathway[self.fm.home_path.count('/') + 1:]
             bar.add('~/', 'directory', fixed=True)
 
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/setup.py b/setup.py
index d7c54b00..edf48c4a 100755
--- a/setup.py
+++ b/setup.py
@@ -111,7 +111,7 @@ def main():
                 'doc/rifle.1',
             ]),
             ('share/doc/ranger', [
-                'doc/colorschemes.txt',
+                'doc/colorschemes.md',
                 'CHANGELOG.md',
                 'HACKING.md',
                 'README.md',