about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authortoonn <toonn@toonn.io>2021-01-05 18:52:25 +0100
committertoonn <toonn@toonn.io>2021-01-05 18:52:25 +0100
commit790d90853504f62e9449844a6481f3955190e35d (patch)
treebaf6c8c4eca175ab0da304c840285bfdbaa4ee09
parent7a0d077e4545f18d30488122fee80ba4df1c7b52 (diff)
parent6fd49695111232df31358a67b11dc5ee3b8dbcfc (diff)
downloadranger-790d90853504f62e9449844a6481f3955190e35d.tar.gz
Merge branch 'chu4ng-fix-files-index' into fix-files-none
-rw-r--r--.pylintrc2
-rw-r--r--Makefile25
-rw-r--r--doc/ranger.114
-rw-r--r--doc/ranger.pod9
-rw-r--r--doc/rifle.16
-rw-r--r--doc/rifle.pod4
-rw-r--r--examples/rifle_different_file_opener.conf4
-rw-r--r--ranger/config/.pylintrc2
-rwxr-xr-xranger/config/commands.py31
-rw-r--r--ranger/config/rc.conf4
-rw-r--r--ranger/config/rifle.conf47
-rw-r--r--ranger/container/settings.py13
-rw-r--r--ranger/core/actions.py2
-rw-r--r--ranger/core/tab.py2
-rw-r--r--ranger/ext/img_display.py2
-rw-r--r--ranger/gui/color.py36
-rw-r--r--ranger/gui/widgets/statusbar.py8
-rw-r--r--ranger/gui/widgets/view_multipane.py102
-rw-r--r--tests/pylint/test_py2_compat.py12
19 files changed, 240 insertions, 85 deletions
diff --git a/.pylintrc b/.pylintrc
index 3fdcbaa8..994ddf62 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -13,7 +13,7 @@ max-branches=16
 [FORMAT]
 max-line-length = 99
 enable=no-absolute-import,old-division
-disable=cyclic-import,duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-break,no-else-continue,no-else-raise,no-else-return,redefined-variable-type,stop-iteration-return,useless-object-inheritance
+disable=cyclic-import,duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-break,no-else-continue,no-else-raise,no-else-return,raise-missing-from,redefined-variable-type,stop-iteration-return,super-with-arguments,useless-object-inheritance
 
 [TYPECHECK]
 ignored-classes=ranger.core.actions.Actions
diff --git a/Makefile b/Makefile
index 82a6153a..0cb35bd4 100644
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,9 @@ FILTER ?= .
 
 CWD = $(shell pwd)
 
+bold := $(shell tput bold)
+normal := $(shell tput sgr0)
+
 default: test compile
 	@echo 'Run `make options` for a list of all options'
 
@@ -91,39 +94,45 @@ TEST_PATHS_MAIN = \
 TEST_PATH_CONFIG = ./ranger/config
 
 test_pylint:
-	@echo "Running pylint..."
+	@echo "$(bold)Running pylint...$(normal)"
 	pylint $(TEST_PATHS_MAIN)
 	pylint --rcfile=$(TEST_PATH_CONFIG)/.pylintrc $(TEST_PATH_CONFIG)
 
 test_flake8:
-	@echo "Running flake8..."
+	@echo "$(bold)Running flake8...$(normal)"
 	flake8 $(TEST_PATHS_MAIN) $(TEST_PATH_CONFIG)
+	@echo
 
 test_doctest:
-	@echo "Running doctests..."
+	@echo "$(bold)Running doctests...$(normal)"
 	@set -e; \
 	for FILE in $(shell grep -IHm 1 doctest -r ranger | grep $(FILTER) | cut -d: -f1); do \
 		echo "Testing $$FILE..."; \
 		RANGER_DOCTEST=1 PYTHONPATH=".:"$$PYTHONPATH ${PYTHON} $$FILE; \
 	done
+	@echo
 
 test_pytest:
-	@echo "Running py.test tests..."
+	@echo "$(bold)Running py.test tests...$(normal)"
 	py.test tests
+	@echo
 
 test_py: test_pylint test_flake8 test_doctest test_pytest test_other
-	@echo "Finished python and documentation tests!"
+	@echo "$(bold)Finished python and documentation tests!$(normal)"
+	@echo
 
 test_shellcheck:
-	@echo "Running shellcheck..."
+	@echo "$(bold)Running shellcheck...$(normal)"
 	sed '2,$$s/^\(\s*\)#/\1/' ./ranger/data/scope.sh | shellcheck -a -
+	@echo
 
 test_other:
-	@echo "Checking completeness of man page..."
+	@echo "$(bold)Checking completeness of man page...$(normal)"
 	@tests/manpage_completion_test.py
+	@echo
 
 test: test_py test_shellcheck
-	@echo "Finished testing: All tests passed!"
+	@echo "$(bold)Finished testing: All tests passed!$(normal)"
 
 doc/ranger.1: doc/ranger.pod README.md
 	pod2man --stderr --center='ranger manual' \
diff --git a/doc/ranger.1 b/doc/ranger.1
index 50d6bb0f..f675da91 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35)
+.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.3" "2020-02-22" "ranger manual"
+.TH RANGER 1 "ranger-1.9.3" "2020-11-18" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -974,6 +974,16 @@ Draw borders around or between the columns? Possible values are:
 \& separators     draw only vertical lines between columns
 \& both           both of the above
 .Ve
+.IP "draw_borders_multipane [string]" 4
+.IX Item "draw_borders_multipane [string]"
+Draw borders around or between the panes. This setting overrides
+\&\fIdraw_borders\fR specifically for the multipane viewmode in case you want
+different border styles in both viewmodes. There's one additional legal value
+on top of those for \fIdraw_borders\fR:
+.Sp
+.Vb 1
+\& active\-pane    draw an outline around the active pane only
+.Ve
 .IP "draw_progress_bar_in_status_bar [bool]" 4
 .IX Item "draw_progress_bar_in_status_bar [bool]"
 Draw a progress bar in the status bar which displays the average state of all
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 3f41467d..bbdfd24e 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -1026,6 +1026,15 @@ Draw borders around or between the columns? Possible values are:
  separators     draw only vertical lines between columns
  both           both of the above
 
+=item draw_borders_multipane [string]
+
+Draw borders around or between the panes. This setting overrides
+I<draw_borders> specifically for the multipane viewmode in case you want
+different border styles in both viewmodes. There's one additional legal value
+on top of those for I<draw_borders>:
+
+ active-pane    draw an outline around the active pane only
+
 =item draw_progress_bar_in_status_bar [bool]
 
 Draw a progress bar in the status bar which displays the average state of all
diff --git a/doc/rifle.1 b/doc/rifle.1
index b3dd6373..6b676162 100644
--- a/doc/rifle.1
+++ b/doc/rifle.1
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RIFLE 1"
-.TH RIFLE 1 "rifle-1.9.3" "2020-02-08" "rifle manual"
+.TH RIFLE 1 "rifle-1.9.3" "2020-11-08" "rifle manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -216,8 +216,8 @@ List all the different methods:
 .PP
 .Vb 4
 \& $ rifle \-l helloworld.py
-\& 0:editor::"$EDITOR" \-\- "$@"
-\& 1:pager::"$PAGER" \-\- "$@"
+\& 0:editor::$EDITOR \-\- "$@"
+\& 1:pager::$PAGER \-\- "$@"
 \& 2:::python \-\- "$1"
 .Ve
 .PP
diff --git a/doc/rifle.pod b/doc/rifle.pod
index e0abaa3b..0cd34478 100644
--- a/doc/rifle.pod
+++ b/doc/rifle.pod
@@ -117,8 +117,8 @@ Specifies the directory for configuration files. Defaults to F<$HOME/.config>.
 List all the different methods:
 
  $ rifle -l helloworld.py
- 0:editor::"$EDITOR" -- "$@"
- 1:pager::"$PAGER" -- "$@"
+ 0:editor::$EDITOR -- "$@"
+ 1:pager::$PAGER -- "$@"
  2:::python -- "$1"
 
 Display its content by opening it with "cat":
diff --git a/examples/rifle_different_file_opener.conf b/examples/rifle_different_file_opener.conf
index 695f27c6..b806b820 100644
--- a/examples/rifle_different_file_opener.conf
+++ b/examples/rifle_different_file_opener.conf
@@ -5,5 +5,5 @@
 else = xdg-open "$1"
 
 # You need an "editor" and "pager" in order to use certain functions in ranger:
-label editor = "$EDITOR" -- "$@"
-label pager  = "$PAGER" -- "$@"
+label editor = $EDITOR -- "$@"
+label pager  = $PAGER -- "$@"
diff --git a/ranger/config/.pylintrc b/ranger/config/.pylintrc
index cc79f97c..316bf189 100644
--- a/ranger/config/.pylintrc
+++ b/ranger/config/.pylintrc
@@ -5,4 +5,4 @@ class-rgx=[a-z][a-z0-9_]{1,30}$
 [FORMAT]
 max-line-length = 99
 max-module-lines=3000
-disable=duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-return
+disable=duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-return,super-with-arguments
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index b7b02e73..9031e7a2 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1465,22 +1465,21 @@ class scout(Command):
     Multiple flags can be combined.  For example, ":scout -gpt" would create
     a :filter-like command using globbing.
     """
-    # pylint: disable=bad-whitespace
-    AUTO_OPEN     = 'a'
-    OPEN_ON_ENTER = 'e'
-    FILTER        = 'f'
-    SM_GLOB       = 'g'
-    IGNORE_CASE   = 'i'
-    KEEP_OPEN     = 'k'
-    SM_LETTERSKIP = 'l'
-    MARK          = 'm'
-    UNMARK        = 'M'
-    PERM_FILTER   = 'p'
-    SM_REGEX      = 'r'
-    SMART_CASE    = 's'
-    AS_YOU_TYPE   = 't'
-    INVERT        = 'v'
-    # pylint: enable=bad-whitespace
+
+    AUTO_OPEN = "a"
+    OPEN_ON_ENTER = "e"
+    FILTER = "f"
+    SM_GLOB = "g"
+    IGNORE_CASE = "i"
+    KEEP_OPEN = "k"
+    SM_LETTERSKIP = "l"
+    MARK = "m"
+    UNMARK = "M"
+    PERM_FILTER = "p"
+    SM_REGEX = "r"
+    SMART_CASE = "s"
+    AS_YOU_TYPE = "t"
+    INVERT = "v"
 
     def __init__(self, *args, **kwargs):
         super(scout, self).__init__(*args, **kwargs)
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index e1ed1b73..d7d79f97 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -627,13 +627,13 @@ eval for arg in "rwxXst": cmd("map +u{0} shell -f chmod u+{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map +g{0} shell -f chmod g+{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map +o{0} shell -f chmod o+{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map +a{0} shell -f chmod a+{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map +{0}  shell -f chmod u+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +{0}  shell -f chmod +{0} %s".format(arg))
 
 eval for arg in "rwxXst": cmd("map -u{0} shell -f chmod u-{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map -g{0} shell -f chmod g-{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map -o{0} shell -f chmod o-{0} %s".format(arg))
 eval for arg in "rwxXst": cmd("map -a{0} shell -f chmod a-{0} %s".format(arg))
-eval for arg in "rwxXst": cmd("map -{0}  shell -f chmod u-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -{0}  shell -f chmod -{0} %s".format(arg))
 
 # ===================================================================
 # == Define keys for the console
diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf
index 8e2a7a40..c9834224 100644
--- a/ranger/config/rifle.conf
+++ b/ranger/config/rifle.conf
@@ -86,15 +86,15 @@ ext x?html?, has w3m,               terminal = w3m "$@"
 #-------------------------------------------
 # Define the "editor" for text files as first action
 mime ^text,  label editor = ${VISUAL:-$EDITOR} -- "$@"
-mime ^text,  label pager  = "$PAGER" -- "$@"
+mime ^text,  label pager  = $PAGER -- "$@"
 !mime ^text, label editor, ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php = ${VISUAL:-$EDITOR} -- "$@"
-!mime ^text, label pager,  ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php = "$PAGER" -- "$@"
+!mime ^text, label pager,  ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php = $PAGER -- "$@"
 
 ext 1                         = man "$1"
 ext s[wmf]c, has zsnes, X     = zsnes "$1"
 ext s[wmf]c, has snes9x-gtk,X = snes9x-gtk "$1"
 ext nes, has fceux, X         = fceux "$1"
-ext exe                       = wine "$1"
+ext exe, has wine             = wine "$1"
 name ^[mM]akefile$            = make
 
 #--------------------------------------------
@@ -118,17 +118,19 @@ ext midi?,        terminal, has wildmidi = wildmidi -- "$@"
 #--------------------------------------------
 # Video/Audio with a GUI
 #-------------------------------------------
-mime ^video|audio, has gmplayer, X, flag f = gmplayer -- "$@"
-mime ^video|audio, has smplayer, X, flag f = smplayer "$@"
-mime ^video,       has mpv,      X, flag f = mpv -- "$@"
-mime ^video,       has mpv,      X, flag f = mpv --fs -- "$@"
-mime ^video,       has mplayer2, X, flag f = mplayer2 -- "$@"
-mime ^video,       has mplayer2, X, flag f = mplayer2 -fs -- "$@"
-mime ^video,       has mplayer,  X, flag f = mplayer -- "$@"
-mime ^video,       has mplayer,  X, flag f = mplayer -fs -- "$@"
-mime ^video|audio, has vlc,      X, flag f = vlc -- "$@"
-mime ^video|audio, has totem,    X, flag f = totem -- "$@"
-mime ^video|audio, has totem,    X, flag f = totem --fullscreen -- "$@"
+mime ^video|^audio, has gmplayer, X, flag f = gmplayer -- "$@"
+mime ^video|^audio, has smplayer, X, flag f = smplayer "$@"
+mime ^video,        has mpv,      X, flag f = mpv -- "$@"
+mime ^video,        has mpv,      X, flag f = mpv --fs -- "$@"
+mime ^video,        has mplayer2, X, flag f = mplayer2 -- "$@"
+mime ^video,        has mplayer2, X, flag f = mplayer2 -fs -- "$@"
+mime ^video,        has mplayer,  X, flag f = mplayer -- "$@"
+mime ^video,        has mplayer,  X, flag f = mplayer -fs -- "$@"
+mime ^video|^audio, has vlc,      X, flag f = vlc -- "$@"
+mime ^video|^audio, has totem,    X, flag f = totem -- "$@"
+mime ^video|^audio, has totem,    X, flag f = totem --fullscreen -- "$@"
+mime ^audio,        has audacity, X, flag f = audacity -- "$@"
+ext aup,            has audacity, X, flag f = audacity -- "$@"
 
 #--------------------------------------------
 # Video without X
@@ -153,7 +155,8 @@ ext pdf, has epdfview, X, flag f = epdfview -- "$@"
 ext pdf, has qpdfview, X, flag f = qpdfview "$@"
 ext pdf, has open,     X, flag f = open "$@"
 
-ext docx?, has catdoc,       terminal = catdoc -- "$@" | "$PAGER"
+ext sc,    has sc,                    = sc -- "$@"
+ext docx?, has catdoc,       terminal = catdoc -- "$@" | $PAGER
 
 ext                        sxc|xlsx?|xlt|xlw|gnm|gnumeric, has gnumeric,    X, flag f = gnumeric -- "$@"
 ext                        sxc|xlsx?|xlt|xlw|gnm|gnumeric, has kspread,     X, flag f = kspread -- "$@"
@@ -174,6 +177,8 @@ ext mobi, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
 ext cbr,  has zathura,      X, flag f = zathura -- "$@"
 ext cbz,  has zathura,      X, flag f = zathura -- "$@"
 
+ext sla,  has scribus,      X, flag f = scribus -- "$@"
+
 #-------------------------------------------
 # Images
 #-------------------------------------------
@@ -193,6 +198,8 @@ mime ^image, has geeqie,    X, flag f = geeqie -- "$@"
 mime ^image, has gpicview,  X, flag f = gpicview -- "$@"
 mime ^image, has gwenview,  X, flag f = gwenview -- "$@"
 mime ^image, has gimp,      X, flag f = gimp -- "$@"
+mime ^image, has krita,     X, flag f = krita -- "$@"
+ext kra,     has krita,     X, flag f = krita -- "$@"
 ext xcf,                    X, flag f = gimp -- "$@"
 
 #-------------------------------------------
@@ -200,15 +207,15 @@ ext xcf,                    X, flag f = gimp -- "$@"
 #-------------------------------------------
 
 # avoid password prompt by providing empty password
-ext 7z, has 7z = 7z -p l "$@" | "$PAGER"
+ext 7z, has 7z = 7z -p l "$@" | $PAGER
 # This requires atool
-ext ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,     has atool = atool --list --each -- "$@" | "$PAGER"
-ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has atool = atool --list --each -- "$@" | "$PAGER"
+ext ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,     has atool = atool --list --each -- "$@" | $PAGER
+ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has atool = atool --list --each -- "$@" | $PAGER
 ext 7z|ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz,  has atool = atool --extract --each -- "$@"
 ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has atool = atool --extract --each -- "$@"
 
 # Listing and extracting archives without atool:
-ext tar|gz|bz2|xz, has tar = tar vvtf "$1" | "$PAGER"
+ext tar|gz|bz2|xz, has tar = tar vvtf "$1" | $PAGER
 ext tar|gz|bz2|xz, has tar = for file in "$@"; do tar vvxf "$file"; done
 ext bz2, has bzip2 = for file in "$@"; do bzip2 -dk "$file"; done
 ext zip, has unzip = unzip -l "$1" | less
@@ -268,7 +275,7 @@ label open, has open     = open -- "$@"
 # Define the editor for non-text files + pager as last action
               !mime ^text, !ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php  = ask
 label editor, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php  = ${VISUAL:-$EDITOR} -- "$@"
-label pager,  !mime ^text, !ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php  = "$PAGER" -- "$@"
+label pager,  !mime ^text, !ext xml|json|csv|tex|py|pl|rb|rs|js|sh|php  = $PAGER -- "$@"
 
 
 ######################################################################
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index 7549325a..e7d6775d 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -14,13 +14,11 @@ from ranger.gui.colorscheme import _colorscheme_name_to_class
 
 # Use these priority constants to trigger events at specific points in time
 # during processing of the signals "setopt" and "setopt.<some_setting_name>"
-# pylint: disable=bad-whitespace
-SIGNAL_PRIORITY_RAW        = 2.0  # signal.value will be raw
-SIGNAL_PRIORITY_SANITIZE   = 1.0  # (Internal) post-processing signal.value
-SIGNAL_PRIORITY_BETWEEN    = 0.6  # sanitized signal.value, old fm.settings.XYZ
-SIGNAL_PRIORITY_SYNC       = 0.2  # (Internal) updating fm.settings.XYZ
+SIGNAL_PRIORITY_RAW = 2.0  # signal.value will be raw
+SIGNAL_PRIORITY_SANITIZE = 1.0  # (Internal) post-processing signal.value
+SIGNAL_PRIORITY_BETWEEN = 0.6  # sanitized signal.value, old fm.settings.XYZ
+SIGNAL_PRIORITY_SYNC = 0.2  # (Internal) updating fm.settings.XYZ
 SIGNAL_PRIORITY_AFTER_SYNC = 0.1  # after fm.settings.XYZ was updated
-# pylint: enable=bad-whitespace
 
 
 ALLOWED_SETTINGS = {
@@ -42,6 +40,7 @@ ALLOWED_SETTINGS = {
     "display_free_space_in_status_bar": bool,
     'display_tags_in_all_columns': bool,
     'draw_borders': str,
+    'draw_borders_multipane': str,
     'draw_progress_bar_in_status_bar': bool,
     'flushinput': bool,
     'freeze_files': bool,
@@ -107,6 +106,8 @@ ALLOWED_VALUES = {
     'cd_tab_case': ['sensitive', 'insensitive', 'smart'],
     'confirm_on_delete': ['multiple', 'always', 'never'],
     'draw_borders': ['none', 'both', 'outline', 'separators'],
+    'draw_borders_multipane': [None, 'none', 'both', 'outline',
+                               'separators', 'active-pane'],
     'line_numbers': ['false', 'absolute', 'relative'],
     'nested_ranger_warning': ['true', 'false', 'error'],
     'one_indexed': [False, True],
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index ba31db58..adfbe94d 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -1025,7 +1025,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             inode = stat(path).st_ino
         inode_path = "{0}{1}".format(str(inode), path)
         if PY3:
-            inode_path = inode_path.encode('utf-8', 'backslashescape')
+            inode_path = inode_path.encode('utf-8', 'backslashreplace')
         return '{0}.jpg'.format(sha512(inode_path).hexdigest())
 
     def get_preview(self, fobj, width, height):
diff --git a/ranger/core/tab.py b/ranger/core/tab.py
index 1771ffa2..b61a77cc 100644
--- a/ranger/core/tab.py
+++ b/ranger/core/tab.py
@@ -73,6 +73,8 @@ class Tab(FileManagerAware, SettingsAware):  # pylint: disable=too-many-instance
             self._pointed_obj = self.thisdir.files[self._pointer]
         except TypeError:
             pass
+        except IndexError:
+            pass
 
     pointer = property(_get_pointer, _set_pointer)
 
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index f056db3b..b738f458 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -683,7 +683,7 @@ class KittyImageDisplayer(ImageDisplayer, FileManagerAware):
         self.stdbout.flush()
         # kitty doesn't seem to reply on deletes, checking like we do in draw()
         # will slows down scrolling with timeouts from select
-        self.image_id -= 1
+        self.image_id = max(0, self.image_id - 1)
         self.fm.ui.win.redrawwin()
         self.fm.ui.win.refresh()
 
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 8f6439c7..81d8b44b 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -49,27 +49,27 @@ def get_color(fg, bg):
     return COLOR_PAIRS[key]
 
 
-# pylint: disable=invalid-name,bad-whitespace
-black      = curses.COLOR_BLACK
-blue       = curses.COLOR_BLUE
-cyan       = curses.COLOR_CYAN
-green      = curses.COLOR_GREEN
-magenta    = curses.COLOR_MAGENTA
-red        = curses.COLOR_RED
-white      = curses.COLOR_WHITE
-yellow     = curses.COLOR_YELLOW
-default    = -1
-
-normal     = curses.A_NORMAL
-bold       = curses.A_BOLD
-blink      = curses.A_BLINK
-reverse    = curses.A_REVERSE
-underline  = curses.A_UNDERLINE
-invisible  = curses.A_INVIS
+# pylint: disable=invalid-name
+black = curses.COLOR_BLACK
+blue = curses.COLOR_BLUE
+cyan = curses.COLOR_CYAN
+green = curses.COLOR_GREEN
+magenta = curses.COLOR_MAGENTA
+red = curses.COLOR_RED
+white = curses.COLOR_WHITE
+yellow = curses.COLOR_YELLOW
+default = -1
+
+normal = curses.A_NORMAL
+bold = curses.A_BOLD
+blink = curses.A_BLINK
+reverse = curses.A_REVERSE
+underline = curses.A_UNDERLINE
+invisible = curses.A_INVIS
 dim = curses.A_DIM
 
 default_colors = (default, default, normal)
-# pylint: enable=invalid-name,bad-whitespace
+# pylint: enable=invalid-name
 
 curses.setupterm()
 # Adding BRIGHT to a color achieves what `bold` was used for.
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index fd44613e..4873c07a 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -42,6 +42,8 @@ class StatusBar(Widget):  # pylint: disable=too-many-instance-attributes
         self.column = column
         self.settings.signal_bind('setopt.display_size_in_status_bar',
                                   self.request_redraw, weak=True)
+        self.fm.signal_bind('tab.layoutchange', self.request_redraw, weak=True)
+        self.fm.signal_bind('setop.viewmode', self.request_redraw, weak=True)
 
     def request_redraw(self):
         self.need_redraw = True
@@ -52,9 +54,13 @@ class StatusBar(Widget):  # pylint: disable=too-many-instance-attributes
     def clear_message(self):
         self.msg = None
 
-    def draw(self):
+    def draw(self):  # pylint: disable=too-many-branches
         """Draw the statusbar"""
 
+        if self.column != self.fm.ui.browser.main_column:
+            self.column = self.fm.ui.browser.main_column
+            self.need_redraw = True
+
         if self.hint and isinstance(self.hint, str):
             if self.old_hint != self.hint:
                 self.need_redraw = True
diff --git a/ranger/gui/widgets/view_multipane.py b/ranger/gui/widgets/view_multipane.py
index 9661d31e..cef8d9a8 100644
--- a/ranger/gui/widgets/view_multipane.py
+++ b/ranger/gui/widgets/view_multipane.py
@@ -3,6 +3,7 @@
 
 from __future__ import (absolute_import, division, print_function)
 
+import curses
 from ranger.gui.widgets.view_base import ViewBase
 from ranger.gui.widgets.browsercolumn import BrowserColumn
 
@@ -16,6 +17,17 @@ class ViewMultipane(ViewBase):  # pylint: disable=too-many-ancestors
         self.fm.signal_bind('tab.change', self._tabchange_handler)
         self.rebuild()
 
+        self.old_draw_borders = self._draw_borders_setting()
+
+    def _draw_borders_setting(self):
+        # If draw_borders_multipane has not been set, it defaults to `None`
+        # and we fallback to using draw_borders. Important to note:
+        # `None` is different from the string "none" referring to no borders
+        if self.settings.draw_borders_multipane is not None:
+            return self.settings.draw_borders_multipane
+        else:
+            return self.settings.draw_borders
+
     def _layoutchange_handler(self):
         if self.fm.ui.browser == self:
             self.rebuild()
@@ -43,13 +55,101 @@ class ViewMultipane(ViewBase):  # pylint: disable=too-many-ancestors
             self.add_child(column)
         self.resize(self.y, self.x, self.hei, self.wid)
 
+    def draw(self):
+        if self.need_clear:
+            self.win.erase()
+            self.need_redraw = True
+            self.need_clear = False
+
+        ViewBase.draw(self)
+
+        if self._draw_borders_setting():
+            draw_borders = self._draw_borders_setting()
+            if draw_borders in ['both', 'true']:   # 'true' for backwards compat.
+                border_types = ['separators', 'outline']
+            else:
+                border_types = [draw_borders]
+            self._draw_borders(border_types)
+        if self.draw_bookmarks:
+            self._draw_bookmarks()
+        elif self.draw_hints:
+            self._draw_hints()
+        elif self.draw_info:
+            self._draw_info(self.draw_info)
+
+    def _draw_border_rectangle(self, left_start, right_end):
+        win = self.win
+        win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
+        win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start)
+        win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)
+        win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
+        # Draw the four corners
+        self.addch(0, left_start, curses.ACS_ULCORNER)
+        self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
+        self.addch(0, right_end, curses.ACS_URCORNER)
+        self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)
+
+    def _draw_borders(self, border_types):
+        # Referenced from ranger.gui.widgets.view_miller
+        win = self.win
+        self.color('in_browser', 'border')
+
+        left_start = 0
+        right_end = self.wid - 1
+
+        # Draw the outline borders
+        if 'active-pane' not in border_types:
+            if 'outline' in border_types:
+                try:
+                    self._draw_border_rectangle(left_start, right_end)
+                except curses.error:
+                    pass
+
+            # Draw the column separators
+            if 'separators' in border_types:
+                for child in self.columns[:-1]:
+                    x = child.x + child.wid
+                    y = self.hei - 1
+                    try:
+                        win.vline(1, x, curses.ACS_VLINE, y - 1)
+                        if 'outline' in border_types:
+                            self.addch(0, x, curses.ACS_TTEE, 0)
+                            self.addch(y, x, curses.ACS_BTEE, 0)
+                        else:
+                            self.addch(0, x, curses.ACS_VLINE, 0)
+                            self.addch(y, x, curses.ACS_VLINE, 0)
+                    except curses.error:
+                        pass
+        else:
+            bordered_column = self.main_column
+            left_start = max(bordered_column.x, 0)
+            right_end = min(left_start + bordered_column.wid, self.wid - 1)
+            try:
+                self._draw_border_rectangle(left_start, right_end)
+            except curses.error:
+                pass
+
     def resize(self, y, x, hei=None, wid=None):
         ViewBase.resize(self, y, x, hei, wid)
+
+        border_type = self._draw_borders_setting()
+        if border_type in ['outline', 'both', 'true', 'active-pane']:
+            # 'true' for backwards compat., no height pad needed for 'separators'
+            pad = 1
+        else:
+            pad = 0
         column_width = int((wid - len(self.columns) + 1) / len(self.columns))
         left = 0
         top = 0
         for column in self.columns:
-            column.resize(top, left, hei, max(1, column_width))
+            column.resize(top + pad, left, hei - pad * 2, max(1, column_width))
             left += column_width + 1
             column.need_redraw = True
         self.need_redraw = True
+
+    def poke(self):
+        ViewBase.poke(self)
+
+        if self.old_draw_borders != self._draw_borders_setting():
+            self.resize(self.y, self.x, self.hei, self.wid)
+            self.old_draw_borders = self._draw_borders_setting()
diff --git a/tests/pylint/test_py2_compat.py b/tests/pylint/test_py2_compat.py
index a5d2e284..bd1ace65 100644
--- a/tests/pylint/test_py2_compat.py
+++ b/tests/pylint/test_py2_compat.py
@@ -5,6 +5,9 @@ import py2_compat
 import astroid
 import pylint.testutils
 
+from sys import version_info
+PY2 = version_info[0] < 3
+
 
 class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
     CHECKER_CLASS = py2_compat.Py2CompatibilityChecker
@@ -42,6 +45,9 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
             self.checker.visit_classdef(from_new)
 
     def test_print_without_import(self):
+        if PY2:
+            return
+
         print_function_call = astroid.extract_node("""
         print("Print function call without importing print_function")
         """)
@@ -73,6 +79,9 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
             self.checker.visit_call(nested_print_function_call)
 
     def test_print_late_import(self):
+        if PY2:
+            return
+
         early_print_function_call = astroid.extract_node("""
         print("Nested print with import in scope") #@
         def f():
@@ -91,6 +100,9 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
             self.checker.visit_call(early_print_function_call)
 
     def test_implicit_format_spec(self):
+        if PY2:
+            return
+
         implicit_format_spec = astroid.extract_node("""
         "{}".format("implicit") #@
         """)