summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--examples/README3
-rw-r--r--examples/plugin_file_filter.py6
-rw-r--r--ranger/config/commands.py77
-rw-r--r--ranger/config/rc.conf6
-rw-r--r--ranger/container/settingobject.py4
-rw-r--r--ranger/core/actions.py1
-rw-r--r--ranger/ext/widestring.py19
-rw-r--r--ranger/fsobject/directory.py16
-rw-r--r--ranger/gui/bar.py2
9 files changed, 112 insertions, 22 deletions
diff --git a/examples/README b/examples/README
index 8606a89d..7c71d9cf 100644
--- a/examples/README
+++ b/examples/README
@@ -1,2 +1,5 @@
 The files in this directory contain applications or extensions of ranger which
 are put here for your inspiration and as references.
+
+In order to use a plugin from this directory, you need to copy it to
+~/.config/ranger/plugins/
diff --git a/examples/plugin_file_filter.py b/examples/plugin_file_filter.py
index 99d026bb..39e4e285 100644
--- a/examples/plugin_file_filter.py
+++ b/examples/plugin_file_filter.py
@@ -6,11 +6,11 @@ import ranger.fsobject.directory
 old_accept_file = ranger.fsobject.directory.accept_file
 
 # Define a new one
-def custom_accept_file(fname, mypath, hidden_filter, name_filter):
-       if hidden_filter and mypath == '/' and fname in ('boot', 'sbin', 'proc', 'sys'):
+def custom_accept_file(fname, directory, hidden_filter, name_filter):
+       if hidden_filter and directory.path == '/' and fname in ('boot', 'sbin', 'proc', 'sys'):
                return False
        else:
-               return old_accept_file(fname, mypath, hidden_filter, name_filter)
+               return old_accept_file(fname, directory, hidden_filter, name_filter)
 
 # Overwrite the old function
 import ranger.fsobject.directory
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 02ef0351..c98f36f9 100644
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1056,6 +1056,81 @@ class pmap(map_):
     context = 'pager'
 
 
+# TODO: Maybe merge this with :find?
+class narrow(Command):
+    """
+    :narrow <string>
+
+    Displays only the files which contain <string> in their basename.
+    Unlike :filter, this command executes the selection and removes the filter
+    again when run.
+    """
+
+    def execute(self):
+        self.cancel() # Clean up
+        if self.rest(1) == "..":
+            self.fm.move(left=1)
+        else:
+            self.fm.move(right=1)
+
+    def cancel(self):
+        self.fm.thisdir.temporary_filter = None
+        self.fm.thisdir.load_content(schedule=False)
+
+    def quick(self):
+        self.fm.thisdir.temporary_filter = self.build_regex(self.rest(1))
+        self.fm.thisdir.load_content(schedule=False)
+
+    def tab(self):
+        if self.fm.env.cwd.files[-1] is not self.fm.env.cf:
+            self.fm.move(down=1)
+        else:
+            # We're at the bottom, so wrap
+            self.fm.move(to=0)
+
+    def build_regex(self, arg):
+        regex = "%s"
+        if arg.endswith("$"):
+            arg = arg[:-1]
+            regex += "$"
+        if arg.startswith("^"):
+            arg = arg[1:]
+            regex = "^" + regex
+
+        case_insensitive = arg.lower() == arg
+        flags = re.I if case_insensitive else 0
+        return re.compile(regex % ".*".join(arg), flags)
+
+
+class travel(narrow, Command):
+    """
+    :travel <string>
+
+    Displays only the files which contain <string> in their basename.
+    Unlike :narrow, this leaves open the console so you can travel faster
+    from directory to directory.
+    """
+
+    def execute(self):
+        thisfile = self.fm.thisfile
+        narrow.execute(self)
+
+        # reopen the console:
+        if thisfile and thisfile.is_directory or self.rest(1) == "..":
+            self.fm.open_console(self.__class__.__name__ + " ")
+            if self.rest(1) != "..":
+                self.fm.block_input(0.5)
+
+    def quick(self):
+        narrow.quick(self)
+        arg = self.rest(1)
+
+        if arg == ".":
+            return False # Make sure we can always use ".."
+        elif arg and len(self.fm.thisdir.files) == 1 or arg == "..":
+            return True
+
+
 class filter(Command):
     """
     :filter <string>
@@ -1067,6 +1142,8 @@ class filter(Command):
         self.fm.set_filter(self.rest(1))
         self.fm.reload_cwd()
 
+    quick = execute
+
 
 class grep(Command):
     """
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index bc9d89e8..ef84f9a0 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -3,10 +3,8 @@
 # To change them, it is recommended to create the file
 # ~/.config/ranger/rc.conf and add your custom commands there.
 #
-# If you copy this whole file there, add this line to your options.py
-# so it is not loaded twice:
-#
-#     load_default_rc = False
+# If you copy this whole file there, you may want to install the plugin
+# "plugins/plugin_skip_default_rc.py" to avoid loading the rc.conf twice.
 #
 # The purpose of this file is mainly to define keybindings and settings.
 # For running more complex python code, please create a plugin in "plugins/" or
diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py
index c5148e6d..b587676a 100644
--- a/ranger/container/settingobject.py
+++ b/ranger/container/settingobject.py
@@ -26,8 +26,8 @@ ALLOWED_SETTINGS = {
     'mouse_enabled': bool,
     'padding_right': bool,
     'preview_directories': bool,
-    'preview_images': bool,
     'preview_files': bool,
+    'preview_images': bool,
     'preview_script': (str, type(None)),
     'save_console_history': bool,
     'scroll_offset': int,
@@ -35,11 +35,11 @@ ALLOWED_SETTINGS = {
     'show_cursor': bool,
     'show_hidden_bookmarks': bool,
     'show_hidden': bool,
-    'status_bar_on_top': bool,
     'sort_case_insensitive': bool,
     'sort_directories_first': bool,
     'sort_reverse': bool,
     'sort': str,
+    'status_bar_on_top': bool,
     'tilde_in_titlebar': bool,
     'unicode_ellipsis': bool,
     'update_title': bool,
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index b02160aa..e0612952 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -44,7 +44,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
     def reset(self):
         """Reset the filemanager, clearing the directory buffer"""
         old_path = self.thisdir.path
-        self.restorable_tabs = {}
         self.previews = {}
         self.garbage_collect(-1)
         self.enter_dir(old_path)
diff --git a/ranger/ext/widestring.py b/ranger/ext/widestring.py
index dac54efd..31e41210 100644
--- a/ranger/ext/widestring.py
+++ b/ranger/ext/widestring.py
@@ -36,7 +36,14 @@ def string_to_charlist(string):
             if east_asian_width(c) in WIDE_SYMBOLS:
                 result.append('')
     else:
-        string = string.decode('utf-8', 'ignore')
+        try:
+            # This raised a "UnicodeEncodeError: 'ascii' codec can't encode
+            # character u'\xe4' in position 10: ordinal not in range(128)"
+            # for me once.  I thought errors='ignore' means IGNORE THE DAMN
+            # ERRORS but apparently it doesn't.
+            string = string.decode('utf-8', 'ignore')
+        except UnicodeEncodeError:
+            return []
         for c in string:
             result.append(c.encode('utf-8'))
             if east_asian_width(c) in WIDE_SYMBOLS:
@@ -46,7 +53,15 @@ def string_to_charlist(string):
 
 class WideString(object):
     def __init__(self, string, chars=None):
-        self.string = string
+        try:
+            self.string = str(string)
+        except UnicodeEncodeError:
+            # Here I assume that string is a "unicode" object, because why else
+            # would str(string) raise a UnicodeEncodeError?
+            try:
+                self.string = string.encode('latin-1', 'ignore')
+            except:
+                self.string = ""
         if chars is None:
             self.chars = string_to_charlist(string)
         else:
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index 8381634e..bd266982 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -37,16 +37,13 @@ def sort_naturally(path):
 def sort_naturally_icase(path):
     return path.basename_natural_lower
 
-def accept_file(fname, dirname, hidden_filter, name_filter):
-    if hidden_filter:
-        try:
-            if hidden_filter.search(fname):
-                return False
-        except:
-            if hidden_filter in fname:
-                return False
+def accept_file(fname, directory, hidden_filter, name_filter):
+    if hidden_filter and hidden_filter.search(fname):
+        return False
     if name_filter and name_filter not in fname:
         return False
+    if directory.temporary_filter and not directory.temporary_filter.search(fname):
+        return False
     return True
 
 class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
@@ -60,6 +57,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
     filenames = None
     files = None
     filter = None
+    temporary_filter = None
     marked_items = None
     scroll_begin = 0
 
@@ -218,7 +216,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 
                 filenames = [mypath + (mypath == '/' and fname or '/' + fname)\
                         for fname in filelist if accept_file(
-                            fname, mypath, hidden_filter, self.filter)]
+                            fname, self, hidden_filter, self.filter)]
                 yield
 
                 self.load_content_mtime = os.stat(mypath).st_mtime
diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py
index a6596bf5..ebd56166 100644
--- a/ranger/gui/bar.py
+++ b/ranger/gui/bar.py
@@ -115,7 +115,7 @@ class ColoredString(object):
         self.string = WideString(string)
         self.lst = lst
         self.fixed = False
-        if not len(string):
+        if not len(string) or not len(self.string.chars):
             self.min_size = 0
         elif PY3:
             self.min_size = utf_char_width(string[0])