summary refs log tree commit diff stats
diff options
context:
space:
mode:
-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.md2
-rw-r--r--doc/ranger.181
-rw-r--r--doc/ranger.pod98
-rw-r--r--doc/rifle.14
-rw-r--r--examples/bash_automatic_cd.sh4
-rw-r--r--examples/plugin_avfs.py33
-rw-r--r--examples/rc_emacs.conf5
-rw-r--r--ranger/__init__.py21
-rw-r--r--ranger/colorschemes/default.py2
-rw-r--r--ranger/config/__init__.py2
-rwxr-xr-xranger/config/commands.py78
-rw-r--r--ranger/config/rc.conf61
-rw-r--r--ranger/config/rifle.conf4
-rw-r--r--ranger/container/directory.py8
-rw-r--r--ranger/container/fsobject.py6
-rw-r--r--ranger/container/settings.py8
-rw-r--r--ranger/core/actions.py92
-rw-r--r--ranger/core/filter_stack.py134
-rw-r--r--ranger/core/fm.py12
-rw-r--r--ranger/core/main.py79
-rw-r--r--ranger/core/runner.py2
-rw-r--r--ranger/core/tab.py8
-rwxr-xr-xranger/data/scope.sh3
-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.py10
-rw-r--r--ranger/gui/context.py2
-rw-r--r--ranger/gui/ui.py6
-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/view_base.py4
-rwxr-xr-xsetup.py2
-rw-r--r--tests/ranger/core/__init__.py0
-rw-r--r--tests/ranger/core/test_main.py18
47 files changed, 1366 insertions, 268 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 572a2eb7..e8d669ce 100644
--- a/doc/howto-publish-a-release.md
+++ b/doc/howto-publish-a-release.md
@@ -31,7 +31,7 @@ Test everything
 Make a release commit
 ---------------------
 * [ ] Update the number in the `README`
-* [ ] Update `__version__` and `VERSION` in `ranger/__init__.py`
+* [ ] Update `__version__` and `__release__` in `ranger/__init__.py`
 * [ ] Update `__version__` in `ranger/ext/rifle.py`
 * [ ] `make man`
 * [ ] Write changelog entry
diff --git a/doc/ranger.1 b/doc/ranger.1
index bd6116d1..060686b8 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -129,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.1" "2018-04-03" "ranger manual"
+.TH RANGER 1 "ranger-1.9.1" "2018-07-15" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -274,10 +274,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.
@@ -307,6 +307,13 @@ This feature relies on the dimensions of the terminal's font.  By default, a
 width of 8 and height of 11 are used.  To use other values, set the options
 \&\f(CW\*(C`iterm2_font_width\*(C'\fR and \f(CW\*(C`iterm2_font_height\*(C'\fR to the desired values.
 .PP
+\fIterminology\fR
+.IX Subsection "terminology"
+.PP
+This only works in terminology. It can render vector graphics, but works only locally.
+.PP
+To enable this feature, set the option \f(CW\*(C`preview_images_method\*(C'\fR to terminology.
+.PP
 \fIurxvt\fR
 .IX Subsection "urxvt"
 .PP
@@ -324,6 +331,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
@@ -470,7 +485,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
@@ -642,6 +657,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
@@ -675,7 +725,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
@@ -709,7 +759,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
@@ -771,6 +821,9 @@ 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?
@@ -890,6 +943,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
@@ -952,6 +1009,11 @@ Abbreviate \f(CW$HOME\fR with ~ in the titlebar (first line) of ranger?
 .IP "unicode_ellipsis [bool]" 4
 .IX Item "unicode_ellipsis [bool]"
 Use a unicode \*(L"...\*(R" character instead of \*(L"~\*(R" to mark cut-off filenames?
+.IP "bidi_support [bool]" 4
+.IX Item "bidi_support [bool]"
+Try to properly display file names in \s-1RTL\s0 languages (Hebrew, Arabic) by using
+a \s-1BIDI\s0 algorithm to reverse the relevant parts of the text.
+Requires the python-bidi pip package.
 .IP "update_title [bool]" 4
 .IX Item "update_title [bool]"
 Set a window title?
@@ -978,6 +1040,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
@@ -1432,6 +1498,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.
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 4b0e90b1..48e6a41e 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -189,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.
@@ -220,6 +220,12 @@ This feature relies on the dimensions of the terminal's font.  By default, a
 width of 8 and height of 11 are used.  To use other values, set the options
 C<iterm2_font_width> and C<iterm2_font_height> to the desired values.
 
+=head3 terminology
+
+This only works in terminology. It can render vector graphics, but works only locally.
+
+To enable this feature, set the option C<preview_images_method> to terminology.
+
 =head3 urxvt
 
 This only works in urxvt compiled with pixbuf support. Does not work over ssh.
@@ -236,6 +242,13 @@ window.
 
 To enable this feature, set the option C<preview_images_method> to urxvt-full.
 
+=head3 kitty
+
+This only works on Kitty. It requires PIL (or pillow) to work.
+Allows remote image previews, for example in an ssh session.
+
+To enable this feature, set the option C<preview_images_method> to kitty.
+
 =head2 SELECTION
 
 The I<selection> is defined as "All marked files IF THERE ARE ANY, otherwise
@@ -379,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.
 
@@ -607,6 +620,58 @@ A key chain that allows you to quickly change the line mode of all the files of
 the current directory.  For a more permanent solution, use the command
 "default_linemode" in your rc.conf.
 
+=item .n
+
+Apply a new filename filter.
+
+=item .d
+
+Apply the typefilter "directory".
+
+=item .f
+
+Apply the typefilter "file".
+
+=item .l
+
+Apply the typefilter "symlink".
+
+=item .|
+
+Combine the two topmost filters from the filter stack in the "OR"
+relationship, instead of the "AND" used implicitly.
+
+=item .&
+
+Explicitly combine the two topmost filters in the "AND"
+relationship. Usually not needed though might be useful in more
+complicated scenarios.
+
+=item .!
+
+Negate the topmost filter.
+
+=item .r
+
+Rotate the filter stack by N elements. Just confirm with enter to
+rotate by 1, i.e. move the topmost element to the bottom of the stack.
+
+=item .c
+
+Clear the filter stack.
+
+=item .*
+
+Decompose the topmost filter combinator (e.g. C<.!>, C<.|>).
+
+=item .p
+
+Pop the topmost filter from the filter stack.
+
+=item ..
+
+Show the current filter stack state.
+
 =back
 
 =head2 READLINE-LIKE BINDINGS IN THE CONSOLE
@@ -662,7 +727,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
@@ -693,7 +758,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.
 
@@ -767,6 +832,10 @@ 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?
@@ -904,6 +973,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
@@ -983,6 +1057,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?
@@ -1013,6 +1093,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
@@ -1527,6 +1612,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 ad32e4f4..c8c679ec 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.09 (Pod::Simple 3.35)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -129,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RIFLE 1"
-.TH RIFLE 1 "rifle-1.9.1" "05.03.2018" "rifle manual"
+.TH RIFLE 1 "rifle-1.9.1" "2018-06-07" "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/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..350c8359 100644
--- a/ranger/colorschemes/default.py
+++ b/ranger/colorschemes/default.py
@@ -138,6 +138,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/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 2f1480b9..c136db56 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.
 #
 # ===================================================================
@@ -1394,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:
@@ -1537,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 = 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>
 
@@ -1719,6 +1782,7 @@ class yank(Command):
 
     modes = {
         '': 'basename',
+        'name_without_extension': 'basename_without_extension',
         'name': 'basename',
         'dir': 'dirname',
         'path': 'path',
@@ -1751,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 afe9ea42..b112dc2c 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
 
@@ -137,6 +159,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 +169,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.
@@ -236,9 +261,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
 
@@ -253,6 +283,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
 # ===================================================================
@@ -378,6 +412,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 ~
@@ -389,6 +424,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
@@ -404,6 +440,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
@@ -507,6 +544,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!
@@ -518,6 +557,20 @@ map zv    set use_preview_script!
 map zf    console filter%space
 copymap zf zz
 
+# Filter stack
+map .n console filter_stack add name%space
+map .d filter_stack add type d
+map .f filter_stack add type f
+map .l filter_stack add type l
+map .| filter_stack add or
+map .& filter_stack add and
+map .! filter_stack add not
+map .r console filter_stack rotate
+map .c filter_stack clear
+map .* filter_stack decompose
+map .p filter_stack pop
+map .. filter_stack show
+
 # Bookmarks
 map `<any>  enter_bookmark %any
 map '<any>  enter_bookmark %any
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index e2653a76..5a87c1a5 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 -- "$@"
 
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 9f54f24d..94e455a5 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,6 +39,7 @@ 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_progress_bar_in_status_bar': bool,
@@ -64,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,
@@ -90,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,
 }
@@ -99,7 +103,8 @@ ALLOWED_VALUES = {
     'confirm_on_delete': ['multiple', 'always', 'never'],
     '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'],
@@ -112,6 +117,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 443520b1..83cfcc08 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -110,7 +110,8 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         self.settings.set(option_name, self._parse_option_value(option_name, value),
                           localpath, tags)
 
-    def _parse_option_value(self, name, value):
+    def _parse_option_value(  # pylint: disable=too-many-return-statements
+            self, name, value):
         types = self.fm.settings.types_of(name)
         if bool in types:
             if value.lower() in ('false', 'off', '0'):
@@ -124,6 +125,11 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 return int(value)
             except ValueError:
                 pass
+        if float in types:
+            try:
+                return float(value)
+            except ValueError:
+                pass
         if str in types:
             return value
         if list in types:
@@ -409,7 +415,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 = []
@@ -607,6 +614,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
     # --------------------------
@@ -980,6 +1004,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):
@@ -1065,14 +1090,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
 
@@ -1113,6 +1135,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
     # --------------------------
@@ -1188,10 +1239,10 @@ 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)
-        return None
+        i = 1
+        while i in self.tabs:
+            i += 1
+        return self.tab_open(i, path)
 
     def tab_switch(self, path, create_directory=False):
         """Switches to tab of given path, opening a new tab as necessary.
@@ -1239,7 +1290,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
diff --git a/ranger/core/filter_stack.py b/ranger/core/filter_stack.py
new file mode 100644
index 00000000..ff9f4080
--- /dev/null
+++ b/ranger/core/filter_stack.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# This file is part of ranger, the console file manager.
+# License: GNU GPL version 3, see the file "AUTHORS" for details.
+# Author: Wojciech Siewierski <wojciech.siewierski@onet.pl>, 2018
+
+from __future__ import (absolute_import, division, print_function)
+
+import re
+
+from ranger.container.directory import accept_file, InodeFilterConstants
+
+# pylint: disable=too-few-public-methods
+
+
+class BaseFilter(object):
+    def decompose(self):
+        return [self]
+
+
+SIMPLE_FILTERS = {}
+FILTER_COMBINATORS = {}
+
+
+def stack_filter(filter_name):
+    def decorator(cls):
+        SIMPLE_FILTERS[filter_name] = cls
+        return cls
+    return decorator
+
+
+def filter_combinator(combinator_name):
+    def decorator(cls):
+        FILTER_COMBINATORS[combinator_name] = cls
+        return cls
+    return decorator
+
+
+@stack_filter("name")
+class NameFilter(BaseFilter):
+    def __init__(self, pattern):
+        self.pattern = pattern
+        self.regex = re.compile(pattern)
+
+    def __call__(self, fobj):
+        return self.regex.search(fobj.relative_path)
+
+    def __str__(self):
+        return "<Filter: name =~ /{}/>".format(self.pattern)
+
+
+@stack_filter("type")
+class TypeFilter(BaseFilter):
+    type_to_function = {
+        InodeFilterConstants.DIRS:
+        (lambda fobj: fobj.is_directory),
+        InodeFilterConstants.FILES:
+        (lambda fobj: fobj.is_file and not fobj.is_link),
+        InodeFilterConstants.LINKS:
+        (lambda fobj: fobj.is_link),
+    }
+
+    def __init__(self, filetype):
+        if filetype not in self.type_to_function:
+            raise KeyError(filetype)
+        self.filetype = filetype
+
+    def __call__(self, fobj):
+        return self.type_to_function[self.filetype](fobj)
+
+    def __str__(self):
+        return "<Filter: type == '{}'>".format(self.filetype)
+
+
+@filter_combinator("or")
+class OrFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilters = [stack[-2], stack[-1]]
+
+        stack.pop()
+        stack.pop()
+
+        stack.append(self)
+
+    def __call__(self, fobj):
+        # Turn logical AND (accept_file()) into a logical OR with the
+        # De Morgan's laws.
+        return not accept_file(
+            fobj,
+            ((lambda x, f=filt: not f(x))
+             for filt
+             in self.subfilters),
+        )
+
+    def __str__(self):
+        return "<Filter: {}>".format(" or ".join(map(str, self.subfilters)))
+
+    def decompose(self):
+        return self.subfilters
+
+
+@filter_combinator("and")
+class AndFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilters = [stack[-2], stack[-1]]
+
+        stack.pop()
+        stack.pop()
+
+        stack.append(self)
+
+    def __call__(self, fobj):
+        return accept_file(fobj, self.subfilters)
+
+    def __str__(self):
+        return "<Filter: {}>".format(" and ".join(map(str, self.subfilters)))
+
+    def decompose(self):
+        return self.subfilters
+
+
+@filter_combinator("not")
+class NotFilter(BaseFilter):
+    def __init__(self, stack):
+        self.subfilter = stack.pop()
+        stack.append(self)
+
+    def __call__(self, fobj):
+        return not self.subfilter(fobj)
+
+    def __str__(self):
+        return "<Filter: not {}>".format(str(self.subfilter))
+
+    def decompose(self):
+        return [self.subfilter]
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 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 2ae4d16b..598ce243 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -94,10 +94,7 @@ def main(
         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:
@@ -183,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)
 
@@ -236,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):
@@ -340,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:
@@ -395,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/core/tab.py b/ranger/core/tab.py
index 7bb45d75..1d5e69d4 100644
--- a/ranger/core/tab.py
+++ b/ranger/core/tab.py
@@ -4,7 +4,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 import os
-from os.path import abspath, normpath, join, expanduser, isdir, dirname
+from os.path import abspath, normpath, join, expanduser, isdir
 import sys
 
 from ranger.container import settings
@@ -123,11 +123,9 @@ class Tab(FileManagerAware, SettingsAware):  # pylint: disable=too-many-instance
 
         # get the absolute path
         path = normpath(join(self.path, expanduser(path)))
-        selectfile = None
 
         if not isdir(path):
-            selectfile = path
-            path = dirname(path)
+            return False
         new_thisdir = self.fm.get_directory(path)
 
         try:
@@ -157,8 +155,6 @@ class Tab(FileManagerAware, SettingsAware):  # pylint: disable=too-many-instance
         self.thisdir.sort_directories_first = self.fm.settings.sort_directories_first
         self.thisdir.sort_reverse = self.fm.settings.sort_reverse
         self.thisdir.sort_if_outdated()
-        if selectfile:
-            self.thisdir.move_to_obj(selectfile)
         if previous and previous.path != path:
             self.thisfile = self.thisdir.pointed_obj
         else:
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 540a910e..25251533 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;;
 
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..07a76488 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,7 +357,7 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
             self.hook_before_executing(command, self._mimetype, self._app_flags)
             try:
                 if 'r' in flags:
-                    prefix = ['sudo', '-E', 'su', '-mc']
+                    prefix = ['sudo', '-E', 'su', 'root', '-mc']
                 else:
                     prefix = ['/bin/sh', '-c']
 
diff --git a/ranger/gui/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/ui.py b/ranger/gui/ui.py
index 990db0ad..4f76dfab 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',
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/view_base.py b/ranger/gui/widgets/view_base.py
index 80061004..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(),
         )
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()