about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/pylint.yml2
-rw-r--r--.github/workflows/python.yml4
-rw-r--r--Makefile4
-rw-r--r--README.md3
-rw-r--r--doc/ranger.113
-rw-r--r--doc/ranger.pod15
-rw-r--r--ranger/core/runner.py6
-rwxr-xr-xranger/data/scope.sh40
-rw-r--r--ranger/ext/accumulator.py6
-rw-r--r--ranger/gui/colorscheme.py5
-rw-r--r--ranger/gui/widgets/browsercolumn.py41
-rw-r--r--requirements.txt2
-rw-r--r--tests/pylint/test_py2_compat.py6
13 files changed, 108 insertions, 39 deletions
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index c15f8f64..474b087f 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -5,10 +5,12 @@ on:
     paths:
       - '.github/workflows/pylint.yml'
       - '**.py'
+      - 'requirements.txt'
   pull_request:
     paths:
       - '.github/workflows/pylint.yml'
       - '**.py'
+      - 'requirements.txt'
 
 jobs:
   test_pylint:
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index dc41318e..fe2c7e59 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -28,7 +28,9 @@ jobs:
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        pip install -r requirements.txt
+        # We need to ignore PyLint because we can't install the one we need for
+        # Python 2.7 and 3.5
+        pip install -r <(grep -v pylint requirements.txt)
     - name: Flake8 and test
       run: |
         make test_flake8 test_doctest test_other
diff --git a/Makefile b/Makefile
index 3479bb2d..568ebd42 100644
--- a/Makefile
+++ b/Makefile
@@ -135,13 +135,13 @@ test_other:
 test: test_py test_shellcheck
 	@echo "$(bold)Finished testing: All tests passed!$(normal)"
 
-doc/ranger.1: doc/ranger.pod README.md
+doc/ranger.1: doc/ranger.pod
 	pod2man --stderr --center='ranger manual' \
 		--date='$(NAME)-$(VERSION)' \
 		--release=$(shell date -u '+%Y-%m-%d') \
 		doc/ranger.pod doc/ranger.1
 
-doc/rifle.1: doc/rifle.pod README.md
+doc/rifle.1: doc/rifle.pod
 	pod2man --stderr --center='rifle manual' \
 		--date='$(NAME_RIFLE)-$(VERSION_RIFLE)' \
 		--release=$(shell date -u '+%Y-%m-%d') \
diff --git a/README.md b/README.md
index 10bdcfb5..6598b655 100644
--- a/README.md
+++ b/README.md
@@ -106,8 +106,11 @@ For enhanced file previews (with `scope.sh`):
 * `mediainfo` or `exiftool` for viewing information about media files
 * `odt2txt` for OpenDocument text files (`odt`, `ods`, `odp` and `sxw`)
 * `python` or `jq` for JSON files
+* `jupyter nbconvert` for Jupyter Notebooks
 * `fontimage` for font previews
 * `openscad` for 3D model previews (`stl`, `off`, `dxf`, `scad`, `csg`)
+* `draw.io` for [draw.io](https://app.diagrams.net/) diagram previews
+  (`drawio` extension)
 
 Installing
 ----------
diff --git a/doc/ranger.1 b/doc/ranger.1
index 22c2adc6..cc166604 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.3" "2022-02-27" "ranger manual"
+.TH RANGER 1 "ranger-1.9.3" "2022-03-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
@@ -318,11 +318,13 @@ are automatically used when available but completely optional.
 .IP "\-" 2
 \&\f(CW\*(C`atool\*(C'\fR, \f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR and/or \f(CW\*(C`7z\*(C'\fR to preview archives
 .IP "\-" 2
-\&\f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`tar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR, \f(CW\*(C`unzip\*(C'\fR and/or \f(CW\*(C`zipinfo\*(C'\fR (and \f(CW\*(C`sed\*(C'\fR) to preview archives as their first image
+\&\f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`tar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR, \f(CW\*(C`unzip\*(C'\fR and/or \f(CW\*(C`zipinfo\*(C'\fR (and \f(CW\*(C`sed\*(C'\fR) to preview
+archives as their first image
 .IP "\-" 2
 \&\f(CW\*(C`lynx\*(C'\fR, \f(CW\*(C`w3m\*(C'\fR or \f(CW\*(C`elinks\*(C'\fR to preview html pages
 .IP "\-" 2
-\&\f(CW\*(C`pdftotext\*(C'\fR or \f(CW\*(C`mutool\*(C'\fR (and \f(CW\*(C`fmt\*(C'\fR) for textual pdf previews, \f(CW\*(C`pdftoppm\*(C'\fR to preview as image
+\&\f(CW\*(C`pdftotext\*(C'\fR or \f(CW\*(C`mutool\*(C'\fR (and \f(CW\*(C`fmt\*(C'\fR) for textual pdf previews, \f(CW\*(C`pdftoppm\*(C'\fR to
+preview as image
 .IP "\-" 2
 \&\f(CW\*(C`djvutxt\*(C'\fR for textual DjVu previews, \f(CW\*(C`ddjvu\*(C'\fR to preview as image
 .IP "\-" 2
@@ -337,6 +339,11 @@ are automatically used when available but completely optional.
 \&\f(CW\*(C`python\*(C'\fR or \f(CW\*(C`jq\*(C'\fR for \s-1JSON\s0 files
 .IP "\-" 2
 \&\f(CW\*(C`fontimage\*(C'\fR for font previews
+.IP "\-" 2
+\&\f(CW\*(C`openscad\*(C'\fR for 3D model previews (stl, off, dxf, scad, csg)
+.IP "\-" 2
+\&\f(CW\*(C`draw.io\*(C'\fR for draw.io <https://app.diagrams.net/> diagram previews (drawio
+extension)
 .RE
 .RS 2
 .RE
diff --git a/doc/ranger.pod b/doc/ranger.pod
index bc9f8e21..f3413856 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -254,7 +254,8 @@ C<atool>, C<bsdtar>, C<unrar> and/or C<7z> to preview archives
 
 =item -
 
-C<bsdtar>, C<tar>, C<unrar>, C<unzip> and/or C<zipinfo> (and C<sed>) to preview archives as their first image
+C<bsdtar>, C<tar>, C<unrar>, C<unzip> and/or C<zipinfo> (and C<sed>) to preview
+archives as their first image
 
 =item -
 
@@ -262,7 +263,8 @@ C<lynx>, C<w3m> or C<elinks> to preview html pages
 
 =item -
 
-C<pdftotext> or C<mutool> (and C<fmt>) for textual pdf previews, C<pdftoppm> to preview as image
+C<pdftotext> or C<mutool> (and C<fmt>) for textual pdf previews, C<pdftoppm> to
+preview as image
 
 =item -
 
@@ -292,6 +294,15 @@ C<python> or C<jq> for JSON files
 
 C<fontimage> for font previews
 
+=item -
+
+C<openscad> for 3D model previews (stl, off, dxf, scad, csg)
+
+=item -
+
+C<draw.io> for draw.io L<https://app.diagrams.net/> diagram previews (drawio
+extension)
+
 =back
 
 =back
diff --git a/ranger/core/runner.py b/ranger/core/runner.py
index 1d2b91f7..c5ec697b 100644
--- a/ranger/core/runner.py
+++ b/ranger/core/runner.py
@@ -235,9 +235,11 @@ class Runner(object):  # pylint: disable=too-few-public-methods
 
         if toggle_ui:
             self._activate_ui(False)
+
+        error = None
+        process = None
+
         try:
-            error = None
-            process = None
             self.fm.signal_emit('runner.execute.before',
                                 popen_kws=popen_kws, context=context)
             try:
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 864e39ab..2e9983ee 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -108,7 +108,15 @@ handle_extension() {
             ;;
 
         ## JSON
-        json|ipynb)
+        json)
+            jq --color-output . "${FILE_PATH}" && exit 5
+            python -m json.tool -- "${FILE_PATH}" && exit 5
+            ;;
+
+        ## Jupyter Notebooks
+        ipynb)
+            jupyter nbconvert --to markdown "${FILE_PATH}" --stdout | env COLORTERM=8bit bat --color=always --style=plain --language=markdown && exit 5
+            jupyter nbconvert --to markdown "${FILE_PATH}" --stdout && exit 5
             jq --color-output . "${FILE_PATH}" && exit 5
             python -m json.tool -- "${FILE_PATH}" && exit 5
             ;;
@@ -262,19 +270,23 @@ handle_image() {
     #     mv "${TMPPNG}" "${IMAGE_CACHE_PATH}"
     # }
 
-    # case "${FILE_EXTENSION_LOWER}" in
-    #     ## 3D models
-    #     ## OpenSCAD only supports png image output, and ${IMAGE_CACHE_PATH}
-    #     ## is hardcoded as jpeg. So we make a tempfile.png and just
-    #     ## move/rename it to jpg. This works because image libraries are
-    #     ## smart enough to handle it.
-    #     csg|scad)
-    #         openscad_image "${FILE_PATH}" && exit 6
-    #         ;;
-    #     3mf|amf|dxf|off|stl)
-    #         openscad_image <(echo "import(\"${FILE_PATH}\");") && exit 6
-    #         ;;
-    # esac
+    case "${FILE_EXTENSION_LOWER}" in
+       ## 3D models
+       ## OpenSCAD only supports png image output, and ${IMAGE_CACHE_PATH}
+       ## is hardcoded as jpeg. So we make a tempfile.png and just
+       ## move/rename it to jpg. This works because image libraries are
+       ## smart enough to handle it.
+       # csg|scad)
+       #     openscad_image "${FILE_PATH}" && exit 6
+       #     ;;
+       # 3mf|amf|dxf|off|stl)
+       #     openscad_image <(echo "import(\"${FILE_PATH}\");") && exit 6
+       #     ;;
+       drawio)
+           draw.io -x "${FILE_PATH}" -o "${IMAGE_CACHE_PATH}" \
+               --width "${DEFAULT_SIZE%x*}" && exit 6
+           exit 1;;
+    esac
 }
 
 handle_mime() {
diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py
index a41db634..c34370d8 100644
--- a/ranger/ext/accumulator.py
+++ b/ranger/ext/accumulator.py
@@ -3,6 +3,8 @@
 
 from __future__ import (absolute_import, division, print_function)
 
+from abc import abstractmethod
+
 from ranger.ext.direction import Direction
 
 
@@ -90,8 +92,8 @@ class Accumulator(object):
     def sync_index(self, **kw):
         self.move_to_obj(self.pointed_obj, **kw)
 
-    @staticmethod
-    def get_list():
+    @abstractmethod
+    def get_list(self):
         """OVERRIDE THIS"""
         return []
 
diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py
index 859773fb..22ff053f 100644
--- a/ranger/gui/colorscheme.py
+++ b/ranger/gui/colorscheme.py
@@ -27,6 +27,7 @@ set colorscheme yourschemename
 from __future__ import (absolute_import, division, print_function)
 
 import os.path
+from abc import abstractmethod
 from curses import color_pair
 from io import open
 
@@ -72,8 +73,8 @@ class ColorScheme(object):
         fg, bg, attr = self.get(*flatten(keys))
         return attr | color_pair(get_color(fg, bg))
 
-    @staticmethod
-    def use(_):
+    @abstractmethod
+    def use(self, context):
         """Use the colorscheme to determine the (fg, bg, attr) tuple.
 
         Override this method in your own colorscheme.
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index 3d68f017..54149e0c 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -212,7 +212,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
 
     def _format_line_number(self, linum_format, i, selected_i):
         line_number = i
-        if self.settings.line_numbers == 'relative':
+        if self.settings.line_numbers.lower() == 'relative':
             line_number = abs(selected_i - i)
             if not self.settings.relative_current_zero and line_number == 0:
                 if self.settings.one_indexed:
@@ -273,12 +273,29 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
 
         copied = [f.path for f in self.fm.copy_buffer]
 
+        selected_i = self._get_index_of_selected_file()
+
         # Set the size of the linum text field to the number of digits in the
         # visible files in directory.
-        linum_text_len = len(str(self.scroll_begin + self.hei))
+        def nr_of_digits(number):
+            return len(str(number))
+
+        scroll_end = self.scroll_begin + min(self.hei, len(self.target)) - 1
+        distance_to_top = selected_i - self.scroll_begin
+        distance_to_bottom = scroll_end - selected_i
+        one_indexed_offset = 1 if self.settings.one_indexed else 0
+
+        if self.settings.line_numbers.lower() == "relative":
+            linum_text_len = nr_of_digits(max(distance_to_top,
+                                              distance_to_bottom))
+            if not self.settings.relative_current_zero:
+                linum_text_len = max(nr_of_digits(selected_i
+                                                  + one_indexed_offset),
+                                     linum_text_len)
+        else:
+            linum_text_len = nr_of_digits(scroll_end + one_indexed_offset)
         linum_format = "{0:>" + str(linum_text_len) + "}"
 
-        selected_i = self._get_index_of_selected_file()
         for line in range(self.hei):
             i = line + self.scroll_begin
 
@@ -307,12 +324,15 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
                    drawn.path in copied, tagged_marker, drawn.infostring,
                    drawn.vcsstatus, drawn.vcsremotestatus, self.target.has_vcschild,
                    self.fm.do_cut, current_linemode.name, metakey, active_pane,
-                   self.settings.line_numbers)
+                   self.settings.line_numbers.lower(), linum_text_len)
 
             # Check if current line has not already computed and cached
             if key in drawn.display_data:
                 # Recompute line numbers because they can't be reliably cached.
-                if self.main_column and self.settings.line_numbers != 'false':
+                if (
+                    self.main_column
+                    and self.settings.line_numbers.lower() != 'false'
+                ):
                     line_number_text = self._format_line_number(linum_format,
                                                                 i,
                                                                 selected_i)
@@ -337,7 +357,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
             space = self.wid
 
             # line number field
-            if self.settings.line_numbers != 'false':
+            if self.settings.line_numbers.lower() != 'false':
                 if self.main_column and space - linum_text_len > 2:
                     line_number_text = self._format_line_number(linum_format,
                                                                 i,
@@ -371,15 +391,16 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
             try:
                 infostringdata = current_linemode.infostring(drawn, metadata)
                 if infostringdata:
-                    infostring.append([" " + infostringdata + " ",
+                    infostring.append([" " + infostringdata,
                                        ["infostring"]])
             except NotImplementedError:
                 infostring = self._draw_infostring_display(drawn, space)
             if infostring:
                 infostringlen = self._total_len(infostring)
                 if space - infostringlen > 2:
-                    predisplay_right = infostring + predisplay_right
-                    space -= infostringlen
+                    sep = [[" ", []]] if predisplay_right else []
+                    predisplay_right = infostring + sep + predisplay_right
+                    space -= infostringlen + len(sep)
 
             textstring = self._draw_text_display(text, space)
             textstringlen = self._total_len(textstring)
@@ -445,7 +466,7 @@ class BrowserColumn(Pager):  # pylint: disable=too-many-instance-attributes
         infostring_display = []
         if self.display_infostring and drawn.infostring \
                 and self.settings.display_size_in_main_column:
-            infostring = str(drawn.infostring) + " "
+            infostring = str(drawn.infostring)
             if len(infostring) <= space:
                 infostring_display.append([infostring, ['infostring']])
         return infostring_display
diff --git a/requirements.txt b/requirements.txt
index fc51e82a..c24c4584 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
 flake8
-pylint
+pylint==2.13.9 # Newer versions drop the python3 port checker, which we need
 pytest
diff --git a/tests/pylint/test_py2_compat.py b/tests/pylint/test_py2_compat.py
index 33fc5681..ff13db7c 100644
--- a/tests/pylint/test_py2_compat.py
+++ b/tests/pylint/test_py2_compat.py
@@ -28,6 +28,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=oldstyle_class,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_classdef(oldstyle_class)
 
@@ -60,6 +61,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=print_function_call,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_call(print_function_call)
 
@@ -100,6 +102,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=early_print_function_call,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_call(early_print_function_call)
 
@@ -117,6 +120,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=implicit_format_spec,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_call(implicit_format_spec)
 
@@ -141,6 +145,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=with_Popen,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_with(with_subprocess_Popen)
             self.checker.visit_with(with_Popen)
@@ -160,6 +165,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
                 node=f_string,
                 confidence=HIGH,
             ),
+            ignore_position=True,
         ):
             self.checker.visit_joinedstr(f_string)
         with self.assertNoMessages():