summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--Dockerfile8
-rw-r--r--Makefile6
-rw-r--r--README.md8
-rw-r--r--doc/cheatsheet.svg4
-rw-r--r--doc/howto-publish-a-release.md34
-rw-r--r--doc/ranger.131
-rw-r--r--doc/ranger.pod26
-rw-r--r--doc/rifle.14
-rw-r--r--examples/plugin_avfs.py33
-rw-r--r--ranger/__init__.py2
-rw-r--r--ranger/config/__init__.py2
-rwxr-xr-xranger/config/commands.py16
-rw-r--r--ranger/config/rc.conf15
-rw-r--r--ranger/config/rifle.conf3
-rw-r--r--ranger/container/settings.py1
-rw-r--r--ranger/core/actions.py19
-rw-r--r--ranger/core/main.py70
-rwxr-xr-xranger/data/scope.sh2
-rwxr-xr-xranger/ext/rifle.py16
-rw-r--r--ranger/gui/curses_shortcuts.py4
-rw-r--r--ranger/gui/widgets/view_base.py66
-rwxr-xr-xsetup.py2
23 files changed, 289 insertions, 95 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c0a16cf..346b18dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 This log documents changes between stable versions.
 
+# 2018-02-22: version 1.9.1
+* Fixed the rifle config backwards compatibility (regression in 1.9.0)
+* Fixed the POSIX compatibility of `Makefile`
+* Fixed `--choosefile`, `--choosefiles` and `--choosedir` so they work
+  with the process substitution (`>(...)` in Bash)
+* Changed the default `gt` binding to `gp` due to a conflict
+* Changed the help message for `--choosefile`, `--choosefiles` and
+  `--choosedir` to avoid confusion
+* Changed the behavior of `:reset` to reload the tags too
+* Added `geeqie` to the default `rifle.conf`
+
 # 2018-01-25: version 1.9.0
 * Fixed memory leak in w3m image preview
 * Fixed `Q` binding, map it to `quitall` instead of `quit!`
@@ -75,6 +86,7 @@ This log documents changes between stable versions.
 * Added support for `$XDG_DATA_HOME`
 * Avoid exiting ranger while copying.  Use `:quit!` to quit while copying.
 * Improved scope.sh (better performance & readability)
+* Improved logs handling by migrating to the python standard logging library (PR #725)
 * Changed `ranger --choosefiles` to return all selected files in all paths
 * Changed interpretation of commands: treat tabs as argument separators
 * Changed `<C-n>` to open new tab in current directory rather than `$HOME`
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 99d3f53e..47e2fa01 100644
--- a/Makefile
+++ b/Makefile
@@ -67,9 +67,9 @@ doc: cleandoc
 
 TEST_PATHS_MAIN = \
 	$(shell find ./ranger -mindepth 1 -maxdepth 1 -type d \
-		-and -not -name '__pycache__' \
-		-and -not -path './ranger/config' \
-		-and -not -path './ranger/data' \
+		! -name '__pycache__' \
+		! -path './ranger/config' \
+		! -path './ranger/data' \
 	) \
 	./ranger/__init__.py \
 	$(shell find ./doc/tools ./examples -type f -name '*.py') \
diff --git a/README.md b/README.md
index e16ea540..ef644ae6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-ranger 1.9.0
+ranger 1.9.1
 ============
 
 [![Build Status](https://travis-ci.org/ranger/ranger.svg?branch=master)](https://travis-ci.org/ranger/ranger)
@@ -29,10 +29,10 @@ About
 -----
 * Authors:     see `AUTHORS` file
 * License:     GNU General Public License Version 3
-* Website:     http://ranger.github.io/
-* Download:    http://ranger.github.io/ranger-stable.tar.gz
+* Website:     https://ranger.github.io/
+* Download:    https://ranger.github.io/ranger-stable.tar.gz
 * Bug reports: https://github.com/ranger/ranger/issues
-* git clone    http://git.sv.gnu.org/r/ranger.git
+* git clone    https://github.com/ranger/ranger.git
 
 
 Design Goals
diff --git a/doc/cheatsheet.svg b/doc/cheatsheet.svg
index f8a97bff..3794a2da 100644
--- a/doc/cheatsheet.svg
+++ b/doc/cheatsheet.svg
@@ -4059,7 +4059,7 @@
          sodipodi:role="line">ranger cheatsheet</tspan></text>
     <a
        id="a5535"
-       xlink:href="http://ranger.github.io"
+       xlink:href="https://ranger.github.io"
        style="fill:#0000ff"
        transform="translate(10,-296.00002)">
       <text
@@ -4073,7 +4073,7 @@
            sodipodi:role="line"
            x="230"
            y="567.36218"
-           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:100%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#0000ff">http://ranger.github.io</tspan></text>
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:100%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#0000ff">https://ranger.github.io</tspan></text>
     </a>
     <text
        xml:space="preserve"
diff --git a/doc/howto-publish-a-release.md b/doc/howto-publish-a-release.md
index 9b6e1dfe..572a2eb7 100644
--- a/doc/howto-publish-a-release.md
+++ b/doc/howto-publish-a-release.md
@@ -1,3 +1,26 @@
+Prepare the "stable" branch
+---------------------------
+Before you can do anything else, you need to decide what should be included in
+the new version.
+
+**Bugfix releases** bump the third number of the version, e.g. 1.9.0 -> 1.9.1.
+They may include bugfix commits that you `git cherry-pick`ed from the master
+branch into the stable branch, or you can just do a fast-forward merge of
+master into stable, if there were only bugfix commits since the last major
+version.  You can also add minor new features that are very likely not causing
+any bugs.  However, there should be absolutely **no** backward-incompatible
+changes, like:
+
+- renamed or removed settings, commands or python functions
+- renamed, removed or reordered function arguments
+- change in syntax of configuration files or in API of configuration scripts
+
+New settings are okay, just make sure a sane default value is defined.
+
+**Major releases** bump the second number of the version, e.g. 1.9.2 -> 1.10.0
+and are necessary if you introduce any breaking changes, like the ones
+mentioned in the list above.
+
 Test everything
 ----------------
 * [ ] `make test`
@@ -13,7 +36,9 @@ Make a release commit
 * [ ] `make man`
 * [ ] Write changelog entry
 * [ ] Think of a witty commit message
-* [ ] Tag signed release
+* [ ] Commit
+* [ ] Tag the signed release with `git tag -a <commit-id>`, using the same
+      commit message as annotation
 * [ ] Push release and tag
 
 Make snapshot and test again
@@ -28,9 +53,11 @@ Update the website
 * [ ] Add the new version as `ranger-stable.tar.gz`
 * [ ] Add the new version as `ranger-X.Y.Z.tar.gz`
 * [ ] Update both signatures `gpg --local-user 0x00FB5CDF --sign --detach-sign <file>`
-* [ ] Update the changelog
 * [ ] Update the man page
-* [ ] Rerun `boobies.py`
+    * [ ] run `make manhtml` in ranger's repository
+    * [ ] copy the generated `doc/ranger.1.html` to the `ranger.github.io` repository
+* [ ] Rebuild the website, see `README.md` in https://github.com/ranger/ranger.github.io
+* [ ] Commit & push the website
 
 Make a PyPI release
 -------------------
@@ -43,7 +70,6 @@ Announce the update
 -------------------
 * [ ] To the mailing list
 * [ ] In the arch linux forum
-* [ ] Write a news entry on savannah
 
 Change back to before
 ---------------------
diff --git a/doc/ranger.1 b/doc/ranger.1
index ab47de61..6358e1d6 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -129,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.0" "2018-01-28" "ranger manual"
+.TH RANGER 1 "ranger-1.9.1" "2018-05-08" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -267,7 +267,7 @@ 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.
@@ -463,7 +463,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
@@ -800,6 +800,13 @@ this pattern will hide all files that start with a dot or end with a tilde.
 .Vb 1
 \& set hidden_filter ^\e.|~$
 .Ve
+.IP "hint_collapse_threshold [int]" 4
+.IX Item "hint_collapse_threshold [int]"
+The key hint lists up to this size have their sublists expanded.
+Otherwise the submaps are replaced with \*(L"...\*(R".
+.IP "hostname_in_titlebar [bool]" 4
+.IX Item "hostname_in_titlebar [bool]"
+Show hostname in titlebar?
 .IP "idle_delay [integer]" 4
 .IX Item "idle_delay [integer]"
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -932,9 +939,6 @@ combination, e.g. \*(L"oN\*(R" to sort from Z to A.
 .IP "status_bar_on_top [bool]" 4
 .IX Item "status_bar_on_top [bool]"
 Put the status bar at the top of the window?
-.IP "hostname_in_titlebar [bool]" 4
-.IX Item "hostname_in_titlebar [bool]"
-Show hostname in titlebar?
 .IP "tilde_in_titlebar [bool]" 4
 .IX Item "tilde_in_titlebar [bool]"
 Abbreviate \f(CW$HOME\fR with ~ in the titlebar (first line) of ranger?
@@ -1421,6 +1425,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.
@@ -1533,13 +1540,13 @@ provided along with the source code.
 \&\s-1GNU\s0 General Public License 3 or (at your option) any later version.
 .SH "LINKS"
 .IX Header "LINKS"
-.IP "Download: <http://ranger.github.io/ranger\-stable.tar.gz>" 4
-.IX Item "Download: <http://ranger.github.io/ranger-stable.tar.gz>"
+.IP "Download: <https://ranger.github.io/ranger\-stable.tar.gz>" 4
+.IX Item "Download: <https://ranger.github.io/ranger-stable.tar.gz>"
 .PD 0
-.IP "The project page: <http://ranger.github.io/>" 4
-.IX Item "The project page: <http://ranger.github.io/>"
-.IP "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>" 4
-.IX Item "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>"
+.IP "The project page: <https://ranger.github.io/>" 4
+.IX Item "The project page: <https://ranger.github.io/>"
+.IP "The mailing list: <https://savannah.nongnu.org/mail/?group=ranger>" 4
+.IX Item "The mailing list: <https://savannah.nongnu.org/mail/?group=ranger>"
 .IP "\s-1IRC\s0 channel: #ranger on freenode.net" 4
 .IX Item "IRC channel: #ranger on freenode.net"
 .PD
diff --git a/doc/ranger.pod b/doc/ranger.pod
index ee869393..9fb4cf0d 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -174,7 +174,7 @@ 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.
@@ -364,7 +364,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.
 
@@ -791,6 +791,15 @@ this pattern will hide all files that start with a dot or end with a tilde.
 
  set hidden_filter ^\.|~$
 
+=item hint_collapse_threshold [int]
+
+The key hint lists up to this size have their sublists expanded.
+Otherwise the submaps are replaced with "...".
+
+=item hostname_in_titlebar [bool]
+
+Show hostname in titlebar?
+
 =item idle_delay [integer]
 
 The delay that ranger idly waits for user input, in milliseconds, with a
@@ -951,10 +960,6 @@ combination, e.g. "oN" to sort from Z to A.
 
 Put the status bar at the top of the window?
 
-=item hostname_in_titlebar [bool]
-
-Show hostname in titlebar?
-
 =item tilde_in_titlebar [bool]
 
 Abbreviate $HOME with ~ in the titlebar (first line) of ranger?
@@ -1507,6 +1512,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.
@@ -1670,11 +1678,11 @@ GNU General Public License 3 or (at your option) any later version.
 
 =over
 
-=item Download: L<http://ranger.github.io/ranger-stable.tar.gz>
+=item Download: L<https://ranger.github.io/ranger-stable.tar.gz>
 
-=item The project page: L<http://ranger.github.io/>
+=item The project page: L<https://ranger.github.io/>
 
-=item The mailing list: L<http://savannah.nongnu.org/mail/?group=ranger>
+=item The mailing list: L<https://savannah.nongnu.org/mail/?group=ranger>
 
 =item IRC channel: #ranger on freenode.net
 
diff --git a/doc/rifle.1 b/doc/rifle.1
index 755ac959..ad32e4f4 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
+.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -129,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RIFLE 1"
-.TH RIFLE 1 "rifle-1.9.0" "2018-01-25" "rifle manual"
+.TH RIFLE 1 "rifle-1.9.1" "05.03.2018" "rifle manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff --git a/examples/plugin_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/ranger/__init__.py b/ranger/__init__.py
index 60a536a0..0d7c8fdc 100644
--- a/ranger/__init__.py
+++ b/ranger/__init__.py
@@ -14,7 +14,7 @@ import os
 
 # Information
 __license__ = 'GPL3'
-__version__ = '1.9.0'
+__version__ = '1.9.1'
 __author__ = __maintainer__ = 'Roman Zimbelmann'
 __email__ = 'hut@hut.pm'
 
diff --git a/ranger/config/__init__.py b/ranger/config/__init__.py
index 71df3cb3..0facbdf8 100644
--- a/ranger/config/__init__.py
+++ b/ranger/config/__init__.py
@@ -1 +1 @@
-"""Default options and configration files"""
+"""Default options and configuration files"""
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index a3837d8e..a7fe68b4 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.
 #
 # ===================================================================
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 39240b06..ce0240c6 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.
@@ -216,6 +217,10 @@ set cd_tab_fuzzy false
 # disable this feature.
 set preview_max_size 0
 
+# The key hint lists up to this size have their sublists expanded.
+# Otherwise the submaps are replaced with "...".
+set hint_collapse_threshold 10
+
 # Add the highlighted file to the path in the titlebar
 set show_selection_in_titlebar true
 
@@ -270,8 +275,8 @@ alias qall  quitall
 alias qall! quitall!
 alias setl  setlocal
 
-alias filter     scout -prt
-alias find       scout -aeit
+alias filter     scout -prts
+alias find       scout -aets
 alias mark       scout -mr
 alias unmark     scout -Mr
 alias search     scout -rs
@@ -387,7 +392,7 @@ map gv cd /var
 map gm cd /media
 map gM cd /mnt
 map gs cd /srv
-map gt cd /tmp
+map gp cd /tmp
 map gr cd /
 map gR eval fm.cd(ranger.RANGERDIR)
 map g/ cd /
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 564fff8d..66e6a5cd 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"
 
@@ -182,6 +182,7 @@ mime ^image, has ristretto, X, flag f = ristretto "$@"
 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 gimp,      X, flag f = gimp -- "$@"
 ext xcf,                    X, flag f = gimp -- "$@"
 
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index d0b094d0..9f54f24d 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -45,6 +45,7 @@ ALLOWED_SETTINGS = {
     'freeze_files': bool,
     'global_inode_type_filter': str,
     'hidden_filter': str,
+    'hint_collapse_threshold': int,
     'hostname_in_titlebar': bool,
     'idle_delay': int,
     'iterm2_font_width': int,
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index aef0d205..6bbb35aa 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -7,7 +7,7 @@ from __future__ import (absolute_import, division, print_function)
 
 import codecs
 import os
-from os import link, symlink, getcwd, listdir, stat
+from os import link, symlink, listdir, stat
 from os.path import join, isdir, realpath, exists
 import re
 import shlex
@@ -74,6 +74,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         if self.metadata:
             self.metadata.reset()
         self.rifle.reload_config()
+        self.fm.tags.sync()
 
     def change_mode(self, mode=None):
         """:change_mode <mode>
@@ -837,11 +838,11 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
 
         self.ui.redraw_main_column()
 
-    def tag_remove(self, paths=None, movedown=None):
-        self.tag_toggle(paths=paths, value=False, movedown=movedown)
+    def tag_remove(self, paths=None, movedown=None, tag=None):
+        self.tag_toggle(paths=paths, value=False, movedown=movedown, tag=tag)
 
-    def tag_add(self, paths=None, movedown=None):
-        self.tag_toggle(paths=paths, value=True, movedown=movedown)
+    def tag_add(self, paths=None, movedown=None, tag=None):
+        self.tag_toggle(paths=paths, value=True, movedown=movedown, tag=tag)
 
     # --------------------------
     # -- Bookmarks
@@ -1372,9 +1373,9 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             self.notify(new_name)
             try:
                 if relative:
-                    relative_symlink(fobj.path, join(getcwd(), new_name))
+                    relative_symlink(fobj.path, join(self.fm.thisdir.path, new_name))
                 else:
-                    symlink(fobj.path, join(getcwd(), new_name))
+                    symlink(fobj.path, join(self.fm.thisdir.path, new_name))
             except OSError as ex:
                 self.notify('Failed to paste symlink: View log for more info',
                             bad=True, exception=ex)
@@ -1383,7 +1384,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
         for fobj in self.copy_buffer:
             new_name = next_available_filename(fobj.basename)
             try:
-                link(fobj.path, join(getcwd(), new_name))
+                link(fobj.path, join(self.fm.thisdir.path, new_name))
             except OSError as ex:
                 self.notify('Failed to paste hardlink: View log for more info',
                             bad=True, exception=ex)
@@ -1391,7 +1392,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
     def paste_hardlinked_subtree(self):
         for fobj in self.copy_buffer:
             try:
-                target_path = join(getcwd(), fobj.basename)
+                target_path = join(self.fm.thisdir.path, fobj.basename)
                 self._recurse_hardlinked_tree(fobj.path, target_path)
             except OSError as ex:
                 self.notify('Failed to paste hardlinked subtree: View log for more info',
diff --git a/ranger/core/main.py b/ranger/core/main.py
index a96b24a1..3d36a357 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -267,17 +267,17 @@ def parse_arguments():
     parser.add_option('--copy-config', type='string', metavar='which',
                       help="copy the default configs to the local config directory. "
                       "Possible values: all, rc, rifle, commands, commands_full, scope")
-    parser.add_option('--choosefile', type='string', metavar='PATH',
+    parser.add_option('--choosefile', type='string', metavar='OUTFILE',
                       help="Makes ranger act like a file chooser. When opening "
                       "a file, it will quit and write the name of the selected "
-                      "file to PATH.")
-    parser.add_option('--choosefiles', type='string', metavar='PATH',
+                      "file to OUTFILE.")
+    parser.add_option('--choosefiles', type='string', metavar='OUTFILE',
                       help="Makes ranger act like a file chooser for multiple files "
                       "at once. When opening a file, it will quit and write the name "
-                      "of all selected files to PATH.")
-    parser.add_option('--choosedir', type='string', metavar='PATH',
+                      "of all selected files to OUTFILE.")
+    parser.add_option('--choosedir', type='string', metavar='OUTFILE',
                       help="Makes ranger act like a directory chooser. When ranger quits"
-                      ", it will write the name of the last visited directory to PATH")
+                      ", it will write the name of the last visited directory to OUTFILE")
     parser.add_option('--selectfile', type='string', metavar='filepath',
                       help="Open ranger with supplied file selected.")
     parser.add_option('--show-only-dirs', action='store_true',
@@ -299,7 +299,7 @@ def parse_arguments():
     def path_init(option):
         argval = args.__dict__[option]
         try:
-            path = os.path.realpath(argval)
+            path = os.path.abspath(argval)
         except OSError as ex:
             sys.stderr.write(
                 '--{0} is not accessible: {1}\n{2}\n'.format(option, argval, str(ex)))
@@ -339,23 +339,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
+            if sys.version_info >= (3, 5):
+                import importlib.util as util
+                spec = util.spec_from_file_location(name, path)
+                module = util.module_from_spec(spec)
+                spec.loader.exec_module(module)
+            elif (3, 3) <= sys.version_info < (3, 5):
+                from importlib.machinery import SourceFileLoader
+                module = SourceFileLoader(name, path).load_module()
+            else:
+                import imp
+                module = imp.load_source(name, path)
+            # pragma pylint: enable=no-name-in-module,import-error,no-member
+            return module
+
+        def load_custom_commands(*paths):
             old_bytecode_setting = sys.dont_write_bytecode
             sys.dont_write_bytecode = True
-            try:
-                import commands as commands_custom
-                fm.commands.load_commands_from_module(commands_custom)
-            except ImportError as ex:
-                LOG.debug("Failed to import custom commands from '%s'", custom_comm_path)
-                LOG.exception(ex)
-            else:
-                LOG.debug("Loaded custom commands from '%s'", custom_comm_path)
+            for custom_comm_path in paths:
+                if os.path.exists(custom_comm_path):
+                    try:
+                        commands_custom = import_file('commands',
+                                                      custom_comm_path)
+                        fm.commands.load_commands_from_module(commands_custom)
+                    except ImportError as ex:
+                        LOG.debug("Failed to import custom commands from '%s'",
+                                  custom_comm_path)
+                        LOG.exception(ex)
+                    else:
+                        LOG.debug("Loaded custom commands from '%s'",
+                                  custom_comm_path)
             sys.dont_write_bytecode = old_bytecode_setting
 
+        system_comm_path = os.path.join(system_confdir, 'commands.py')
+        custom_comm_path = fm.confpath('commands.py')
+        load_custom_commands(system_comm_path, custom_comm_path)
+
         # XXX Load plugins (experimental)
         plugindir = fm.confpath('plugins')
         try:
@@ -394,12 +421,17 @@ 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/data/scope.sh b/ranger/data/scope.sh
index 35021129..540a910e 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -31,7 +31,7 @@ IMAGE_CACHE_PATH="${4}"  # Full path that should be used to cache image preview
 PV_IMAGE_ENABLED="${5}"  # 'True' if image previews are enabled, 'False' otherwise.
 
 FILE_EXTENSION="${FILE_PATH##*.}"
-FILE_EXTENSION_LOWER="${FILE_EXTENSION,,}"
+FILE_EXTENSION_LOWER=$(echo ${FILE_EXTENSION} | tr '[:upper:]' '[:lower:]')
 
 # Settings
 HIGHLIGHT_SIZE_MAX=262143  # 256KiB
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 36f3243e..672b0597 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -21,7 +21,7 @@ import re
 from subprocess import Popen, PIPE
 import sys
 
-__version__ = 'rifle 1.9.0'
+__version__ = 'rifle 1.9.1'
 
 # Options and constants that a user might want to change:
 DEFAULT_PAGER = 'less'
@@ -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):
@@ -343,10 +351,8 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
         else:
             if 'PAGER' not in os.environ:
                 os.environ['PAGER'] = DEFAULT_PAGER
-            if 'VISUAL' not in os.environ and 'EDITOR' not in os.environ:
-                os.environ['VISUAL'] = DEFAULT_EDITOR
-                # necessary for compatibility with old rifle.conf
-                os.environ['EDITOR'] = DEFAULT_EDITOR
+            if 'EDITOR' not in os.environ:
+                os.environ['EDITOR'] = os.environ.get('VISUAL', DEFAULT_EDITOR)
             command = self.hook_command_postprocessing(command)
             self.hook_before_executing(command, self._mimetype, self._app_flags)
             try:
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index ac067d01..14f1e0e4 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -35,7 +35,9 @@ class CursesShortcuts(SettingsAware):
 
         try:
             self.win.addstr(*args)
-        except (curses.error, TypeError):
+        except (curses.error, TypeError, ValueError):
+            # a TypeError changed to ValueError from version 3.5 onwards
+            # https://bugs.python.org/issue22215
             if len(args) > 1:
                 self.win.move(y, x)
 
diff --git a/ranger/gui/widgets/view_base.py b/ranger/gui/widgets/view_base.py
index cb205d92..80061004 100644
--- a/ranger/gui/widgets/view_base.py
+++ b/ranger/gui/widgets/view_base.py
@@ -112,16 +112,62 @@ class ViewBase(Widget, DisplayableContainer):  # pylint: disable=too-many-instan
         self.color_reset()
         self.need_clear = True
         hints = []
-        for key, value in self.fm.ui.keybuffer.pointer.items():
-            key = key_to_string(key)
-            if isinstance(value, dict):
-                text = '...'
-            else:
-                text = value
-            if text.startswith('hint') or text.startswith('chain hint'):
-                continue
-            hints.append((key, text))
-        hints.sort(key=lambda t: t[1])
+
+        def populate_hints(keymap, prefix=""):
+            for key, value in keymap.items():
+                key = prefix + key_to_string(key)
+                if isinstance(value, dict):
+                    populate_hints(value, key)
+                else:
+                    text = value
+                    if text.startswith('hint') or text.startswith('chain hint'):
+                        continue
+                    hints.append((key, text))
+        populate_hints(self.fm.ui.keybuffer.pointer)
+
+        def sort_hints(hints):
+            """Sort the hints by the action string but first group them by the
+            first key.
+
+            """
+            from itertools import groupby
+
+            # groupby needs the list to be sorted.
+            hints.sort(key=lambda t: t[0])
+
+            def group_hints(hints):
+                def first_key(hint):
+                    return hint[0][0]
+
+                def action_string(hint):
+                    return hint[1]
+
+                return (sorted(group, key=action_string)
+                        for _, group
+                        in groupby(
+                            hints,
+                            key=first_key))
+
+            grouped_hints = group_hints(hints)
+
+            # If there are too many hints, collapse the sublists.
+            if len(hints) > self.fm.settings.hint_collapse_threshold:
+                def first_key_in_group(group):
+                    return group[0][0][0]
+                grouped_hints = (
+                    [(first_key_in_group(hint_group), "...")]
+                    if len(hint_group) > 1
+                    else hint_group
+                    for hint_group in grouped_hints
+                )
+
+            # Sort by the first action in group.
+            grouped_hints = sorted(grouped_hints, key=lambda g: g[0][1])
+
+            def flatten(nested_list):
+                return [item for inner_list in nested_list for item in inner_list]
+            return flatten(grouped_hints)
+        hints = sort_hints(hints)
 
         hei = min(self.hei - 1, len(hints))
         ystart = self.hei - hei
diff --git a/setup.py b/setup.py
index f8b8df14..d7c54b00 100755
--- a/setup.py
+++ b/setup.py
@@ -72,7 +72,7 @@ def main():
         author=ranger.__author__,
         author_email=ranger.__email__,
         license=ranger.__license__,
-        url='http://ranger.github.io',
+        url='https://ranger.github.io',
         keywords='file-manager vim console file-launcher file-preview',
         classifiers=[
             'Environment :: Console',