about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorNathaniel <natemaia10@gmail.com>2018-09-05 20:42:13 -0700
committerGitHub <noreply@github.com>2018-09-05 20:42:13 -0700
commite79b08089ba6d91c18905e58f7c4749300473b98 (patch)
treed99749a084ae731b60171230ccb131d41bdf462a
parent573bc3aff2c6881928fae984280ff03ad5360fe7 (diff)
parentcc6d9a0b19d250215b822342436e8e81e71c4b4d (diff)
downloadranger-e79b08089ba6d91c18905e58f7c4749300473b98.tar.gz
Merge branch 'master' into patch-4
-rw-r--r--.travis.yml13
-rw-r--r--Makefile13
-rw-r--r--Pipfile15
-rw-r--r--Pipfile.lock175
-rw-r--r--doc/howto-publish-a-release.md2
-rw-r--r--doc/ranger.172
-rw-r--r--doc/ranger.pod66
-rw-r--r--examples/bash_automatic_cd.sh4
-rw-r--r--ranger/__init__.py21
-rwxr-xr-xranger/config/commands.py55
-rw-r--r--ranger/config/rc.conf24
-rw-r--r--ranger/config/rifle.conf1
-rw-r--r--ranger/container/directory.py3
-rw-r--r--ranger/core/actions.py10
-rw-r--r--ranger/core/filter_stack.py134
-rwxr-xr-xranger/ext/rifle.py2
-rw-r--r--ranger/gui/ui.py4
-rw-r--r--ranger/gui/widgets/pager.py4
-rw-r--r--ranger/gui/widgets/titlebar.py5
19 files changed, 586 insertions, 37 deletions
diff --git a/.travis.yml b/.travis.yml
index f9e31256..0efd09fc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,14 @@
-dist: 'trusty'
+dist: 'xenial'
 
 language: 'python'
 python:
-    - '2.7'
-    - '3.4'
-    - '3.5'
+  - '2.7'
+  - '3.4'
+  - '3.5'
 
 install:
-    - 'pip install pytest pylint flake8'
+  - 'pip install pipenv'
+  - 'pipenv update --dev'
 
 script:
-    - 'make test'
+  - 'make test'
diff --git a/Makefile b/Makefile
index 6cd1ec8f..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 ?= /
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/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 5b3afa73..ea010eac 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
+.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -54,16 +54,20 @@
 .\" Avoid warning from groff about undefined register 'F'.
 .de IX
 ..
-.if !\nF .nr F 0
-.if \nF>0 \{\
-.    de IX
-.    tm Index:\\$1\t\\n%\t"\\$2"
+.nr rF 0
+.if \n(.g .if rF .nr rF 1
+.if (\n(rF:(\n(.g==0)) \{\
+.    if \nF \{\
+.        de IX
+.        tm Index:\\$1\t\\n%\t"\\$2"
 ..
-.    if !\nF==2 \{\
-.        nr % 0
-.        nr F 2
+.        if !\nF==2 \{\
+.            nr % 0
+.            nr F 2
+.        \}
 .    \}
 .\}
+.rr rF
 .\"
 .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
 .\" Fear.  Run.  Save yourself.  No user-serviceable parts.
@@ -129,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.1" "2018-06-07" "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
@@ -169,7 +173,7 @@ plugins, sample configuration files and some programs for integrating ranger
 with other software.  They are usually installed to
 \&\fI/usr/share/doc/ranger/examples\fR.
 .PP
-The man page of \fIrifle\fR\|(1) describes the functions of the file opener
+The man page of \fBrifle\fR\|(1) describes the functions of the file opener
 .PP
 The section \fI\s-1LINKS\s0\fR of this man page contains further resources.
 .SH "POSITIONAL ARGUMENTS"
@@ -639,6 +643,9 @@ to use to open the current file selection.
 .IP "cd" 14
 .IX Item "cd"
 Open the console with the content \*(L"cd \*(R"
+.IP "^P" 14
+.IX Item "^P"
+Open the console with the most recent command.
 .IP "Alt\-\fIN\fR" 14
 .IX Item "Alt-N"
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -657,6 +664,41 @@ Close the current tab.  The last tab cannot be closed this way.
 A key chain that allows you to quickly change the line mode of all the files of
 the current directory.  For a more permanent solution, use the command
 \&\*(L"default_linemode\*(R" in your rc.conf.
+.IP ".n" 14
+.IX Item ".n"
+Apply a new filename filter.
+.IP ".d" 14
+.IX Item ".d"
+Apply the typefilter \*(L"directory\*(R".
+.IP ".f" 14
+.IX Item ".f"
+Apply the typefilter \*(L"file\*(R".
+.IP ".l" 14
+.IX Item ".l"
+Apply the typefilter \*(L"symlink\*(R".
+.IP ".|" 14
+Combine the two topmost filters from the filter stack in the \*(L"\s-1OR\*(R"\s0
+relationship, instead of the \*(L"\s-1AND\*(R"\s0 used implicitly.
+.IP ".&" 14
+Explicitly combine the two topmost filters in the \*(L"\s-1AND\*(R"\s0
+relationship. Usually not needed though might be useful in more
+complicated scenarios.
+.IP ".!" 14
+Negate the topmost filter.
+.IP ".r" 14
+.IX Item ".r"
+Rotate the filter stack by N elements. Just confirm with enter to
+rotate by 1, i.e. move the topmost element to the bottom of the stack.
+.IP ".c" 14
+.IX Item ".c"
+Clear the filter stack.
+.IP ".*" 14
+Decompose the topmost filter combinator (e.g. \f(CW\*(C`.!\*(C'\fR, \f(CW\*(C`.|\*(C'\fR).
+.IP ".p" 14
+.IX Item ".p"
+Pop the topmost filter from the filter stack.
+.IP ".." 14
+Show the current filter stack state.
 .SS "READLINE-LIKE \s-1BINDINGS IN THE CONSOLE\s0"
 .IX Subsection "READLINE-LIKE BINDINGS IN THE CONSOLE"
 .IP "^B, ^F" 14
@@ -668,6 +710,12 @@ Move up and down (P for previous, N for Next)
 .IP "^A, ^E" 14
 .IX Item "^A, ^E"
 Move to the start or to the end
+.IP "Alt-B, Alt-LEFT" 14
+.IX Item "Alt-B, Alt-LEFT"
+Move backwards by words.
+.IP "Alt-F, Alt-RIGHT" 14
+.IX Item "Alt-F, Alt-RIGHT"
+Move forwards by words.
 .IP "^D" 14
 .IX Item "^D"
 Delete the current character.
@@ -690,7 +738,7 @@ scrolling to switch directories.
 .SH "SETTINGS"
 .IX Header "SETTINGS"
 This section lists all built-in settings of ranger.  The valid types for the
-value are in [brackets].  The hotkey to toggle the setting is in <brokets>, if
+value are in [brackets].  The hotkey to toggle the setting is in <brakets>, if
 a hotkey exists.
 .PP
 Settings can be changed in the file \fI~/.config/ranger/rc.conf\fR or on the
@@ -1597,7 +1645,7 @@ copy, run:
 .Ve
 .SH "SEE ALSO"
 .IX Header "SEE ALSO"
-\&\fIrifle\fR\|(1)
+\&\fBrifle\fR\|(1)
 .SH "BUGS"
 .IX Header "BUGS"
 Report bugs here: <https://github.com/ranger/ranger/issues>
diff --git a/doc/ranger.pod b/doc/ranger.pod
index fcee60aa..5c668235 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -597,6 +597,10 @@ to use to open the current file selection.
 
 Open the console with the content "cd "
 
+=item ^P
+
+Open the console with the most recent command.
+
 =item Alt-I<N>
 
 Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it
@@ -620,6 +624,58 @@ A key chain that allows you to quickly change the line mode of all the files of
 the current directory.  For a more permanent solution, use the command
 "default_linemode" in your rc.conf.
 
+=item .n
+
+Apply a new filename filter.
+
+=item .d
+
+Apply the typefilter "directory".
+
+=item .f
+
+Apply the typefilter "file".
+
+=item .l
+
+Apply the typefilter "symlink".
+
+=item .|
+
+Combine the two topmost filters from the filter stack in the "OR"
+relationship, instead of the "AND" used implicitly.
+
+=item .&
+
+Explicitly combine the two topmost filters in the "AND"
+relationship. Usually not needed though might be useful in more
+complicated scenarios.
+
+=item .!
+
+Negate the topmost filter.
+
+=item .r
+
+Rotate the filter stack by N elements. Just confirm with enter to
+rotate by 1, i.e. move the topmost element to the bottom of the stack.
+
+=item .c
+
+Clear the filter stack.
+
+=item .*
+
+Decompose the topmost filter combinator (e.g. C<.!>, C<.|>).
+
+=item .p
+
+Pop the topmost filter from the filter stack.
+
+=item ..
+
+Show the current filter stack state.
+
 =back
 
 =head2 READLINE-LIKE BINDINGS IN THE CONSOLE
@@ -638,6 +694,14 @@ Move up and down (P for previous, N for Next)
 
 Move to the start or to the end
 
+=item Alt-B, Alt-LEFT
+
+Move backwards by words.
+
+=item Alt-F, Alt-RIGHT
+
+Move forwards by words.
+
 =item ^D
 
 Delete the current character.
@@ -675,7 +739,7 @@ scrolling to switch directories.
 =head1 SETTINGS
 
 This section lists all built-in settings of ranger.  The valid types for the
-value are in [brackets].  The hotkey to toggle the setting is in <brokets>, if
+value are in [brackets].  The hotkey to toggle the setting is in <brakets>, if
 a hotkey exists.
 
 Settings can be changed in the file F<~/.config/ranger/rc.conf> or on the
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/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/config/commands.py b/ranger/config/commands.py
index 7f5fc764..d177203a 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1545,6 +1545,61 @@ class filter_inode_type(Command):
         self.fm.thisdir.refilter()
 
 
+class filter_stack(Command):
+    """
+    :filter_stack ...
+
+    Manages the filter stack.
+
+        filter_stack add FILTER_TYPE ARGS...
+        filter_stack pop
+        filter_stack decompose
+        filter_stack rotate [N=1]
+        filter_stack clear
+        filter_stack show
+    """
+    def execute(self):
+        from ranger.core.filter_stack import SIMPLE_FILTERS, FILTER_COMBINATORS
+
+        subcommand = self.arg(1)
+
+        if subcommand == "add":
+            try:
+                self.fm.thisdir.filter_stack.append(
+                    SIMPLE_FILTERS[self.arg(2)](self.rest(3))
+                )
+            except KeyError:
+                FILTER_COMBINATORS[self.arg(2)](self.fm.thisdir.filter_stack)
+        elif subcommand == "pop":
+            self.fm.thisdir.filter_stack.pop()
+        elif subcommand == "decompose":
+            inner_filters = self.fm.thisdir.filter_stack.pop().decompose()
+            if inner_filters:
+                self.fm.thisdir.filter_stack.extend(inner_filters)
+        elif subcommand == "clear":
+            self.fm.thisdir.filter_stack = []
+        elif subcommand == "rotate":
+            rotate_by = int(self.arg(2) or 1)
+            self.fm.thisdir.filter_stack = (
+                self.fm.thisdir.filter_stack[-rotate_by:]
+                + self.fm.thisdir.filter_stack[:-rotate_by]
+            )
+        elif subcommand == "show":
+            stack = list(map(str, self.fm.thisdir.filter_stack))
+            pager = self.fm.ui.open_pager()
+            pager.set_source(["Filter stack: "] + stack)
+            pager.move(to=100, percentage=True)
+            return
+        else:
+            self.fm.notify(
+                "Unknown subcommand: {}".format(subcommand),
+                bad=True
+            )
+            return
+
+        self.fm.thisdir.refilter()
+
+
 class grep(Command):
     """:grep <string>
 
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 62331e22..9c1a6a83 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -349,6 +349,8 @@ map r  chain draw_possible_programs; console open_with%%space
 map f  console find%space
 map cd console cd%space
 
+map <C-p> chain console; eval fm.ui.console.history_move(-1)
+
 # Change the line mode
 map Mf linemode filename
 map Mi linemode fileinfo
@@ -557,6 +559,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
@@ -601,8 +617,11 @@ cmap <left>  eval fm.ui.console.move(left=1)
 cmap <right> eval fm.ui.console.move(right=1)
 cmap <home>  eval fm.ui.console.move(right=0, absolute=True)
 cmap <end>   eval fm.ui.console.move(right=-1, absolute=True)
-cmap <a-left>   eval fm.ui.console.move_word(left=1)
-cmap <a-right>  eval fm.ui.console.move_word(right=1)
+cmap <a-b> eval fm.ui.console.move_word(left=1)
+cmap <a-f> eval fm.ui.console.move_word(right=1)
+
+copycmap <a-b> <a-left>
+copycmap <a-f> <a-right>
 
 # Line Editing
 cmap <backspace>  eval fm.ui.console.delete(-1)
@@ -614,6 +633,7 @@ cmap <C-u>        eval fm.ui.console.delete_rest(-1)
 cmap <C-y>        eval fm.ui.console.paste()
 
 # And of course the emacs way
+copycmap <ESC>       <C-g>
 copycmap <up>        <C-p>
 copycmap <down>      <C-n>
 copycmap <left>      <C-b>
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index b1a9bb71..5a87c1a5 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -164,6 +164,7 @@ ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has ooffice,     X, f
 ext djvu, has zathura,X, flag f = zathura -- "$@"
 ext djvu, has evince, X, flag f = evince -- "$@"
 ext djvu, has atril,  X, flag f = atril -- "$@"
+ext djvu, has djview, X, flag f = djview -- "$@"
 
 ext epub, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
 ext mobi, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
diff --git a/ranger/container/directory.py b/ranger/container/directory.py
index e281e6c9..81dabb24 100644
--- a/ranger/container/directory.py
+++ b/ranger/container/directory.py
@@ -155,6 +155,8 @@ class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public
 
         self.marked_items = []
 
+        self.filter_stack = []
+
         self._signal_functions = []
         func = self.signal_function_factory(self.sort)
         self._signal_functions += [func]
@@ -297,6 +299,7 @@ class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public
         if self.temporary_filter:
             temporary_filter_search = self.temporary_filter.search
             filters.append(lambda fobj: temporary_filter_search(fobj.basename))
+        filters.extend(self.filter_stack)
 
         self.files = [f for f in self.files_all if accept_file(f, filters)]
 
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index ae8e33d4..83cfcc08 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -490,7 +490,9 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 mode = narg
             tfile = self.thisfile
             selection = self.thistab.get_selection()
-            if not self.thistab.enter_dir(tfile) and selection:
+            if tfile.is_directory:
+                self.thistab.enter_dir(tfile)
+            elif selection:
                 result = self.execute_file(selection, mode=mode)
                 if result in (False, ASK_COMMAND):
                     self.open_console('open_with ')
@@ -1088,7 +1090,11 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 data[(-1, -1)] = None
                 data['foundpreview'] = False
             elif rcode == 2:
-                data[(-1, -1)] = self.read_text_file(path, 1024 * 32)
+                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
 
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/ext/rifle.py b/ranger/ext/rifle.py
index 672b0597..07a76488 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -357,7 +357,7 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
             self.hook_before_executing(command, self._mimetype, self._app_flags)
             try:
                 if 'r' in flags:
-                    prefix = ['sudo', '-E', 'su', '-mc']
+                    prefix = ['sudo', '-E', 'su', 'root', '-mc']
                 else:
                     prefix = ['/bin/sh', '-c']
 
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 4f76dfab..441e9032 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -365,7 +365,9 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
         DisplayableContainer.draw(self)
         if self._draw_title and self.settings.update_title:
             cwd = self.fm.thisdir.path
-            if cwd.startswith(self.fm.home_path):
+            if self.settings.tilde_in_titlebar \
+               and (cwd == self.fm.home_path
+                    or cwd.startswith(self.fm.home_path + "/")):
                 cwd = '~' + cwd[len(self.fm.home_path):]
             if self.settings.shorten_title:
                 split = cwd.rsplit(os.sep, self.settings.shorten_title)
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index 9afbfd15..d64d4ac1 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -234,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)
@@ -244,5 +244,5 @@ class Pager(Widget):  # pylint: disable=too-many-instance-attributes
                     line = line[startx:self.wid + startx]
                 yield line.rstrip().replace('\r\n', '\n')
             except IndexError:
-                raise StopIteration
+                return
             i += 1
diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py
index 042b4b04..765c1248 100644
--- a/ranger/gui/widgets/titlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -102,8 +102,9 @@ class TitleBar(Widget):
             bar.add(' ', 'hostname', clr, fixed=True)
 
         pathway = self.fm.thistab.pathway
-        if self.settings.tilde_in_titlebar and \
-                self.fm.thisdir.path.startswith(self.fm.home_path):
+        if self.settings.tilde_in_titlebar \
+           and (self.fm.thisdir.path.startswith(self.fm.home_path + "/")
+                or self.fm.thisdir.path == self.fm.home_path):
             pathway = pathway[self.fm.home_path.count('/') + 1:]
             bar.add('~/', 'directory', fixed=True)
 
6 01:40:22 -0400 committer Thomas E. Dickey <dickey@invisible-island.net> 1997-04-16 01:40:22 -0400 snapshot of project "lynx", label v2-7-1ac_0-6' href='/ingrix/lynx-snapshots/commit/src/LYGlobalDefs.h?id=e4409c408eedf320b8845cafdd62b664bec1afd8'>e4409c40 ^
d326f24d ^
6bbc5d0b ^
e4409c40 ^



1fc1d8af ^























6bbc5d0b ^

1fc1d8af ^
6bbc5d0b ^
a2a1ab1e ^
4525eb4b ^
1fc1d8af ^
4525eb4b ^

a2a1ab1e ^
1fc1d8af ^

a2a1ab1e ^

a667eb0b ^
1fc1d8af ^
a667eb0b ^

a2a1ab1e ^
1fc1d8af ^
a2a1ab1e ^

50f9f94b ^
1fc1d8af ^

50f9f94b ^

4bb52da7 ^
1fc1d8af ^

4bb52da7 ^
1fc1d8af ^
4bb52da7 ^
1fc1d8af ^

a93eca0b ^
4525eb4b ^
1fc1d8af ^

d326f24d ^
4525eb4b ^
1fc1d8af ^
9c512bba ^
4525eb4b ^

1fc1d8af ^
4525eb4b ^
1fc1d8af ^
9c512bba ^
a93eca0b ^
1fc1d8af ^


945e8eb6 ^
533c7482 ^
1fc1d8af ^
533c7482 ^

1fc1d8af ^

c7bfda90 ^
50f9f94b ^
1fc1d8af ^


50f9f94b ^
a2a1ab1e ^

1fc1d8af ^




a2a1ab1e ^

1fc1d8af ^
a2a1ab1e ^
6bbc5d0b ^
1fc1d8af ^
a2a1ab1e ^
383b3f2d ^
1fc1d8af ^
383b3f2d ^

2b52e9e6 ^
1fc1d8af ^


2b52e9e6 ^

a2a1ab1e ^









fde43024 ^

1fc1d8af ^



fde43024 ^

fe769404 ^
1fc1d8af ^
fe769404 ^

d1349dd6 ^
1fc1d8af ^
d1349dd6 ^

1fc1d8af ^



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641