about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authortoonn <toonn@toonn.io>2021-09-07 19:20:56 +0200
committertoonn <toonn@toonn.io>2021-09-07 19:20:56 +0200
commit6f3a6006a0cd074a550e645383c7240d2d97f1ef (patch)
treedddf80df892f850736f585703549ed6db1dbc64c
parent7e6a0aa6a144f6733a8000a2822c107c93e60c1a (diff)
parent99771df4d7ec71b45976bdc85be66c30b725d14e (diff)
downloadranger-6f3a6006a0cd074a550e645383c7240d2d97f1ef.tar.gz
Merge branch 'jakanaka-eva-setlocal'
-rw-r--r--doc/ranger.139
-rw-r--r--doc/ranger.pod39
-rwxr-xr-xranger/config/commands.py106
-rw-r--r--ranger/container/settings.py2
4 files changed, 146 insertions, 40 deletions
diff --git a/doc/ranger.1 b/doc/ranger.1
index 5ce8566a..c005afee 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.3" "2021-09-01" "ranger manual"
+.TH RANGER 1 "ranger-1.9.3" "2021-09-05" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -1288,6 +1288,8 @@ ranger.  For your convenience, this is a list of the \*(L"public\*(R" commands i
 \& search pattern
 \& search_inc pattern
 \& set option value
+\& setinpath [path=<path>] option value
+\& setinregex [re=<regex>] option value
 \& setintag tags option value
 \& setlocal [path=<path>] option value
 \& shell [\-FLAGS...] command
@@ -1693,6 +1695,30 @@ doesn't work for functions and regular expressions. Valid values are:
 \& list           | 1,2,3,4
 \& none           | none
 .Ve
+.IP "setinpath [path=\fIpath\fR] \fIoption\fR \fIvalue\fR" 2
+.IX Item "setinpath [path=path] option value"
+Assigns a new value to an option, but locally for the directory given by
+\&\fIpath\fR. This means, that this option only takes effect when visiting that
+directory. If no path is given, uses the current directory.
+.Sp
+\&\fIpath\fR can be quoted with either single or double quotes to prevent unwanted
+splitting, \fIpath='~/dl dl'\fR or \fIpath=\*(L"~/dl dl\*(R"\fR. You can use \*(L"pattern\*(R" rather
+than \*(L"path\*(R" for consistency with \f(CW\*(C`setinregex\*(C'\fR.
+.IP "setinregex [re=\fIregex\fR] \fIoption\fR \fIvalue\fR" 2
+.IX Item "setinregex [re=regex] option value"
+Assigns a new value to an option, but locally for directories matching
+\&\fIregex\fR. This means, that this option only takes effect when visiting such
+directories. If no regular expression is given, uses the current directory.
+.Sp
+\&\fIregex\fR is a regular expression.  This means that \f(CW\*(C`re=~/dl\*(C'\fR applies to all
+paths that start with \fI~/dl\fR, e.g. \fI~/dl2\fR and \fI~/dl/foo\fR. To avoid this,
+use \f(CW\*(C`path=~/dl$\*(C'\fR.  To specify a folder with special characters
+(.^$\e*+?(){}[]|), escape them with a backslash.
+.Sp
+\&\fIregex\fR can be quoted with either single or double quotes to prevent unwanted
+splitting,. \fIre='~/dl dl$'\fR or \fIre=\*(L"~/dl dl$\*(R"\fR. You can use \*(L"regex\*(R" rather
+than \*(L"re\*(R" to avoid having to remember the spelling and you can use \*(L"pattern\*(R"
+for consistency with \f(CW\*(C`setinpath\*(C'\fR.
 .IP "setintag \fItags\fR \fIoption\fR \fIvalue\fR" 2
 .IX Item "setintag tags option value"
 Assigns a new value to an option, but locally for the directories that are
@@ -1707,16 +1733,7 @@ with the \fIv\fR tag by typing \fI"v\fR, then use this command:
 .Ve
 .IP "setlocal [path=\fIpath\fR] \fIoption\fR \fIvalue\fR" 2
 .IX Item "setlocal [path=path] option value"
-Assigns a new value to an option, but locally for the directory given by
-\&\fIpath\fR. This means, that this option only takes effect when visiting that
-directory. If no path is given, uses the current directory.
-.Sp
-\&\fIpath\fR is a regular expression.  This means that \f(CW\*(C`path=~/dl\*(C'\fR applies to all
-paths that start with \fI~/dl\fR, e.g. \fI~/dl2\fR and \fI~/dl/foo\fR. To avoid this,
-use \f(CW\*(C`path=~/dl$\*(C'\fR.
-.Sp
-\&\fIpath\fR can be quoted with either single or double quotes to prevent unwanted
-splitting. \fIpath='~/dl dl$'\fR or \fIpath=\*(L"~/dl dl$\*(R"\fR
+Alias for \f(CW\*(C`setinpath\*(C'\fR.
 .IP "shell [\-\fIflags\fR] \fIcommand\fR" 2
 .IX Item "shell [-flags] command"
 Run a shell command.  \fIflags\fR are discussed in their own section.
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 3fdc37d3..bf92d1d4 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -1388,6 +1388,8 @@ ranger.  For your convenience, this is a list of the "public" commands including
  search pattern
  search_inc pattern
  set option value
+ setinpath [path=<path>] option value
+ setinregex [re=<regex>] option value
  setintag tags option value
  setlocal [path=<path>] option value
  shell [-FLAGS...] command
@@ -1837,6 +1839,32 @@ doesn't work for functions and regular expressions. Valid values are:
  list           | 1,2,3,4
  none           | none
 
+=item setinpath [path=I<path>] I<option> I<value>
+
+Assigns a new value to an option, but locally for the directory given by
+I<path>. This means, that this option only takes effect when visiting that
+directory. If no path is given, uses the current directory.
+
+I<path> can be quoted with either single or double quotes to prevent unwanted
+splitting, I<path='~/dl dl'> or I<path="~/dl dl">. You can use "pattern" rather
+than "path" for consistency with C<setinregex>.
+
+=item setinregex [re=I<regex>] I<option> I<value>
+
+Assigns a new value to an option, but locally for directories matching
+I<regex>. This means, that this option only takes effect when visiting such
+directories. If no regular expression is given, uses the current directory.
+
+I<regex> is a regular expression.  This means that C<re=~/dl> applies to all
+paths that start with I<~/dl>, e.g. I<~/dl2> and I<~/dl/foo>. To avoid this,
+use C<path=~/dl$>.  To specify a folder with special characters
+(.^$\*+?(){}[]|), escape them with a backslash.
+
+I<regex> can be quoted with either single or double quotes to prevent unwanted
+splitting,. I<re='~/dl dl$'> or I<re="~/dl dl$">. You can use "regex" rather
+than "re" to avoid having to remember the spelling and you can use "pattern"
+for consistency with C<setinpath>.
+
 =item setintag I<tags> I<option> I<value>
 
 Assigns a new value to an option, but locally for the directories that are
@@ -1850,16 +1878,7 @@ with the I<v> tag by typing I<"v>, then use this command:
 
 =item setlocal [path=I<path>] I<option> I<value>
 
-Assigns a new value to an option, but locally for the directory given by
-I<path>. This means, that this option only takes effect when visiting that
-directory. If no path is given, uses the current directory.
-
-I<path> is a regular expression.  This means that C<path=~/dl> applies to all
-paths that start with I<~/dl>, e.g. I<~/dl2> and I<~/dl/foo>. To avoid this,
-use C<path=~/dl$>.
-
-I<path> can be quoted with either single or double quotes to prevent unwanted
-splitting. I<path='~/dl dl$'> or I<path="~/dl dl$">
+Alias for C<setinpath>.
 
 =item shell [-I<flags>] I<command>
 
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index d1d84e91..43c4993f 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -482,36 +482,106 @@ class set_(Command):
         return None
 
 
-class setlocal(set_):
-    """:setlocal path=<regular expression> <option name>=<python expression>
+class setlocal_(set_):
+    """Shared class for setinpath and setinregex
 
-    Gives an option a new value.
+    By implementing the _arg abstract propery you can affect what the name of
+    the pattern/path/regex argument can be, this should be a regular expression
+    without match groups.
+
+    By implementing the _format_arg abstract method you can affect how the
+    argument is formatted as a regular expression.
     """
-    PATH_RE_DQUOTED = re.compile(r'^setlocal\s+path="(.*?)"')
-    PATH_RE_SQUOTED = re.compile(r"^setlocal\s+path='(.*?)'")
-    PATH_RE_UNQUOTED = re.compile(r'^path=(.*?)$')
+    from abc import (ABCMeta, abstractmethod, abstractproperty)
+
+    __metaclass__ = ABCMeta
+
+    @property
+    @abstractmethod
+    def _arg(self):
+        """The name of the option for the path/regex"""
+        raise NotImplementedError
+
+    def __init__(self, *args, **kwargs):
+        super(setlocal_, self).__init__(*args, **kwargs)
+        # We require quoting of paths with whitespace so we have to take care
+        # not to match escaped quotes.
+        self.path_re_dquoted = re.compile(
+            r'^set.+?\s+{arg}="(.*?[^\\])"'.format(arg=self._arg)
+        )
+        self.path_re_squoted = re.compile(
+            r"^set.+?\s+{arg}='(.*?[^\\])'".format(arg=self._arg)
+        )
+        self.path_re_unquoted = re.compile(
+            r'^{arg}=(.+?)$'.format(arg=self._arg)
+        )
 
     def _re_shift(self, match):
         if not match:
             return None
-        path = os.path.expanduser(match.group(1))
-        for _ in range(len(path.split())):
+        path = match.group(1)
+        # Prepend something that behaves like "path=" in case path starts with
+        # whitespace
+        for _ in "={0}".format(path).split():
             self.shift()
-        return path
+        return os.path.expanduser(path)
+
+    @abstractmethod
+    def _format_arg(self, arg):
+        """How to format the argument as a regular expression"""
+        raise NotImplementedError
 
     def execute(self):
-        path = self._re_shift(self.PATH_RE_DQUOTED.match(self.line))
-        if path is None:
-            path = self._re_shift(self.PATH_RE_SQUOTED.match(self.line))
-        if path is None:
-            path = self._re_shift(self.PATH_RE_UNQUOTED.match(self.arg(1)))
-        if path is None and self.fm.thisdir:
-            path = self.fm.thisdir.path
-        if not path:
+        arg = self._re_shift(self.path_re_dquoted.match(self.line))
+        if arg is None:
+            arg = self._re_shift(self.path_re_squoted.match(self.line))
+        if arg is None:
+            arg = self._re_shift(self.path_re_unquoted.match(self.arg(1)))
+        if arg is None and self.fm.thisdir:
+            arg = self.fm.thisdir.path
+        if arg is None:
             return
+        else:
+            arg = self._format_arg(arg)
 
         name, value, _ = self.parse_setting_line()
-        self.fm.set_option_from_string(name, value, localpath=path)
+        self.fm.set_option_from_string(name, value, localpath=arg)
+
+
+class setinpath(setlocal_):
+    """:setinpath path=<path> <option name>=<python expression>
+
+    Sets an option when in a directory that matches <path>, relative paths can
+    match multiple directories, for example, ``path=build`` would match a build
+    directory in any directory. If the <path> contains whitespace it needs to
+    be quoted and nested quotes need to be backslash-escaped. The "path"
+    argument can also be named "pattern" to allow for easier switching with
+    ``setinregex``.
+    """
+    _arg = "(?:path|pattern)"
+
+    def _format_arg(self, arg):
+        return "{0}$".format(re.escape(arg))
+
+
+class setlocal(setinpath):
+    """:setlocal is an alias for :setinpath"""
+
+
+class setinregex(setlocal_):
+    """:setinregex re=<regex> <option name>=<python expression>
+
+    Sets an option when in a specific directory. If the <regex> contains
+    whitespace it needs to be quoted and nested quotes need to be
+    backslash-escaped. Special characters need to be escaped if they are
+    intended to match literally as documented in the ``re`` library
+    documentation. The "re" argument can also be named "regex" or "pattern,"
+    which allows for easier switching with ``setinpath``.
+    """
+    _arg = "(?:re(?:gex)?|pattern)"
+
+    def _format_arg(self, arg):
+        return arg
 
 
 class setintag(set_):
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index e083c5ac..80250d4f 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -281,7 +281,7 @@ class Settings(SignalDispatcher, FileManagerAware):
         if path:
             if path not in self._localsettings:
                 try:
-                    regex = re.compile(re.escape(path))
+                    regex = re.compile(path)
                 except re.error:  # Bad regular expression
                     return
                 self._localregexes[path] = regex