summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorToon Nolten <toonn@toonn.io>2018-09-09 18:38:54 +0200
committerToon Nolten <toonn@toonn.io>2018-09-09 18:38:54 +0200
commitae1ef141b5dedf1296a8c163e5a1f9f5ec6f4f9e (patch)
tree0a78dbf70d4a33c8b32e406ba28d485b892e41f1
parentd2b3c17578b70b1ab0bdbc7eb70046fa2e17a434 (diff)
parent2485dd705e84fb64e3f04f5b4f6956968a5b9708 (diff)
downloadranger-ae1ef141b5dedf1296a8c163e5a1f9f5ec6f4f9e.tar.gz
Merge branch 'master' into stable
Preparing for release 1.9.2
-rw-r--r--.flake82
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml13
-rw-r--r--Dockerfile8
-rw-r--r--Makefile22
-rw-r--r--Pipfile15
-rw-r--r--Pipfile.lock175
-rw-r--r--README.md4
-rw-r--r--doc/colorschemes.md140
-rw-r--r--doc/colorschemes.txt92
-rw-r--r--doc/howto-publish-a-release.md36
-rw-r--r--doc/ranger.1160
-rw-r--r--doc/ranger.pod163
-rw-r--r--doc/rifle.122
-rw-r--r--examples/bash_automatic_cd.sh4
-rw-r--r--examples/plugin_avfs.py33
-rw-r--r--examples/plugin_pmount_dynamic.py70
-rw-r--r--examples/rc_emacs.conf5
-rw-r--r--ranger/__init__.py21
-rw-r--r--ranger/colorschemes/default.py27
-rw-r--r--ranger/colorschemes/snow.py4
-rw-r--r--ranger/config/__init__.py2
-rwxr-xr-xranger/config/commands.py81
-rw-r--r--ranger/config/rc.conf89
-rw-r--r--ranger/config/rifle.conf32
-rw-r--r--ranger/container/directory.py8
-rw-r--r--ranger/container/fsobject.py6
-rw-r--r--ranger/container/settings.py12
-rw-r--r--ranger/core/actions.py177
-rw-r--r--ranger/core/filter_stack.py151
-rw-r--r--ranger/core/fm.py12
-rw-r--r--ranger/core/main.py80
-rw-r--r--ranger/core/runner.py2
-rwxr-xr-xranger/data/scope.sh40
-rw-r--r--ranger/ext/direction.py4
-rw-r--r--ranger/ext/human_readable.py4
-rw-r--r--ranger/ext/img_display.py305
-rwxr-xr-xranger/ext/rifle.py93
-rw-r--r--ranger/gui/color.py5
-rw-r--r--ranger/gui/context.py2
-rw-r--r--ranger/gui/curses_shortcuts.py4
-rw-r--r--ranger/gui/ui.py10
-rw-r--r--ranger/gui/widgets/__init__.py8
-rw-r--r--ranger/gui/widgets/browsercolumn.py28
-rw-r--r--ranger/gui/widgets/pager.py7
-rw-r--r--ranger/gui/widgets/statusbar.py15
-rw-r--r--ranger/gui/widgets/titlebar.py5
-rw-r--r--ranger/gui/widgets/view_base.py70
-rw-r--r--ranger/gui/widgets/view_miller.py92
-rwxr-xr-xsetup.py2
-rw-r--r--tests/ranger/core/__init__.py0
-rw-r--r--tests/ranger/core/test_main.py18
52 files changed, 1984 insertions, 399 deletions
diff --git a/.flake8 b/.flake8
index f5072c08..b77e4e6c 100644
--- a/.flake8
+++ b/.flake8
@@ -1,3 +1,3 @@
 [flake8]
 max-line-length = 99
-ignore = E221
+ignore = E221,W503
diff --git a/.gitignore b/.gitignore
index 88c75b90..73ca85e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,6 @@
 /ranger_fm.egg-info
 
 /stuff/*
+
+.idea
+.pytest_cache
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/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..36ad0a95
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+# Usage instructions:
+# 1. "docker build -t ranger/ranger:latest ."
+# 2. "docker run -it ranger/ranger"
+
+FROM debian
+
+RUN apt-get update && apt-get install -y ranger
+ENTRYPOINT ["ranger"]
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 ef644ae6..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`):
 
@@ -75,10 +76,11 @@ Optional, for enhanced file previews (with `scope.sh`):
 * `highlight` or `pygmentize` for syntax highlighting of code
 * `atool`, `bsdtar` and/or `unrar` for previews of archives
 * `lynx`, `w3m` or `elinks` for previews of html pages
-* `pdftotext` for pdf previews
+* `pdftotext` or `mutool` for pdf previews
 * `transmission-show` for viewing bit-torrent information
 * `mediainfo` or `exiftool` for viewing information about media files
 * `odt2txt` for OpenDocument text files (`odt`, `ods`, `odp` and `sxw`)
+* `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 9b6e1dfe..e8d669ce 100644
--- a/doc/howto-publish-a-release.md
+++ b/doc/howto-publish-a-release.md
@@ -1,3 +1,26 @@
+Prepare the "stable" branch
+---------------------------
+Before you can do anything else, you need to decide what should be included in
+the new version.
+
+**Bugfix releases** bump the third number of the version, e.g. 1.9.0 -> 1.9.1.
+They may include bugfix commits that you `git cherry-pick`ed from the master
+branch into the stable branch, or you can just do a fast-forward merge of
+master into stable, if there were only bugfix commits since the last major
+version.  You can also add minor new features that are very likely not causing
+any bugs.  However, there should be absolutely **no** backward-incompatible
+changes, like:
+
+- renamed or removed settings, commands or python functions
+- renamed, removed or reordered function arguments
+- change in syntax of configuration files or in API of configuration scripts
+
+New settings are okay, just make sure a sane default value is defined.
+
+**Major releases** bump the second number of the version, e.g. 1.9.2 -> 1.10.0
+and are necessary if you introduce any breaking changes, like the ones
+mentioned in the list above.
+
 Test everything
 ----------------
 * [ ] `make test`
@@ -8,12 +31,14 @@ 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
 * [ ] Think of a witty commit message
-* [ ] Tag signed release
+* [ ] Commit
+* [ ] Tag the signed release with `git tag -a <commit-id>`, using the same
+      commit message as annotation
 * [ ] Push release and tag
 
 Make snapshot and test again
@@ -28,9 +53,11 @@ Update the website
 * [ ] Add the new version as `ranger-stable.tar.gz`
 * [ ] Add the new version as `ranger-X.Y.Z.tar.gz`
 * [ ] Update both signatures `gpg --local-user 0x00FB5CDF --sign --detach-sign <file>`
-* [ ] Update the changelog
 * [ ] Update the man page
-* [ ] Rerun `boobies.py`
+    * [ ] run `make manhtml` in ranger's repository
+    * [ ] copy the generated `doc/ranger.1.html` to the `ranger.github.io` repository
+* [ ] Rebuild the website, see `README.md` in https://github.com/ranger/ranger.github.io
+* [ ] Commit & push the website
 
 Make a PyPI release
 -------------------
@@ -43,7 +70,6 @@ Announce the update
 -------------------
 * [ ] To the mailing list
 * [ ] In the arch linux forum
-* [ ] Write a news entry on savannah
 
 Change back to before
 ---------------------
diff --git a/doc/ranger.1 b/doc/ranger.1
index fca30604..acf3f29a 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
+.\" Automatically generated by Pod::Man 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" "02/22/2018" "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
@@ -145,7 +149,7 @@ ranger \- visual file manager
 [\fB\-\-choosedir\fR=\fItarget\fR] [\fB\-\-selectfile\fR=\fIfilepath\fR]
 [\fB\-\-show\-only\-dirs\fR]
 [\fB\-\-list\-unused\-keys\fR] [\fB\-\-list\-tagged\-files\fR=\fItag\fR]
-[\fB\-\-profile\fR] [\fB\-\-cmd\fR=\fIcommand\fR] [\fIpath\fR]
+[\fB\-\-profile\fR] [\fB\-\-cmd\fR=\fIcommand\fR] [\fIpath ...\fR]
 .SH "DESCRIPTION"
 .IX Header "DESCRIPTION"
 ranger is a console file manager with \s-1VI\s0 key bindings.
@@ -169,9 +173,15 @@ 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"
+.IX Header "POSITIONAL ARGUMENTS"
+.IP "\fIpath ...\fR" 14
+.IX Item "path ..."
+Each path will be opened in a tab and if the path is a file it will be selected.
+Omitting this is equivalent to providing the current directory.
 .SH "OPTIONS"
 .IX Header "OPTIONS"
 .IP "\fB\-d\fR, \fB\-\-debug\fR" 14
@@ -225,7 +235,8 @@ Allows you to pick a directory with ranger.  When you exit ranger, it will
 write the last visited directory into \fItargetfile\fR.
 .IP "\fB\-\-selectfile\fR=\fItargetfile\fR" 14
 .IX Item "--selectfile=targetfile"
-Open ranger with \fItargetfile\fR selected.
+Open ranger with \fItargetfile\fR selected. This is a legacy option, superseded by
+the behavior for the \s-1POSITIONAL ARGUMENTS.\s0
 .IP "\fB\-\-show\-only\-dirs\fR" 14
 .IX Item "--show-only-dirs"
 Display only the directories. May be used in conjunction with
@@ -267,10 +278,10 @@ typing \fI"<tagname>\fR.
 By default, only text files are previewed, but you can enable external preview
 scripts by setting the option \f(CW\*(C`use_preview_script\*(C'\fR and \f(CW\*(C`preview_files\*(C'\fR to true.
 .PP
-This default script is \fI~/.config/ranger/scope.sh\fR. It contains more
+This default script is \fI\f(CI%rangerdir\fI/data/scope.sh\fR. It contains more
 documentation and calls to the programs \fIlynx\fR and \fIelinks\fR for html,
 \&\fIhighlight\fR for text/code, \fIimg2txt\fR for images, \fIatool\fR for archives,
-\&\fIpdftotext\fR for PDFs and \fImediainfo\fR for video and audio files.
+\&\fIpdftotext\fR or \fImutool\fR for PDFs and \fImediainfo\fR for video and audio files.
 .PP
 Install these programs (just the ones you need) and scope.sh will automatically
 use them.
@@ -300,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
@@ -317,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
@@ -463,7 +489,7 @@ sample plugins in the \fI/usr/share/doc/ranger/examples/\fR directory, including
 hello-world plugin that describes this procedure.
 .SH "KEY BINDINGS"
 .IX Header "KEY BINDINGS"
-Key bindings are defined in the file \fIranger/config/rc.conf\fR.  Check this
+Key bindings are defined in the file \fI\f(CI%rangerdir\fI/config/rc.conf\fR.  Check this
 file for a list of all key bindings.  You can copy it to your local
 configuration directory with the \-\-copy\-config=rc option.
 .PP
@@ -599,7 +625,7 @@ Toggle the mark-status of all files
 .IP "V" 14
 .IX Item "V"
 Starts the visual mode, which selects all files between the starting point and
-the cursor until you press \s-1ESC. \s0 To unselect files in the same way, use \*(L"uV\*(R".
+the cursor until you press \s-1ESC.\s0  To unselect files in the same way, use \*(L"uV\*(R".
 .IP "/" 14
 Search for files in the current directory.
 .IP ":" 14
@@ -617,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
@@ -635,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
@@ -646,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.
@@ -668,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
@@ -702,7 +772,7 @@ in ranger.
 .IP "automatically_count_files [bool]" 4
 .IX Item "automatically_count_files [bool]"
 Should ranger count and display the number of files in each directory
-as soon as it's visible?  This gets slow with remote file sytems.  Turning it
+as soon as it's visible?  This gets slow with remote file systems.  Turning it
 off will still allow you to see the number of files after entering the
 directory.
 .IP "autosave_bookmarks [bool]" 4
@@ -764,12 +834,22 @@ Display the file size in the main column?
 .IP "display_size_in_status_bar [bool]" 4
 .IX Item "display_size_in_status_bar [bool]"
 Display the file size in the status bar?
+.IP "display_free_space_in_status_bar [bool]" 4
+.IX Item "display_free_space_in_status_bar [bool]"
+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
@@ -800,6 +880,13 @@ this pattern will hide all files that start with a dot or end with a tilde.
 .Vb 1
 \& set hidden_filter ^\e.|~$
 .Ve
+.IP "hint_collapse_threshold [int]" 4
+.IX Item "hint_collapse_threshold [int]"
+The key hint lists up to this size have their sublists expanded.
+Otherwise the submaps are replaced with \*(L"...\*(R".
+.IP "hostname_in_titlebar [bool]" 4
+.IX Item "hostname_in_titlebar [bool]"
+Show hostname in titlebar?
 .IP "idle_delay [integer]" 4
 .IX Item "idle_delay [integer]"
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -876,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
@@ -932,15 +1023,17 @@ combination, e.g. \*(L"oN\*(R" to sort from Z to A.
 .IP "status_bar_on_top [bool]" 4
 .IX Item "status_bar_on_top [bool]"
 Put the status bar at the top of the window?
-.IP "hostname_in_titlebar [bool]" 4
-.IX Item "hostname_in_titlebar [bool]"
-Show hostname in titlebar?
 .IP "tilde_in_titlebar [bool]" 4
 .IX Item "tilde_in_titlebar [bool]"
 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?
@@ -967,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
@@ -985,7 +1082,7 @@ ranger.  For your convenience, this is a list of the \*(L"public\*(R" commands i
 .Vb 10
 \& alias [newcommand] [oldcommand]
 \& bulkrename
-\& cd [directory]
+\& cd [path]
 \& chain command1[; command2[; command3...]]
 \& chmod octal_number
 \& cmap key command
@@ -1061,10 +1158,10 @@ renaming according to the changes you did in the file.
 .Sp
 This shell script is opened in an editor for you to review.  After you close
 it, it will be executed.
-.IP "cd [\fIdirectory\fR]" 2
-.IX Item "cd [directory]"
-The cd command changes the directory.  The command \f(CW\*(C`:cd \-\*(C'\fR is equivalent to
-typing ``.
+.IP "cd [\fIpath\fR]" 2
+.IX Item "cd [path]"
+The cd command changes the directory.  If path is a file, selects that file.
+The command \f(CW\*(C`:cd \-\*(C'\fR is equivalent to typing ``.
 .IP "chain \fIcommand1\fR[; \fIcommand2\fR[; \fIcommand3\fR...]]" 2
 .IX Item "chain command1[; command2[; command3...]]"
 Combines multiple commands into one, separated by semicolons.
@@ -1421,6 +1518,9 @@ being bound despite the corresponding line being removed from the user's copy of
 the configuration file. This behavior may be disabled with an environment
 variable (see also: \fB\s-1ENVIRONMENT\s0\fR). Note: All other configuration files only
 read from one source; i.e. default \s-1OR\s0 user, not both.
+\&\fIrc.conf\fR and \fIcommands.py\fR are additionally read from \fI/etc/ranger\fR if they
+exist for system-wide configuration, user configuration overrides system
+configuration which overrides the default configuration.
 .PP
 When starting ranger with the \fB\-\-clean\fR option, it will not access or create
 any of these files.
@@ -1552,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 523d8d9d..772107e0 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -14,7 +14,7 @@ B<ranger> [B<--version>] [B<--help>] [B<--debug>] [B<--clean>]
 [B<--choosedir>=I<target>] [B<--selectfile>=I<filepath>]
 [B<--show-only-dirs>]
 [B<--list-unused-keys>] [B<--list-tagged-files>=I<tag>]
-[B<--profile>] [B<--cmd>=I<command>] [I<path>]
+[B<--profile>] [B<--cmd>=I<command>] [I<path ...>]
 
 
 
@@ -53,6 +53,20 @@ The section I<LINKS> of this man page contains further resources.
 
 
 
+=head1 POSITIONAL ARGUMENTS
+
+=over 14
+
+=item I<path ...>
+
+Each path will be opened in a tab and if the path is a file it will be selected.
+Omitting this is equivalent to providing the current directory.
+
+=back
+
+
+
+
 =head1 OPTIONS
 
 =over 14
@@ -117,7 +131,8 @@ write the last visited directory into I<targetfile>.
 
 =item B<--selectfile>=I<targetfile>
 
-Open ranger with I<targetfile> selected.
+Open ranger with I<targetfile> selected. This is a legacy option, superseded by
+the behavior for the POSITIONAL ARGUMENTS.
 
 =item B<--show-only-dirs>
 
@@ -174,10 +189,10 @@ typing I<"<tagnameE<gt>>.
 By default, only text files are previewed, but you can enable external preview
 scripts by setting the option C<use_preview_script> and C<preview_files> to true.
 
-This default script is F<~/.config/ranger/scope.sh>. It contains more
+This default script is F<%rangerdir/data/scope.sh>. It contains more
 documentation and calls to the programs I<lynx> and I<elinks> for html,
 I<highlight> for text/code, I<img2txt> for images, I<atool> for archives,
-I<pdftotext> for PDFs and I<mediainfo> for video and audio files.
+I<pdftotext> or I<mutool> for PDFs and I<mediainfo> for video and audio files.
 
 Install these programs (just the ones you need) and scope.sh will automatically
 use them.
@@ -205,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.
@@ -221,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
@@ -364,7 +392,7 @@ hello-world plugin that describes this procedure.
 
 =head1 KEY BINDINGS
 
-Key bindings are defined in the file F<ranger/config/rc.conf>.  Check this
+Key bindings are defined in the file F<%rangerdir/config/rc.conf>.  Check this
 file for a list of all key bindings.  You can copy it to your local
 configuration directory with the --copy-config=rc option.
 
@@ -569,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
@@ -592,6 +624,62 @@ 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 .m
+
+Apply a new mimetype 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
@@ -610,6 +698,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.
@@ -647,7 +743,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
@@ -678,7 +774,7 @@ in ranger.
 =item automatically_count_files [bool]
 
 Should ranger count and display the number of files in each directory
-as soon as it's visible?  This gets slow with remote file sytems.  Turning it
+as soon as it's visible?  This gets slow with remote file systems.  Turning it
 off will still allow you to see the number of files after entering the
 directory.
 
@@ -752,14 +848,23 @@ Display the file size in the main column?
 
 Display the file size in the status bar?
 
+=item display_free_space_in_status_bar [bool]
+
+Display the free disk space in the status bar?
+
 =item display_tags_in_all_columns [bool]
 
 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
@@ -791,6 +896,15 @@ this pattern will hide all files that start with a dot or end with a tilde.
 
  set hidden_filter ^\.|~$
 
+=item hint_collapse_threshold [int]
+
+The key hint lists up to this size have their sublists expanded.
+Otherwise the submaps are replaced with "...".
+
+=item hostname_in_titlebar [bool]
+
+Show hostname in titlebar?
+
 =item idle_delay [integer]
 
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -880,6 +994,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
@@ -951,10 +1070,6 @@ combination, e.g. "oN" to sort from Z to A.
 
 Put the status bar at the top of the window?
 
-=item hostname_in_titlebar [bool]
-
-Show hostname in titlebar?
-
 =item tilde_in_titlebar [bool]
 
 Abbreviate $HOME with ~ in the titlebar (first line) of ranger?
@@ -963,6 +1078,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?
@@ -993,6 +1114,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
@@ -1015,7 +1141,7 @@ ranger.  For your convenience, this is a list of the "public" commands including
 
  alias [newcommand] [oldcommand]
  bulkrename
- cd [directory]
+ cd [path]
  chain command1[; command2[; command3...]]
  chmod octal_number
  cmap key command
@@ -1095,10 +1221,10 @@ renaming according to the changes you did in the file.
 This shell script is opened in an editor for you to review.  After you close
 it, it will be executed.
 
-=item cd [I<directory>]
+=item cd [I<path>]
 
-The cd command changes the directory.  The command C<:cd -> is equivalent to
-typing ``.
+The cd command changes the directory.  If path is a file, selects that file.
+The command C<:cd -> is equivalent to typing ``.
 
 =item chain I<command1>[; I<command2>[; I<command3>...]]
 
@@ -1507,6 +1633,9 @@ being bound despite the corresponding line being removed from the user's copy of
 the configuration file. This behavior may be disabled with an environment
 variable (see also: B<ENVIRONMENT>). Note: All other configuration files only
 read from one source; i.e. default OR user, not both.
+F<rc.conf> and F<commands.py> are additionally read from F</etc/ranger> if they
+exist for system-wide configuration, user configuration overrides system
+configuration which overrides the default configuration.
 
 When starting ranger with the B<--clean> option, it will not access or create
 any of these files.
diff --git a/doc/rifle.1 b/doc/rifle.1
index 9e439c35..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" "02/22/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_avfs.py b/examples/plugin_avfs.py
new file mode 100644
index 00000000..07968a03
--- /dev/null
+++ b/examples/plugin_avfs.py
@@ -0,0 +1,33 @@
+# Tested with ranger 1.9.1
+#
+# A very simple and possibly buggy support for AVFS
+# (http://avf.sourceforge.net/), that allows ranger to handle
+# archives.
+#
+# Run `:avfs' to browse the selected archive.
+
+from __future__ import (absolute_import, division, print_function)
+
+import os
+import os.path
+
+from ranger.api.commands import Command
+
+
+class avfs(Command):  # pylint: disable=invalid-name
+    avfs_root = os.path.join(os.environ["HOME"], ".avfs")
+    avfs_suffix = "#"
+
+    def execute(self):
+        if os.path.isdir(self.avfs_root):
+            archive_directory = "".join([
+                self.avfs_root,
+                self.fm.thisfile.path,
+                self.avfs_suffix,
+            ])
+            if os.path.isdir(archive_directory):
+                self.fm.cd(archive_directory)
+            else:
+                self.fm.notify("This file cannot be handled by avfs.", bad=True)
+        else:
+            self.fm.notify("Install `avfs' and run `mountavfs' first.", bad=True)
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/examples/rc_emacs.conf b/examples/rc_emacs.conf
index 26074a42..0462282e 100644
--- a/examples/rc_emacs.conf
+++ b/examples/rc_emacs.conf
@@ -122,6 +122,9 @@ set mouse_enabled true
 set display_size_in_main_column true
 set display_size_in_status_bar true
 
+# Display the free disk space in the status bar?
+set display_free_space_in_status_bar true
+
 # Display files tags in all columns or only in main column?
 set display_tags_in_all_columns true
 
@@ -129,7 +132,7 @@ set display_tags_in_all_columns true
 set update_title false
 
 # Set the title to "ranger" in the tmux program?
-set update_tmux_title false
+set update_tmux_title true
 
 # Shorten the title if it gets long?  The number defines how many
 # directories are displayed at once, 0 turns off this feature.
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 c88cdc7c..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:
@@ -138,6 +155,8 @@ class Default(ColorScheme):
             attr &= ~bold
             if context.vcsconflict:
                 fg = magenta
+            elif context.vcsuntracked:
+                fg = cyan
             elif context.vcschanged:
                 fg = red
             elif context.vcsunknown:
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/__init__.py b/ranger/config/__init__.py
index 71df3cb3..0facbdf8 100644
--- a/ranger/config/__init__.py
+++ b/ranger/config/__init__.py
@@ -1 +1 @@
-"""Default options and configration files"""
+"""Default options and configuration files"""
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index a3837d8e..d177203a 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -3,8 +3,9 @@
 # This configuration file is licensed under the same terms as ranger.
 # ===================================================================
 #
-# NOTE: If you copied this file to ~/.config/ranger/commands_full.py,
-# then it will NOT be loaded by ranger, and only serve as a reference.
+# NOTE: If you copied this file to /etc/ranger/commands_full.py or
+# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger,
+# and only serve as a reference.
 #
 # ===================================================================
 # This file contains ranger's commands.
@@ -13,9 +14,14 @@
 # Note that additional commands are automatically generated from the methods
 # of the class ranger.core.actions.Actions.
 #
-# You can customize commands in the file ~/.config/ranger/commands.py.
-# It has the same syntax as this file.  In fact, you can just copy this
-# file there with `ranger --copy-config=commands' and make your modifications.
+# You can customize commands in the files /etc/ranger/commands.py (system-wide)
+# and ~/.config/ranger/commands.py (per user).
+# They have the same syntax as this file.  In fact, you can just copy this
+# file to ~/.config/ranger/commands_full.py with
+# `ranger --copy-config=commands_full' and make your modifications, don't
+# forget to rename it to commands.py.  You can also use
+# `ranger --copy-config=commands' to copy a short sample commands.py that
+# has everything you need to get started.
 # But make sure you update your configs when you update ranger.
 #
 # ===================================================================
@@ -120,9 +126,10 @@ class echo(Command):
 
 
 class cd(Command):
-    """:cd [-r] <dirname>
+    """:cd [-r] <path>
 
     The cd command changes the directory.
+    If the path is a file, selects that file.
     The command 'cd -' is equivalent to typing ``.
     Using the option "-r" will get you to the real path.
     """
@@ -1393,6 +1400,8 @@ class scout(Command):
                 self.fm.cd(pattern)
             else:
                 self.fm.move(right=1)
+                if self.quickly_executed:
+                    self.fm.block_input(0.5)
 
         if self.KEEP_OPEN in flags and thisdir != self.fm.thisdir:
             # reopen the console:
@@ -1536,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>
 
@@ -1718,6 +1782,7 @@ class yank(Command):
 
     modes = {
         '': 'basename',
+        'name_without_extension': 'basename_without_extension',
         'name': 'basename',
         'dir': 'dirname',
         'path': 'path',
@@ -1750,7 +1815,9 @@ class yank(Command):
 
         clipboard_commands = clipboards()
 
-        selection = self.get_selection_attr(self.modes[self.arg(1)])
+        mode = self.modes[self.arg(1)]
+        selection = self.get_selection_attr(mode)
+
         new_clipboard_contents = "\n".join(selection)
         for command in clipboard_commands:
             process = subprocess.Popen(command, universal_newlines=True,
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 676090fb..a32ab494 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -1,7 +1,8 @@
 # ===================================================================
 # This file contains the default startup commands for ranger.
-# To change them, it is recommended to create the file
-# ~/.config/ranger/rc.conf and add your custom commands there.
+# To change them, it is recommended to create either /etc/ranger/rc.conf
+# (system-wide) or ~/.config/ranger/rc.conf (per user) and add your custom
+# commands there.
 #
 # If you copy this whole file there, you may want to set the environment
 # variable RANGER_LOAD_DEFAULT_RC to FALSE to avoid loading it twice.
@@ -85,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.
@@ -92,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
@@ -101,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
 
@@ -124,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
@@ -137,6 +162,9 @@ set mouse_enabled true
 set display_size_in_main_column true
 set display_size_in_status_bar true
 
+# Display the free disk space in the status bar?
+set display_free_space_in_status_bar true
+
 # Display files tags in all columns or only in main column?
 set display_tags_in_all_columns true
 
@@ -144,7 +172,7 @@ set display_tags_in_all_columns true
 set update_title false
 
 # Set the title to "ranger" in the tmux program?
-set update_tmux_title false
+set update_tmux_title true
 
 # Shorten the title if it gets long?  The number defines how many
 # directories are displayed at once, 0 turns off this feature.
@@ -216,6 +244,10 @@ set cd_tab_fuzzy false
 # disable this feature.
 set preview_max_size 0
 
+# The key hint lists up to this size have their sublists expanded.
+# Otherwise the submaps are replaced with "...".
+set hint_collapse_threshold 10
+
 # Add the highlighted file to the path in the titlebar
 set show_selection_in_titlebar true
 
@@ -232,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
 
@@ -249,6 +286,10 @@ set wrap_scroll false
 # directories, files and symlinks respectively.
 set global_inode_type_filter
 
+# This setting allows to freeze the list of files to save I/O bandwidth.  It
+# should be 'false' during start-up, but you can toggle it by pressing F.
+set freeze_files false
+
 # ===================================================================
 # == Local Options
 # ===================================================================
@@ -270,8 +311,8 @@ alias qall  quitall
 alias qall! quitall!
 alias setl  setlocal
 
-alias filter     scout -prt
-alias find       scout -aeit
+alias filter     scout -prts
+alias find       scout -aets
 alias mark       scout -mr
 alias unmark     scout -Mr
 alias search     scout -rs
@@ -311,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
@@ -374,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 ~
@@ -385,6 +429,7 @@ map gL cd -r %f
 map go cd /opt
 map gv cd /var
 map gm cd /media
+map gi eval fm.cd('/run/media/' + os.getenv('USER'))
 map gM cd /mnt
 map gs cd /srv
 map gp cd /tmp
@@ -400,6 +445,7 @@ map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh
 map yp yank path
 map yd yank dir
 map yn yank name
+map y. yank name_without_extension
 
 # Filesystem Operations
 map =  chmod
@@ -474,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!
@@ -503,6 +551,8 @@ map zc    set collapse_preview!
 map zd    set sort_directories_first!
 map zh    set show_hidden!
 map <C-h> set show_hidden!
+copymap <C-h> <backspace>
+copymap <backspace> <backspace2>
 map zI    set flushinput!
 map zi    set preview_images!
 map zm    set mouse_enabled!
@@ -514,6 +564,21 @@ 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 .m console filter_stack add mime%space
+map .d filter_stack add type d
+map .f filter_stack add type f
+map .l filter_stack add type l
+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
@@ -558,8 +623,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)
@@ -571,6 +639,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 e2653a76..babdcda7 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -151,7 +151,7 @@ ext pdf, has atril,    X, flag f = atril -- "$@"
 ext pdf, has okular,   X, flag f = okular -- "$@"
 ext pdf, has epdfview, X, flag f = epdfview -- "$@"
 ext pdf, has qpdfview, X, flag f = qpdfview "$@"
-ext pdf, has open,     X, flat f = open "$@"
+ext pdf, has open,     X, flag f = open "$@"
 
 ext docx?, has catdoc,       terminal = catdoc -- "$@" | "$PAGER"
 
@@ -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 -- "$@"
@@ -183,6 +184,7 @@ mime ^image, has eog,       X, flag f = eog -- "$@"
 mime ^image, has eom,       X, flag f = eom -- "$@"
 mime ^image, has nomacs,    X, flag f = nomacs -- "$@"
 mime ^image, has geeqie,    X, flag f = geeqie -- "$@"
+mime ^image, has gwenview,  X, flag f = gwenview -- "$@"
 mime ^image, has gimp,      X, flag f = gimp -- "$@"
 ext xcf,                    X, flag f = gimp -- "$@"
 
@@ -210,6 +212,34 @@ ext rar, has unrar = unrar l "$1" | less
 ext rar, has unrar = for file in "$@"; do unrar x "$file"; done
 
 #-------------------------------------------
+# Flag t fallback terminals
+#-------------------------------------------
+# Rarely installed terminal emulators get higher priority; It is assumed that
+# if you install a rare terminal emulator, you probably use it.
+# gnome-terminal/konsole/xterm on the other hand are often installed as part of
+# a desktop environment or as fallback terminal emulators.
+mime ^ranger/x-terminal-emulator, has terminology = terminology -e "$@"
+mime ^ranger/x-terminal-emulator, has kitty = kitty -- "$@"
+mime ^ranger/x-terminal-emulator, has alacritty = alacritty -e "$@"
+mime ^ranger/x-terminal-emulator, has sakura = sakura -e "$@"
+mime ^ranger/x-terminal-emulator, has lilyterm = lilyterm -e "$@"
+#mime ^ranger/x-terminal-emulator, has cool-retro-term = cool-retro-term -e "$@"
+mime ^ranger/x-terminal-emulator, has termite = termite -x '"$@"'
+#mime ^ranger/x-terminal-emulator, has yakuake = yakuake -e "$@"
+mime ^ranger/x-terminal-emulator, has guake = guake -ne "$@"
+mime ^ranger/x-terminal-emulator, has tilda = tilda -c "$@"
+mime ^ranger/x-terminal-emulator, has st = st -e "$@"
+mime ^ranger/x-terminal-emulator, has terminator = terminator -x "$@"
+mime ^ranger/x-terminal-emulator, has urxvt = urxvt -e "$@"
+mime ^ranger/x-terminal-emulator, has pantheon-terminal = pantheon-terminal -e "$@"
+mime ^ranger/x-terminal-emulator, has lxterminal = lxterminal -e "$@"
+mime ^ranger/x-terminal-emulator, has mate-terminal = mate-terminal -x "$@"
+mime ^ranger/x-terminal-emulator, has xfce4-terminal = xfce4-terminal -x "$@"
+mime ^ranger/x-terminal-emulator, has konsole = konsole -e "$@"
+mime ^ranger/x-terminal-emulator, has gnome-terminal = gnome-terminal -- "$@"
+mime ^ranger/x-terminal-emulator, has xterm = xterm -e "$@"
+
+#-------------------------------------------
 # Misc
 #-------------------------------------------
 label wallpaper, number 11, mime ^image, has feh, X = feh --bg-scale "$1"
diff --git a/ranger/container/directory.py b/ranger/container/directory.py
index 18b1687c..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)]
 
@@ -338,8 +341,9 @@ class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public
                         dirlist = [
                             os.path.join("/", dirpath, d)
                             for d in dirnames
-                            if self.flat == -1 or
-                            (dirpath.count(os.path.sep) - mypath.count(os.path.sep)) <= self.flat
+                            if self.flat == -1
+                            or (dirpath.count(os.path.sep)
+                                - mypath.count(os.path.sep)) <= self.flat
                         ]
                         filelist += dirlist
                         filelist += [os.path.join("/", dirpath, f) for f in filenames]
diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py
index 0c9f70f6..37151ecf 100644
--- a/ranger/container/fsobject.py
+++ b/ranger/container/fsobject.py
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
 import re
 from grp import getgrgid
 from os import lstat, stat
-from os.path import abspath, basename, dirname, realpath, relpath
+from os.path import abspath, basename, dirname, realpath, relpath, splitext
 from pwd import getpwuid
 from time import time
 
@@ -171,6 +171,10 @@ class FileSystemObject(  # pylint: disable=too-many-instance-attributes,too-many
         return basename_list
 
     @lazy_property
+    def basename_without_extension(self):
+        return splitext(self.basename)[0]
+
+    @lazy_property
     def safe_basename(self):
         return self.basename.translate(_SAFE_STRING_TABLE)
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index d0b094d0..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,
@@ -38,13 +39,15 @@ ALLOWED_SETTINGS = {
     'dirname_in_tabs': bool,
     'display_size_in_main_column': bool,
     '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,
     'global_inode_type_filter': str,
     'hidden_filter': str,
+    'hint_collapse_threshold': int,
     'hostname_in_titlebar': bool,
     'idle_delay': int,
     'iterm2_font_width': int,
@@ -63,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,
@@ -89,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,
 }
@@ -96,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'],
@@ -111,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 31fa9518..40fd52a3 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function)
 
 import codecs
 import os
-from os import link, symlink, getcwd, listdir, stat
+from os import link, symlink, listdir, stat
 from os.path import join, isdir, realpath, exists
 import re
 import shlex
@@ -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
     # --------------------------
@@ -1186,9 +1257,56 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
     def tab_new(self, path=None, narg=None):
         if narg:
             return self.tab_open(narg, path)
-        for i in range(1, 10):
-            if i not in self.tabs:
-                return self.tab_open(i, path)
+        i = 1
+        while i in self.tabs:
+            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):
@@ -1237,7 +1355,18 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
 
     def get_tab_list(self):
         assert self.tabs, "There must be at least 1 tab at all times"
-        return sorted(self.tabs)
+
+        class NaturalOrder(object):  # pylint: disable=too-few-public-methods
+            def __init__(self, obj):
+                self.obj = obj
+
+            def __lt__(self, other):
+                try:
+                    return self.obj < other.obj
+                except TypeError:
+                    return str(self.obj) < str(other.obj)
+
+        return sorted(self.tabs, key=NaturalOrder)
 
     # --------------------------
     # -- Overview of internals
@@ -1373,9 +1502,9 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             self.notify(new_name)
             try:
                 if relative:
-                    relative_symlink(fobj.path, join(getcwd(), new_name))
+                    relative_symlink(fobj.path, join(self.fm.thisdir.path, new_name))
                 else:
-                    symlink(fobj.path, join(getcwd(), new_name))
+                    symlink(fobj.path, join(self.fm.thisdir.path, new_name))
             except OSError as ex:
                 self.notify('Failed to paste symlink: View log for more info',
                             bad=True, exception=ex)
@@ -1384,7 +1513,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         for fobj in self.copy_buffer:
             new_name = next_available_filename(fobj.basename)
             try:
-                link(fobj.path, join(getcwd(), new_name))
+                link(fobj.path, join(self.fm.thisdir.path, new_name))
             except OSError as ex:
                 self.notify('Failed to paste hardlink: View log for more info',
                             bad=True, exception=ex)
@@ -1392,7 +1521,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
     def paste_hardlinked_subtree(self):
         for fobj in self.copy_buffer:
             try:
-                target_path = join(getcwd(), fobj.basename)
+                target_path = join(self.fm.thisdir.path, fobj.basename)
                 self._recurse_hardlinked_tree(fobj.path, target_path)
             except OSError as ex:
                 self.notify('Failed to paste hardlinked subtree: View log for more info',
diff --git a/ranger/core/filter_stack.py b/ranger/core/filter_stack.py
new file mode 100644
index 00000000..2ca2b1c5
--- /dev/null
+++ b/ranger/core/filter_stack.py
@@ -0,0 +1,151 @@
+# -*- 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
+import mimetypes
+
+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("mime")
+class MimeFilter(BaseFilter):
+    def __init__(self, pattern):
+        self.pattern = pattern
+        self.regex = re.compile(pattern)
+
+    def __call__(self, fobj):
+        mimetype, _ = mimetypes.guess_type(fobj.relative_path)
+        if mimetype is None:
+            return False
+        return self.regex.search(mimetype)
+
+    def __str__(self):
+        return "<Filter: mimetype =~ /{}/>".format(self.pattern)
+
+
+@stack_filter("type")
+class TypeFilter(BaseFilter):
+    type_to_function = {
+        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 c55a3922..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()
-                                         if t != self.current_tab) + '\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 4adea918..598ce243 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -89,14 +89,12 @@ def main(
 
     SettingsAware.settings_set(Settings())
 
+    # TODO: deprecate --selectfile
     if args.selectfile:
         args.selectfile = os.path.abspath(args.selectfile)
         args.paths.insert(0, os.path.dirname(args.selectfile))
 
-    if args.paths:
-        paths = [p[7:] if p.startswith('file:///') else p for p in args.paths]
-    else:
-        paths = [os.environ.get('PWD', os.getcwd())]
+    paths = get_paths(args)
     paths_inaccessible = []
     for path in paths:
         try:
@@ -182,6 +180,7 @@ def main(
             fm.select_file(args.selectfile)
 
         if args.cmd:
+            fm.enter_dir(fm.thistab.path)
             for command in args.cmd:
                 fm.execute_console(command)
 
@@ -235,6 +234,24 @@ https://github.com/ranger/ranger/issues
         return exit_code  # pylint: disable=lost-exception
 
 
+def get_paths(args):
+    if args.paths:
+        prefix = 'file:///'
+        prefix_length = len(prefix)
+        paths = [path[prefix_length:] if path.startswith(prefix) else path for path in args.paths]
+    else:
+        start_directory = os.environ.get('PWD')
+        is_valid_start_directory = start_directory and os.path.exists(start_directory)
+        if not is_valid_start_directory:
+            start_directory = __get_home_directory()
+        paths = [start_directory]
+    return paths
+
+
+def __get_home_directory():
+    return os.path.expanduser('~')
+
+
 def xdg_path(env_var):
     path = os.environ.get(env_var)
     if path and os.path.isabs(path):
@@ -339,23 +356,50 @@ def load_settings(  # pylint: disable=too-many-locals,too-many-branches,too-many
     fm.commands.load_commands_from_module(commands_default)
 
     if not clean:
+        system_confdir = os.path.join(os.sep, 'etc', 'ranger')
+        if os.path.exists(system_confdir):
+            sys.path.append(system_confdir)
         allow_access_to_confdir(ranger.args.confdir, True)
 
         # Load custom commands
-        custom_comm_path = fm.confpath('commands.py')
-        if os.path.exists(custom_comm_path):
+        def import_file(name, path):  # From https://stackoverflow.com/a/67692
+            # 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)
+                module = util.module_from_spec(spec)
+                spec.loader.exec_module(module)
+            elif (3, 3) <= sys.version_info < (3, 5):
+                from importlib.machinery import SourceFileLoader
+                module = SourceFileLoader(name, path).load_module()
+            else:
+                import imp
+                module = imp.load_source(name, path)
+            # pragma pylint: enable=no-name-in-module,import-error,no-member
+            return module
+
+        def load_custom_commands(*paths):
             old_bytecode_setting = sys.dont_write_bytecode
             sys.dont_write_bytecode = True
-            try:
-                import commands as commands_custom
-                fm.commands.load_commands_from_module(commands_custom)
-            except ImportError as ex:
-                LOG.debug("Failed to import custom commands from '%s'", custom_comm_path)
-                LOG.exception(ex)
-            else:
-                LOG.debug("Loaded custom commands from '%s'", custom_comm_path)
+            for custom_comm_path in paths:
+                if os.path.exists(custom_comm_path):
+                    try:
+                        commands_custom = import_file('commands',
+                                                      custom_comm_path)
+                        fm.commands.load_commands_from_module(commands_custom)
+                    except ImportError as ex:
+                        LOG.debug("Failed to import custom commands from '%s'",
+                                  custom_comm_path)
+                        LOG.exception(ex)
+                    else:
+                        LOG.debug("Loaded custom commands from '%s'",
+                                  custom_comm_path)
             sys.dont_write_bytecode = old_bytecode_setting
 
+        system_comm_path = os.path.join(system_confdir, 'commands.py')
+        custom_comm_path = fm.confpath('commands.py')
+        load_custom_commands(system_comm_path, custom_comm_path)
+
         # XXX Load plugins (experimental)
         plugindir = fm.confpath('plugins')
         try:
@@ -394,12 +438,16 @@ def load_settings(  # pylint: disable=too-many-locals,too-many-branches,too-many
         allow_access_to_confdir(ranger.args.confdir, False)
         # Load rc.conf
         custom_conf = fm.confpath('rc.conf')
+        system_conf = os.path.join(system_confdir, 'rc.conf')
         default_conf = fm.relpath('config', 'rc.conf')
 
         custom_conf_is_readable = os.access(custom_conf, os.R_OK)
-        if (os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE' or
-                not custom_conf_is_readable):
+        system_conf_is_readable = os.access(system_conf, os.R_OK)
+        if (os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE'
+                or not (custom_conf_is_readable or system_conf_is_readable)):
             fm.source(default_conf)
+        if system_conf_is_readable:
+            fm.source(system_conf)
         if custom_conf_is_readable:
             fm.source(custom_conf)
 
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 540a910e..13a25b45 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -60,7 +60,8 @@ handle_extension() {
         # PDF
         pdf)
             # Preview as text conversion
-            pdftotext -l 10 -nopgbrk -q -- "${FILE_PATH}" - && exit 5
+            pdftotext -l 10 -nopgbrk -q -- "${FILE_PATH}" - | fmt -w ${PV_WIDTH} && exit 5
+            mutool draw -F txt -i -- "${FILE_PATH}" 1-10 | fmt -w ${PV_WIDTH} && exit 5
             exiftool "${FILE_PATH}" && exit 5
             exit 1;;
 
@@ -122,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/direction.py b/ranger/ext/direction.py
index 7df45556..33ebb604 100644
--- a/ranger/ext/direction.py
+++ b/ranger/ext/direction.py
@@ -97,8 +97,8 @@ class Direction(dict):
         return self.get('cycle') in (True, 'true', 'on', 'yes')
 
     def one_indexed(self):
-        return ('one_indexed' in self and
-                self.get('one_indexed') in (True, 'true', 'on', 'yes'))
+        return ('one_indexed' in self
+                and self.get('one_indexed') in (True, 'true', 'on', 'yes'))
 
     def multiply(self, n):
         for key in ('up', 'right', 'down', 'left'):
diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py
index df74eabf..f365e594 100644
--- a/ranger/ext/human_readable.py
+++ b/ranger/ext/human_readable.py
@@ -15,6 +15,10 @@ def human_readable(byte, separator=' '):  # pylint: disable=too-many-return-stat
     '1023 M'
     """
 
+    # handle automatically_count_files false
+    if byte is None:
+        return ''
+
     # I know this can be written much shorter, but this long version
     # performs much better than what I had before.  If you attempt to
     # shorten this code, take performance into consideration.
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index 67941e27..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()
@@ -434,20 +440,20 @@ class URXVTImageDisplayer(ImageDisplayer, FileManagerAware):
         pct_width, pct_height = self._get_sizes()
 
         sys.stdout.write(
-            self.display_protocol +
-            path +
-            ";{pct_width}x{pct_height}+{pct_x}+{pct_y}:op=keep-aspect".format(
+            self.display_protocol
+            + path
+            + ";{pct_width}x{pct_height}+{pct_x}+{pct_y}:op=keep-aspect".format(
                 pct_width=pct_width, pct_height=pct_height, pct_x=pct_x, pct_y=pct_y
-            ) +
-            self.close_protocol
+            )
+            + self.close_protocol
         )
         sys.stdout.flush()
 
     def clear(self, start_x, start_y, width, height):
         sys.stdout.write(
-            self.display_protocol +
-            ";100x100+1000+1000" +
-            self.close_protocol
+            self.display_protocol
+            + ";100x100+1000+1000"
+            + self.close_protocol
         )
         sys.stdout.flush()
 
@@ -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 70215039..6832e2cf 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -261,6 +261,14 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
             process = Popen(["file", "--mime-type", "-Lb", fname], stdout=PIPE, stderr=PIPE)
             mimetype, _ = process.communicate()
             self._mimetype = mimetype.decode(ENCODING).strip()
+            if self._mimetype == 'application/octet-stream':
+                try:
+                    process = Popen(["mimetype", "--output-format", "%m", fname],
+                                    stdout=PIPE, stderr=PIPE)
+                    mimetype, _ = process.communicate()
+                    self._mimetype = mimetype.decode(ENCODING).strip()
+                except OSError:
+                    pass
         return self._mimetype
 
     def _build_command(self, files, action, flags):
@@ -349,29 +357,80 @@ 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']
 
                 cmd = prefix + [command]
                 if 't' in flags:
-                    if 'TERMCMD' not in os.environ:
-                        term = os.environ['TERM']
-                        if term.startswith('rxvt-unicode'):
+                    term = os.environ.get('TERMCMD', os.environ['TERM'])
+
+                    # Handle aliases of xterm and urxvt, rxvt and st and
+                    # termite
+                    # Match 'xterm', 'xterm-256color'
+                    if term in ['xterm', 'xterm-256color']:
+                        term = 'xterm'
+                    if term in ['xterm-kitty']:
+                        term = 'kitty'
+                    if term in ['xterm-termite']:
+                        term = 'termite'
+                    if term in ['st', 'st-256color']:
+                        term = 'st'
+                    if term in ['urxvt', 'rxvt-unicode',
+                                'rxvt-unicode-256color']:
+                        term = 'urxvt'
+                    if term in ['rxvt', 'rxvt-256color']:
+                        if 'rxvt' in get_executables():
+                            term = 'rxvt'
+                        else:
                             term = 'urxvt'
-                        elif term.startswith('rxvt-'):
-                            # Sometimes urxvt calls itself "rxvt-256color"
-                            if 'rxvt' in get_executables():
-                                term = 'rxvt'
-                            else:
-                                term = 'urxvt'
-                        if term not in get_executables():
-                            self.hook_logger("Can not determine terminal command.  "
-                                             "Please set $TERMCMD manually.")
-                            # A fallback terminal that is likely installed:
-                            term = 'xterm'
-                        os.environ['TERMCMD'] = term
-                    cmd = [os.environ['TERMCMD'], '-e'] + cmd
+
+                    if term not in get_executables():
+                        self.hook_logger("Can not determine terminal command, "
+                                         "using rifle to determine fallback.  "
+                                         "Please set $TERMCMD manually or "
+                                         "change fallbacks in rifle.conf.")
+                        self._mimetype = 'ranger/x-terminal-emulator'
+                        self.execute(
+                            files=[command.split(';')[1].split('--')[0].strip()]
+                            + files, flags='f',
+                            mimetype='ranger/x-terminal-emulator')
+                        return None
+
+                    # Choose correct cmdflag accordingly
+                    if term in ['xfce4-terminal', 'mate-terminal',
+                                'terminator']:
+                        cmdflag = '-x'
+                    elif term in ['xterm', 'urxvt', 'rxvt', 'lxterminal',
+                                  'konsole', 'lilyterm', 'cool-retro-term',
+                                  'terminology', 'pantheon-terminal', 'termite',
+                                  'st', 'stterm']:
+                        cmdflag = '-e'
+                    elif term in ['gnome-terminal', 'kitty']:
+                        cmdflag = '--'
+                    elif term in ['tilda', ]:
+                        cmdflag = '-c'
+                    else:
+                        cmdflag = '-e'
+
+                    os.environ['TERMCMD'] = term
+
+                    # These terms don't work with the '/bin/sh set --' scheme.
+                    # A temporary fix.
+                    if term in ['tilda', 'pantheon-terminal', 'terminology',
+                                'termite']:
+
+                        target = command.split(';')[0].split('--')[1].strip()
+                        app = command.split(';')[1].split('--')[0].strip()
+                        cmd = [os.environ['TERMCMD'], cmdflag, '%s %s'
+                               % (app, target)]
+                    elif term in ['guake']:
+                        cmd = [os.environ['TERMCMD'], '-n', '${PWD}', cmdflag] + cmd
+                    else:
+                        cmd = [os.environ['TERMCMD'], cmdflag] + cmd
+
+                    # self.hook_logger('cmd: %s' %cmd)
+
                 if 'f' in flags or 't' in flags:
                     Popen_forked(cmd, env=self.hook_environment(os.environ))
                 else:
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 45f983e8..8f6439c7 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -66,6 +66,11 @@ blink      = curses.A_BLINK
 reverse    = curses.A_REVERSE
 underline  = curses.A_UNDERLINE
 invisible  = curses.A_INVIS
+dim = curses.A_DIM
 
 default_colors = (default, default, normal)
 # pylint: enable=invalid-name,bad-whitespace
+
+curses.setupterm()
+# Adding BRIGHT to a color achieves what `bold` was used for.
+BRIGHT = 8 if curses.tigetnum('colors') >= 16 else 0
diff --git a/ranger/gui/context.py b/ranger/gui/context.py
index d8d1957c..96849686 100644
--- a/ranger/gui/context.py
+++ b/ranger/gui/context.py
@@ -23,7 +23,7 @@ CONTEXT_KEYS = [
     'keybuffer',
     'infostring',
     'vcsfile', 'vcsremote', 'vcsinfo', 'vcscommit', 'vcsdate',
-    'vcsconflict', 'vcschanged', 'vcsunknown', 'vcsignored',
+    'vcsconflict', 'vcschanged', 'vcsunknown', 'vcsignored', 'vcsuntracked',
     'vcsstaged', 'vcssync', 'vcsnone', 'vcsbehind', 'vcsahead', 'vcsdiverged'
 ]
 
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index ac067d01..14f1e0e4 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -35,7 +35,9 @@ class CursesShortcuts(SettingsAware):
 
         try:
             self.win.addstr(*args)
-        except (curses.error, TypeError):
+        except (curses.error, TypeError, ValueError):
+            # a TypeError changed to ValueError from version 3.5 onwards
+            # https://bugs.python.org/issue22215
             if len(args) > 1:
                 self.win.move(y, x)
 
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 990db0ad..441e9032 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -113,7 +113,7 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
             self._draw_title = curses.tigetflag('hs')  # has_status_line
 
             # Save tmux setting `automatic-rename`
-            if self.settings.update_tmux_title:
+            if self.settings.update_tmux_title and 'TMUX' in os.environ:
                 try:
                     self._tmux_automatic_rename = check_output(
                         ['tmux', 'show-window-options', '-v', 'automatic-rename']).strip()
@@ -123,7 +123,7 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
         self.update_size()
         self.is_on = True
 
-        if self.settings.update_tmux_title:
+        if self.settings.update_tmux_title and 'TMUX' in os.environ:
             sys.stdout.write("\033kranger\033\\")
             sys.stdout.flush()
 
@@ -172,7 +172,7 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
         DisplayableContainer.destroy(self)
 
         # Restore tmux setting `automatic-rename`
-        if self.settings.update_tmux_title:
+        if self.settings.update_tmux_title and 'TMUX' in os.environ:
             if self._tmux_automatic_rename:
                 try:
                     check_output(['tmux', 'set-window-option',
@@ -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/__init__.py b/ranger/gui/widgets/__init__.py
index 36292103..c8f1262b 100644
--- a/ranger/gui/widgets/__init__.py
+++ b/ranger/gui/widgets/__init__.py
@@ -12,11 +12,11 @@ class Widget(Displayable):
         'conflict': (
             'X', ['vcsconflict']),
         'untracked': (
-            '+', ['vcschanged']),
+            '?', ['vcsuntracked']),
         'deleted': (
             '-', ['vcschanged']),
         'changed': (
-            '*', ['vcschanged']),
+            '+', ['vcschanged']),
         'staged': (
             '*', ['vcsstaged']),
         'ignored': (
@@ -26,7 +26,7 @@ class Widget(Displayable):
         'none': (
             ' ', []),
         'unknown': (
-            '?', ['vcsunknown']),
+            '!', ['vcsunknown']),
     }
 
     vcsremotestatus_symb = {
@@ -41,7 +41,7 @@ class Widget(Displayable):
         'none': (
             '⌂', ['vcsnone']),
         'unknown': (
-            '?', ['vcsunknown']),
+            '!', ['vcsunknown']),
     }
 
     ellipsis = {False: '~', True: '…'}
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index b3272cbc..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:
@@ -318,8 +324,8 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
 
             text = current_linemode.filetitle(drawn, metadata)
 
-            if drawn.marked and (self.main_column or
-                                 self.settings.display_tags_in_all_columns):
+            if drawn.marked and (self.main_column
+                                 or self.settings.display_tags_in_all_columns):
                 text = " " + text
 
             # Computing predisplay data. predisplay contains a list of lists
@@ -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/statusbar.py b/ranger/gui/widgets/statusbar.py
index 266d48ca..3457955e 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -275,13 +275,14 @@ class StatusBar(Widget):  # pylint: disable=too-many-instance-attributes
             right.add("/" + str(len(target.marked_items)))
         else:
             right.add(human_readable(target.disk_usage, separator='') + " sum")
-            try:
-                free = get_free_space(target.mount_path)
-            except OSError:
-                pass
-            else:
-                right.add(", ", "space")
-                right.add(human_readable(free, separator='') + " free")
+            if self.settings.display_free_space_in_status_bar:
+                try:
+                    free = get_free_space(target.mount_path)
+                except OSError:
+                    pass
+                else:
+                    right.add(", ", "space")
+                    right.add(human_readable(free, separator='') + " free")
         right.add("  ", "space")
 
         if target.marked_items:
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_base.py b/ranger/gui/widgets/view_base.py
index cb205d92..4493443e 100644
--- a/ranger/gui/widgets/view_base.py
+++ b/ranger/gui/widgets/view_base.py
@@ -72,8 +72,8 @@ class ViewBase(Widget, DisplayableContainer):  # pylint: disable=too-many-instan
         sorted_bookmarks = sorted(
             (
                 item for item in self.fm.bookmarks
-                if self.fm.settings.show_hidden_bookmarks or
-                '/.' not in item[1].path
+                if self.fm.settings.show_hidden_bookmarks
+                or '/.' not in item[1].path
             ),
             key=lambda t: t[0].lower(),
         )
@@ -112,16 +112,62 @@ class ViewBase(Widget, DisplayableContainer):  # pylint: disable=too-many-instan
         self.color_reset()
         self.need_clear = True
         hints = []
-        for key, value in self.fm.ui.keybuffer.pointer.items():
-            key = key_to_string(key)
-            if isinstance(value, dict):
-                text = '...'
-            else:
-                text = value
-            if text.startswith('hint') or text.startswith('chain hint'):
-                continue
-            hints.append((key, text))
-        hints.sort(key=lambda t: t[1])
+
+        def populate_hints(keymap, prefix=""):
+            for key, value in keymap.items():
+                key = prefix + key_to_string(key)
+                if isinstance(value, dict):
+                    populate_hints(value, key)
+                else:
+                    text = value
+                    if text.startswith('hint') or text.startswith('chain hint'):
+                        continue
+                    hints.append((key, text))
+        populate_hints(self.fm.ui.keybuffer.pointer)
+
+        def sort_hints(hints):
+            """Sort the hints by the action string but first group them by the
+            first key.
+
+            """
+            from itertools import groupby
+
+            # groupby needs the list to be sorted.
+            hints.sort(key=lambda t: t[0])
+
+            def group_hints(hints):
+                def first_key(hint):
+                    return hint[0][0]
+
+                def action_string(hint):
+                    return hint[1]
+
+                return (sorted(group, key=action_string)
+                        for _, group
+                        in groupby(
+                            hints,
+                            key=first_key))
+
+            grouped_hints = group_hints(hints)
+
+            # If there are too many hints, collapse the sublists.
+            if len(hints) > self.fm.settings.hint_collapse_threshold:
+                def first_key_in_group(group):
+                    return group[0][0][0]
+                grouped_hints = (
+                    [(first_key_in_group(hint_group), "...")]
+                    if len(hint_group) > 1
+                    else hint_group
+                    for hint_group in grouped_hints
+                )
+
+            # Sort by the first action in group.
+            grouped_hints = sorted(grouped_hints, key=lambda g: g[0][1])
+
+            def flatten(nested_list):
+                return [item for inner_list in nested_list for item in inner_list]
+            return flatten(grouped_hints)
+        hints = sort_hints(hints)
 
         hei = min(self.hei - 1, len(hints))
         ystart = self.hei - hei
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',
diff --git a/tests/ranger/core/__init__.py b/tests/ranger/core/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/ranger/core/__init__.py
diff --git a/tests/ranger/core/test_main.py b/tests/ranger/core/test_main.py
new file mode 100644
index 00000000..d992b8a7
--- /dev/null
+++ b/tests/ranger/core/test_main.py
@@ -0,0 +1,18 @@
+import collections
+import os
+
+from ranger.core import main
+
+
+def test_get_paths():
+    args_tuple = collections.namedtuple('args', 'paths')
+    args = args_tuple(paths=None)
+
+    paths = main.get_paths(args)
+
+    for path in paths:
+        assert os.path.exists(path)
+
+
+if __name__ == '__main__':
+    test_get_paths()