summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.flake82
-rw-r--r--.gitignore3
-rw-r--r--Dockerfile8
-rw-r--r--README.md2
-rw-r--r--doc/ranger.141
-rw-r--r--doc/ranger.pod42
-rw-r--r--examples/plugin_avfs.py33
-rw-r--r--examples/rc_emacs.conf5
-rw-r--r--ranger/colorschemes/default.py2
-rw-r--r--ranger/config/__init__.py2
-rwxr-xr-xranger/config/commands.py26
-rw-r--r--ranger/config/rc.conf22
-rw-r--r--ranger/config/rifle.conf3
-rw-r--r--ranger/container/directory.py5
-rw-r--r--ranger/container/fsobject.py6
-rw-r--r--ranger/container/settings.py1
-rw-r--r--ranger/core/actions.py21
-rw-r--r--ranger/core/fm.py4
-rw-r--r--ranger/core/main.py80
-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.py16
-rwxr-xr-xranger/ext/rifle.py8
-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.py4
-rw-r--r--ranger/gui/widgets/statusbar.py15
-rw-r--r--ranger/gui/widgets/view_base.py4
-rw-r--r--tests/ranger/core/__init__.py0
-rw-r--r--tests/ranger/core/test_main.py18
32 files changed, 303 insertions, 97 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/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/README.md b/README.md
index de0514d3..8abb7265 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ 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`)
diff --git a/doc/ranger.1 b/doc/ranger.1
index f362d263..1290cb58 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32)
+.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -129,7 +129,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.1" "05.03.2018" "ranger manual"
+.TH RANGER 1 "ranger-1.9.1" "2018-05-14" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -145,7 +145,7 @@ ranger \- visual file manager
 [\fB\-\-choosedir\fR=\fItarget\fR] [\fB\-\-selectfile\fR=\fIfilepath\fR]
 [\fB\-\-show\-only\-dirs\fR]
 [\fB\-\-list\-unused\-keys\fR] [\fB\-\-list\-tagged\-files\fR=\fItag\fR]
-[\fB\-\-profile\fR] [\fB\-\-cmd\fR=\fIcommand\fR] [\fIpath\fR]
+[\fB\-\-profile\fR] [\fB\-\-cmd\fR=\fIcommand\fR] [\fIpath ...\fR]
 .SH "DESCRIPTION"
 .IX Header "DESCRIPTION"
 ranger is a console file manager with \s-1VI\s0 key bindings.
@@ -172,6 +172,12 @@ with other software.  They are usually installed to
 The man page of \fIrifle\fR\|(1) describes the functions of the file opener
 .PP
 The section \fI\s-1LINKS\s0\fR of this man page contains further resources.
+.SH "POSITIONAL ARGUMENTS"
+.IX Header "POSITIONAL ARGUMENTS"
+.IP "\fIpath ...\fR" 14
+.IX Item "path ..."
+Each path will be opened in a tab and if the path is a file it will be selected.
+Omitting this is equivalent to providing the current directory.
 .SH "OPTIONS"
 .IX Header "OPTIONS"
 .IP "\fB\-d\fR, \fB\-\-debug\fR" 14
@@ -225,7 +231,8 @@ Allows you to pick a directory with ranger.  When you exit ranger, it will
 write the last visited directory into \fItargetfile\fR.
 .IP "\fB\-\-selectfile\fR=\fItargetfile\fR" 14
 .IX Item "--selectfile=targetfile"
-Open ranger with \fItargetfile\fR selected.
+Open ranger with \fItargetfile\fR selected. This is a legacy option, superseded by
+the behavior for the \s-1POSITIONAL ARGUMENTS.\s0
 .IP "\fB\-\-show\-only\-dirs\fR" 14
 .IX Item "--show-only-dirs"
 Display only the directories. May be used in conjunction with
@@ -267,10 +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.
@@ -463,7 +470,7 @@ sample plugins in the \fI/usr/share/doc/ranger/examples/\fR directory, including
 hello-world plugin that describes this procedure.
 .SH "KEY BINDINGS"
 .IX Header "KEY BINDINGS"
-Key bindings are defined in the file \fIranger/config/rc.conf\fR.  Check this
+Key bindings are defined in the file \fI\f(CI%rangerdir\fI/config/rc.conf\fR.  Check this
 file for a list of all key bindings.  You can copy it to your local
 configuration directory with the \-\-copy\-config=rc option.
 .PP
@@ -599,7 +606,7 @@ Toggle the mark-status of all files
 .IP "V" 14
 .IX Item "V"
 Starts the visual mode, which selects all files between the starting point and
-the cursor until you press \s-1ESC. \s0 To unselect files in the same way, use \*(L"uV\*(R".
+the cursor until you press \s-1ESC.\s0  To unselect files in the same way, use \*(L"uV\*(R".
 .IP "/" 14
 Search for files in the current directory.
 .IP ":" 14
@@ -702,7 +709,7 @@ in ranger.
 .IP "automatically_count_files [bool]" 4
 .IX Item "automatically_count_files [bool]"
 Should ranger count and display the number of files in each directory
-as soon as it's visible?  This gets slow with remote file sytems.  Turning it
+as soon as it's visible?  This gets slow with remote file systems.  Turning it
 off will still allow you to see the number of files after entering the
 directory.
 .IP "autosave_bookmarks [bool]" 4
@@ -764,6 +771,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?
@@ -989,7 +999,7 @@ ranger.  For your convenience, this is a list of the \*(L"public\*(R" commands i
 .Vb 10
 \& alias [newcommand] [oldcommand]
 \& bulkrename
-\& cd [directory]
+\& cd [path]
 \& chain command1[; command2[; command3...]]
 \& chmod octal_number
 \& cmap key command
@@ -1065,10 +1075,10 @@ renaming according to the changes you did in the file.
 .Sp
 This shell script is opened in an editor for you to review.  After you close
 it, it will be executed.
-.IP "cd [\fIdirectory\fR]" 2
-.IX Item "cd [directory]"
-The cd command changes the directory.  The command \f(CW\*(C`:cd \-\*(C'\fR is equivalent to
-typing ``.
+.IP "cd [\fIpath\fR]" 2
+.IX Item "cd [path]"
+The cd command changes the directory.  If path is a file, selects that file.
+The command \f(CW\*(C`:cd \-\*(C'\fR is equivalent to typing ``.
 .IP "chain \fIcommand1\fR[; \fIcommand2\fR[; \fIcommand3\fR...]]" 2
 .IX Item "chain command1[; command2[; command3...]]"
 Combines multiple commands into one, separated by semicolons.
@@ -1425,6 +1435,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 8599a1a6..4cac8ef9 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -14,7 +14,7 @@ B<ranger> [B<--version>] [B<--help>] [B<--debug>] [B<--clean>]
 [B<--choosedir>=I<target>] [B<--selectfile>=I<filepath>]
 [B<--show-only-dirs>]
 [B<--list-unused-keys>] [B<--list-tagged-files>=I<tag>]
-[B<--profile>] [B<--cmd>=I<command>] [I<path>]
+[B<--profile>] [B<--cmd>=I<command>] [I<path ...>]
 
 
 
@@ -53,6 +53,20 @@ The section I<LINKS> of this man page contains further resources.
 
 
 
+=head1 POSITIONAL ARGUMENTS
+
+=over 14
+
+=item I<path ...>
+
+Each path will be opened in a tab and if the path is a file it will be selected.
+Omitting this is equivalent to providing the current directory.
+
+=back
+
+
+
+
 =head1 OPTIONS
 
 =over 14
@@ -117,7 +131,8 @@ write the last visited directory into I<targetfile>.
 
 =item B<--selectfile>=I<targetfile>
 
-Open ranger with I<targetfile> selected.
+Open ranger with I<targetfile> selected. This is a legacy option, superseded by
+the behavior for the POSITIONAL ARGUMENTS.
 
 =item B<--show-only-dirs>
 
@@ -174,10 +189,10 @@ typing I<"<tagnameE<gt>>.
 By default, only text files are previewed, but you can enable external preview
 scripts by setting the option C<use_preview_script> and C<preview_files> to true.
 
-This default script is F<~/.config/ranger/scope.sh>. It contains more
+This default script is F<%rangerdir/data/scope.sh>. It contains more
 documentation and calls to the programs I<lynx> and I<elinks> for html,
 I<highlight> for text/code, I<img2txt> for images, I<atool> for archives,
-I<pdftotext> for PDFs and I<mediainfo> for video and audio files.
+I<pdftotext> or I<mutool> for PDFs and I<mediainfo> for video and audio files.
 
 Install these programs (just the ones you need) and scope.sh will automatically
 use them.
@@ -364,7 +379,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.
 
@@ -678,7 +693,7 @@ in ranger.
 =item automatically_count_files [bool]
 
 Should ranger count and display the number of files in each directory
-as soon as it's visible?  This gets slow with remote file sytems.  Turning it
+as soon as it's visible?  This gets slow with remote file systems.  Turning it
 off will still allow you to see the number of files after entering the
 directory.
 
@@ -752,6 +767,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?
@@ -1020,7 +1039,7 @@ ranger.  For your convenience, this is a list of the "public" commands including
 
  alias [newcommand] [oldcommand]
  bulkrename
- cd [directory]
+ cd [path]
  chain command1[; command2[; command3...]]
  chmod octal_number
  cmap key command
@@ -1100,10 +1119,10 @@ renaming according to the changes you did in the file.
 This shell script is opened in an editor for you to review.  After you close
 it, it will be executed.
 
-=item cd [I<directory>]
+=item cd [I<path>]
 
-The cd command changes the directory.  The command C<:cd -> is equivalent to
-typing ``.
+The cd command changes the directory.  If path is a file, selects that file.
+The command C<:cd -> is equivalent to typing ``.
 
 =item chain I<command1>[; I<command2>[; I<command3>...]]
 
@@ -1512,6 +1531,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/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/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 a3837d8e..7f5fc764 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -3,8 +3,9 @@
 # This configuration file is licensed under the same terms as ranger.
 # ===================================================================
 #
-# NOTE: If you copied this file to ~/.config/ranger/commands_full.py,
-# then it will NOT be loaded by ranger, and only serve as a reference.
+# NOTE: If you copied this file to /etc/ranger/commands_full.py or
+# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger,
+# and only serve as a reference.
 #
 # ===================================================================
 # This file contains ranger's commands.
@@ -13,9 +14,14 @@
 # Note that additional commands are automatically generated from the methods
 # of the class ranger.core.actions.Actions.
 #
-# You can customize commands in the file ~/.config/ranger/commands.py.
-# It has the same syntax as this file.  In fact, you can just copy this
-# file there with `ranger --copy-config=commands' and make your modifications.
+# You can customize commands in the files /etc/ranger/commands.py (system-wide)
+# and ~/.config/ranger/commands.py (per user).
+# They have the same syntax as this file.  In fact, you can just copy this
+# file to ~/.config/ranger/commands_full.py with
+# `ranger --copy-config=commands_full' and make your modifications, don't
+# forget to rename it to commands.py.  You can also use
+# `ranger --copy-config=commands' to copy a short sample commands.py that
+# has everything you need to get started.
 # But make sure you update your configs when you update ranger.
 #
 # ===================================================================
@@ -120,9 +126,10 @@ class echo(Command):
 
 
 class cd(Command):
-    """:cd [-r] <dirname>
+    """:cd [-r] <path>
 
     The cd command changes the directory.
+    If the path is a file, selects that file.
     The command 'cd -' is equivalent to typing ``.
     Using the option "-r" will get you to the real path.
     """
@@ -1393,6 +1400,8 @@ class scout(Command):
                 self.fm.cd(pattern)
             else:
                 self.fm.move(right=1)
+                if self.quickly_executed:
+                    self.fm.block_input(0.5)
 
         if self.KEEP_OPEN in flags and thisdir != self.fm.thisdir:
             # reopen the console:
@@ -1718,6 +1727,7 @@ class yank(Command):
 
     modes = {
         '': 'basename',
+        'name_without_extension': 'basename_without_extension',
         'name': 'basename',
         'dir': 'dirname',
         'path': 'path',
@@ -1750,7 +1760,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 fe00c7c0..6b0802bb 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.
@@ -137,6 +138,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 +148,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.
@@ -253,6 +257,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
 # ===================================================================
@@ -274,8 +282,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
@@ -389,6 +397,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 +413,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 +517,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!
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index e2653a76..b1a9bb71 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"
 
@@ -183,6 +183,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..e281e6c9 100644
--- a/ranger/container/directory.py
+++ b/ranger/container/directory.py
@@ -338,8 +338,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..170ace5a 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -38,6 +38,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,
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 6f4f60e2..e790aaa8 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -1210,10 +1210,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.
@@ -1261,7 +1261,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/fm.py b/ranger/core/fm.py
index c55a3922..d85dd48c 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -424,5 +424,5 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             if not ranger.args.clean and self.settings.save_tabs_on_exit and len(self.tabs) > 1:
                 with open(self.datapath('tabs'), 'a') as fobj:
                     # Don't save active tab since launching ranger changes the active tab
-                    fobj.write('\0'.join(v.path for t, v in self.tabs.items()
-                                         if t != self.current_tab) + '\0\0')
+                    fobj.write('\0'.join(v.path for t, v in self.tabs.items()) +
+                               '\0\0')
diff --git a/ranger/core/main.py b/ranger/core/main.py
index 4adea918..b5d0af77 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -89,14 +89,12 @@ def main(
 
     SettingsAware.settings_set(Settings())
 
+    # TODO: deprecate --selectfile
     if args.selectfile:
         args.selectfile = os.path.abspath(args.selectfile)
         args.paths.insert(0, os.path.dirname(args.selectfile))
 
-    if args.paths:
-        paths = [p[7:] if p.startswith('file:///') else p for p in args.paths]
-    else:
-        paths = [os.environ.get('PWD', os.getcwd())]
+    paths = get_paths(args)
     paths_inaccessible = []
     for path in paths:
         try:
@@ -182,6 +180,7 @@ def main(
             fm.select_file(args.selectfile)
 
         if args.cmd:
+            fm.enter_dir(fm.thistab.path)
             for command in args.cmd:
                 fm.execute_console(command)
 
@@ -235,6 +234,24 @@ https://github.com/ranger/ranger/issues
         return exit_code  # pylint: disable=lost-exception
 
 
+def get_paths(args):
+    if args.paths:
+        prefix = 'file:///'
+        prefix_length = len(prefix)
+        paths = [path[prefix_length:] if path.startswith(prefix) else path for path in args.paths]
+    else:
+        start_directory = os.environ.get('PWD')
+        is_valid_start_directory = start_directory and os.path.exists(start_directory)
+        if not is_valid_start_directory:
+            start_directory = __get_home_directory()
+        paths = [start_directory]
+    return paths
+
+
+def __get_home_directory():
+    return os.path.expanduser('~')
+
+
 def xdg_path(env_var):
     path = os.environ.get(env_var)
     if path and os.path.isabs(path):
@@ -339,23 +356,50 @@ def load_settings(  # pylint: disable=too-many-locals,too-many-branches,too-many
     fm.commands.load_commands_from_module(commands_default)
 
     if not clean:
+        system_confdir = os.path.join(os.sep, 'etc', 'ranger')
+        if os.path.exists(system_confdir):
+            sys.path.append(system_confdir)
         allow_access_to_confdir(ranger.args.confdir, True)
 
         # Load custom commands
-        custom_comm_path = fm.confpath('commands.py')
-        if os.path.exists(custom_comm_path):
+        def import_file(name, path):  # From https://stackoverflow.com/a/67692
+            # pragma pylint: disable=no-name-in-module,import-error,no-member
+            if sys.version_info >= (3, 5):
+                import importlib.util as util
+                spec = util.spec_from_file_location(name, path)
+                module = util.module_from_spec(spec)
+                spec.loader.exec_module(module)
+            elif (3, 3) <= sys.version_info < (3, 5):
+                from importlib.machinery import SourceFileLoader
+                module = SourceFileLoader(name, path).load_module()
+            else:
+                import imp
+                module = imp.load_source(name, path)
+            # pragma pylint: enable=no-name-in-module,import-error,no-member
+            return module
+
+        def load_custom_commands(*paths):
             old_bytecode_setting = sys.dont_write_bytecode
             sys.dont_write_bytecode = True
-            try:
-                import commands as commands_custom
-                fm.commands.load_commands_from_module(commands_custom)
-            except ImportError as ex:
-                LOG.debug("Failed to import custom commands from '%s'", custom_comm_path)
-                LOG.exception(ex)
-            else:
-                LOG.debug("Loaded custom commands from '%s'", custom_comm_path)
+            for custom_comm_path in paths:
+                if os.path.exists(custom_comm_path):
+                    try:
+                        commands_custom = import_file('commands',
+                                                      custom_comm_path)
+                        fm.commands.load_commands_from_module(commands_custom)
+                    except ImportError as ex:
+                        LOG.debug("Failed to import custom commands from '%s'",
+                                  custom_comm_path)
+                        LOG.exception(ex)
+                    else:
+                        LOG.debug("Loaded custom commands from '%s'",
+                                  custom_comm_path)
             sys.dont_write_bytecode = old_bytecode_setting
 
+        system_comm_path = os.path.join(system_confdir, 'commands.py')
+        custom_comm_path = fm.confpath('commands.py')
+        load_custom_commands(system_comm_path, custom_comm_path)
+
         # XXX Load plugins (experimental)
         plugindir = fm.confpath('plugins')
         try:
@@ -394,12 +438,16 @@ def load_settings(  # pylint: disable=too-many-locals,too-many-branches,too-many
         allow_access_to_confdir(ranger.args.confdir, False)
         # Load rc.conf
         custom_conf = fm.confpath('rc.conf')
+        system_conf = os.path.join(system_confdir, 'rc.conf')
         default_conf = fm.relpath('config', 'rc.conf')
 
         custom_conf_is_readable = os.access(custom_conf, os.R_OK)
-        if (os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE' or
-                not custom_conf_is_readable):
+        system_conf_is_readable = os.access(system_conf, os.R_OK)
+        if (os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE'
+                or not (custom_conf_is_readable or system_conf_is_readable)):
             fm.source(default_conf)
+        if system_conf_is_readable:
+            fm.source(system_conf)
         if custom_conf_is_readable:
             fm.source(custom_conf)
 
diff --git a/ranger/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..78d71cb2 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -434,20 +434,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()
 
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index 70215039..672b0597 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):
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..cf322409 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -318,8 +318,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
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/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()