emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/orderless 81248a8dbf 109/204: Merge branch 'dispatcher'


From: ELPA Syncer
Subject: [elpa] externals/orderless 81248a8dbf 109/204: Merge branch 'dispatcher'
Date: Tue, 11 Jan 2022 12:58:22 -0500 (EST)

branch: externals/orderless
commit 81248a8dbfdc546e62fe155c353b36dbd0e95c80
Merge: 99e90bf139 42bca066b9
Author: Omar Antolín <omar.antolin@gmail.com>
Commit: Omar Antolín <omar.antolin@gmail.com>

    Merge branch 'dispatcher'
---
 README.org   | 126 ++++++++++++++++++++++++++++--
 orderless.el | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 330 insertions(+), 41 deletions(-)

diff --git a/README.org b/README.org
index ea1f914e12..39979aa32b 100644
--- a/README.org
+++ b/README.org
@@ -10,8 +10,8 @@ any one of several ways: literally, as a regexp, as an 
initialism, in
 the flex style, or as multiple word prefixes. By default, regexp and
 initialism matches are enabled.
 
-A completion style is a backend for completion and is used from a
-frontend that provides a completion UI. Any completion style can be
+A completion style is a back-end for completion and is used from a
+front-end that provides a completion UI. Any completion style can be
 used with the default Emacs completion UI (sometimes called minibuffer
 tab completion) or with the built-in Icomplete package (which is
 similar to the more well-known Ido Mode). To use a completion style in
@@ -55,8 +55,11 @@ Bug reports are highly welcome and appreciated!
 - [[#screenshot][Screenshot]]
 - [[#customization][Customization]]
   - [[#component-matching-styles][Component matching styles]]
+    - [[#style-dispatchers][Style dispatchers]]
   - [[#component-separator-regexp][Component separator regexp]]
   - [[#faces-for-component-matches][Faces for component matches]]
+  - [[#pattern-compiler][Pattern compiler]]
+  - [[#interactively-changing-the-configuration][Interactively changing the 
configuration]]
 - [[#integration-with-other-completion-uis][Integration with other completion 
UIs]]
   - [[#ivy][Ivy]]
   - [[#selectrum][Selectrum]]
@@ -130,9 +133,57 @@ define new matching styles. The predefined ones are:
 
   This maps =abc= to =a.*b.*c=.
 
-The variable =orderless-component-matching-styles= should be set to a
-list of the desired styles to use. By default it enables the regexp
-and initialism styles.
+- orderless-prefixes :: the component is split at word endings and
+  each piece must match at a word boundary in the candidate, occurring
+  in that order.
+
+  This is similar to the built-in =partial-completion= completion-style.
+  For example, =re-re= matches =query-replace-regexp=, =recode-region= and
+  =magit-remote-list-refs=; =f-d.t= matches =final-draft.txt=.
+
+The variable =orderless-matching-styles= can be set to a list of the
+desired matching styles to use. By default it enables the regexp and
+initialism styles.
+
+*** Style dispatchers
+
+ For more fine-grained control on which matching styles to use for
+ each component of the input string, you can customize the variable
+ =orderless-style-dispatchers=.
+
+ Style dispatchers are functions which take a component, its index in
+ the list of components (starting from 0), and the total number of
+ components, and are used to determine the matching styles used for
+ that specific component, overriding the default matching styles.
+
+ A style dispatcher can either decline to handle the input string or
+ component, or it can return which matching styles to use. It can
+ also, if desired, additionally return a new string to use in place of
+ the given one. Consult the documentation of =orderless-dispatch= for
+ full details.
+
+ As an example, say you wanted the following setup:
+
+ - you normally want components to match as regexps,
+ - except for the first component, which should always match as an
+   initialism ---this is pretty useful for, say,
+   =execute-extended-command= (=M-x=) or =describe-function= (=C-h f=),
+ - and any later component ending in =~= should match (the characters
+   other than the final =~=) in the flex style.
+
+ You can achieve this with the following configuration:
+
+ #+begin_src emacs-lisp
+   (defun flex-if-twiddle (pattern _index _total)
+     (when (string-suffix-p "~" pattern)
+       `(orderless-flex . ,(substring pattern 0 -1))))
+
+   (defun first-initialism (pattern index _total)
+     (if (= index 0) 'orderless-initialism))
+
+   (setq orderless-matching-styles '(orderless-regexp)
+         orderless-style-dispatchers '(first-initialism flex-if-twiddle))
+ #+end_src
 
 ** Component separator regexp
 
@@ -160,6 +211,70 @@ tries each completion style in turn and uses the first one 
returning
 matches. You will only see these particular faces when the =orderless=
 completion is the one that ends up being used, of course.
 
+** Pattern compiler
+
+The default mechanism for turning an input string into a list of
+regexps to match against, configured using =orderless-matching-styles=,
+is probably flexible enough for the vast majority of users. But if you
+want to completely change the mechanism, customize the
+=orderless-pattern-compiler=. It's value should be a function from
+string to lists of regexps. You might find it convenient to use
+=orderless-default-pattern-compiler= as a subroutine in your own pattern
+compiler, it conveniently accepts optional arguments that specify
+lists to use instead of =orderless-matching-styles=.
+
+** Interactively changing the configuration
+
+You might want to change the separator or the matching style
+configuration on the fly while matching. There many possible UIs for
+this: you could toggle between two chosen configurations, cycle among
+several, have a keymap where each key sets a different configurations,
+have a set of named configurations and be prompted (with completion)
+for one of them, popup a [[https://github.com/abo-abo/hydra][hydra]] to choose 
a configuration, etc.
+
+Rather than include commands for any of those on-the-fly configuration
+changes, =orderless= provides a general mechanism to make it easy to
+write such commands yourself. For each variable you might to
+temporarily change there is a corresponding /transient/ variable that
+overrides it when the transient variable is non-nil. You can write
+your own commands to set these transient variable to the desired value
+without clobbering the value of the variables they override. To reset
+the transient variables to =nil= again after each completion session,
+use the following configuration:
+
+#+begin_src emacs-lisp
+  (add-hook 'minibuffer-exit-hook
+            #'orderless-remove-transient-configuration)
+#+end_src
+
+The transient variables provided are:
+
+- =orderless-transient-component-separator=
+- =orderless-transient-matching-styles=
+- =orderless-transient-style-dispatchers=
+
+For example, say you want to use the keybinding =C-l= to make all
+components match literally. You could use the following configuration:
+
+#+begin_src emacs-lisp
+  (defun my/match-components-literally ()
+    "Components match literally for the rest of the session."
+    (interactive)
+    (setq orderless-transient-matching-styles '(orderless-literal)
+          orderless-transient-style-dispatchers nil))
+
+  (add-hook 'minibuffer-exit-hook
+            #'orderless-remove-transient-configuration)
+
+  (define-key minibuffer-local-completion-map (kbd "C-l")
+    #'my/match-components-literally)
+#+end_src
+
+Note that we also set =orderless-transient-style-dispatchers= to =nil=, to
+ensure the literal matching does not get overridden. You may want to
+allow dispatcher overrides, in which case you'd simply remove that
+assignment.
+
 * Integration with other completion UIs
 
 Several excellent completion UIs exist for Emacs in third party
@@ -227,6 +342,7 @@ But there are a couple of points of discomfort:
    #+end_src
 
    (Aren't dynamically scoped variables and the advice system nifty?)
+
 * Related packages
 
 ** Ivy and Helm
diff --git a/orderless.el b/orderless.el
index 0684a0f18b..84923e42f2 100644
--- a/orderless.el
+++ b/orderless.el
@@ -4,7 +4,7 @@
 
 ;; Author: Omar Antolín Camarena <omar@matem.unam.mx>
 ;; Keywords: extensions
-;; Version: 0.3
+;; Version: 0.4
 ;; Homepage: https://github.com/oantolin/orderless
 ;; Package-Requires: ((emacs "24.4"))
 
@@ -46,8 +46,8 @@
 ;; literally, as a regexp, as an initialism, in the flex style, or as
 ;; word prefixes.  It is easy to add new styles: they are functions
 ;; from strings to strings that map a component to a regexp to match
-;; against.  The variable `orderless-component-matching-styles' lists
-;; the matching styles to be used for components, by default it allows
+;; against.  The variable `orderless-matching-styles' lists the
+;; matching styles to be used for components, by default it allows
 ;; regexp and initialism matching.
 
 ;;; Code:
@@ -99,6 +99,19 @@ component regexps."
                  (regexp :tag "Custom regexp"))
   :group 'orderless)
 
+(defcustom orderless-transient-component-separator nil
+  "Component separator regexp override.
+This variabel, if non-nil, overrides `orderless-component-separator'.
+It is meant to be set by commands that interactively change the
+separator.  No such commands are provided with this package, but
+this variable is meant to make writing them simple.  If you do
+use this variable you are likely to want to reset it to nil after
+every completion session, which can be achieved by adding the
+function `orderless-remove-transient-configuration' to the
+`minibuffer-exit-hook'."
+  :type '(choice string nil)
+  :group 'orderless)
+
 (defcustom orderless-match-faces
   [orderless-match-face-0
    orderless-match-face-1
@@ -108,29 +121,100 @@ component regexps."
   :type '(vector 'face)
   :group 'orderless)
 
-(defcustom orderless-component-matching-styles
+(define-obsolete-variable-alias
+  'orderless-component-matching-styles 'orderless-matching-styles
+  "20200502")
+
+(defcustom orderless-matching-styles
   '(orderless-regexp orderless-initialism)
-  "List of allowed component matching styles.
+  "List of component matching styles.
 If this variable is nil, regexp matching is assumed.
 
 A matching style is simply a function from strings to strings
 that takes a component to a regexp to match against.  If the
 resulting regexp has no capturing groups, the entire match is
-highlighted, otherwise just the captured groups are."
-  :type '(set
-          (const :tag "Regexp" orderless-regexp)
-          (const :tag "Literal" orderless-literal)
-          (const :tag "Initialism" orderless-initialism)
-          (const :tag "Strict initialism" orderless-strict-initialism)
-          (const :tag "Strict leading initialism"
-            orderless-strict-leading-initialism)
-          (const :tag "Strict full initialism"
-            orderless-strict-full-initialism)
-          (const :tag "Flex" orderless-flex)
-          (const :tag "Prefixes" orderless-prefixes)
-          (function :tag "Custom matching style"))
+highlighted, otherwise just the captured groups are.  Several are
+provided with this package: try customizing this variable to see
+a list of them."
+  :type 'hook
+  :options '(orderless-regexp
+             orderless-literal
+             orderless-initialism
+             orderless-strict-initialism
+             orderless-strict-leading-initialism
+             orderless-strict-full-initialism
+             orderless-prefixes
+             orderless-flex)
+  :group 'orderless)
+
+(defcustom orderless-style-dispatchers nil
+  "List of style dispatchers.
+Style dispatchers are used to override to the matching styles
+based on the actual component and its place in the list of
+components.  A style dispatcher is a function that takes a string
+and two integers as arguments, it gets called with a component,
+the 0-based index of the component and the total number of
+components.  It can decides what matching styles to use for the
+component and otionally replace the component with a different
+string, or it can decline to handle the component leaving it for
+future dispatchers.  For details see `orderless-dispatch'.
+
+For example, a style dispatcher could arrange for the first
+component to match as an initialism and subsequent components to
+match as literals.  As another example, a style dispatcher could
+arrange for a component starting with `?' to match the rest of
+the component in the `orderless-flex' style.  For more
+information on how this variable is used see
+`orderless-default-pattern-compiler'."
+  :type 'hook
+  :group 'orderless)
+
+(defcustom orderless-transient-matching-styles nil
+  "Component matching styles override.
+This variable, if non-nil, overrides `orderless-matching-styles'.
+It is meant to be set by commands that interactively change the
+matching style configuration.  No such commands are provided with
+this package, but this variable is meant to make writing them
+simple.  If you do use this variable you are likely to want to
+reset it to nil after every completion session, which can be
+achieved by adding the function
+`orderless-remove-transient-configuration' to the
+`minibuffer-exit-hook'."
+  :type 'hook
+  :group 'orderless)
+
+(defcustom orderless-transient-style-dispatchers nil
+  "Component style dispatchers override.
+This variable, if non-nil, overrides `orderless-style-dispatchers'.
+It is meant to be set by commands that interactively change the
+matching style configuration.  No such commands are provided with
+this package, but this variable is meant to make writing them
+simple.  If you do use this variable you are likely to want to
+reset it to nil after every completion session, which can be
+achieved by adding the function
+`orderless-remove-transient-configuration' to the
+`minibuffer-exit-hook'."
+  :type 'hook
+  :group 'orderless)
+
+(defcustom orderless-pattern-compiler #'orderless-default-pattern-compiler
+  "The `orderless' pattern compiler.
+This should be a function that takes an input pattern and returns
+a list of regexps that must all match a candidate in order for
+the candidate to be considered a completion of the pattern.
+
+The default pattern compiler is probably flexible enough for most
+users.  See `orderless-default-pattern-compiler' for details.
+
+The documentation for `orderless-matching-styles' is written
+assuming the default pattern compiler is used, if you change the
+pattern compiler it can, of course, do anything and need not
+consult this variable at all."
+  :type 'function
   :group 'orderless)
 
+;;; Matching styles
+
 (defalias 'orderless-regexp #'identity
   "Match a component as a regexp.
 This is simply the identity function.")
@@ -214,6 +298,8 @@ at a word boundary in the candidate.  This is similar to the
    (cl-loop for prefix in (split-string component "\\>" t)
             collect `(seq word-boundary ,prefix))))
 
+;;; Highlighting matches
+
 (defun orderless--highlight (regexps string)
   "Propertize STRING to highlight a match of each of the REGEXPS.
 Warning: only use this if you know all REGEXPs match!"
@@ -234,26 +320,109 @@ Warning: only use this if you know all REGEXPs match!"
 Warning: only use this if you know all REGEXPs match all STRINGS!
 For the user's convenience, if REGEXPS is a string, it is
 converted to a list of regexps according to the value of
-`orderless-component-matching-styles'."
+`orderless-matching-styles'."
     (when (stringp regexps)
-      (setq regexps (orderless--component-regexps regexps)))
+      (setq regexps (funcall orderless-pattern-compiler regexps)))
     (cl-loop for original in strings
              for string = (copy-sequence original)
              collect (orderless--highlight regexps string)))
 
-(defun orderless--component-regexps (pattern)
-  "Build regexps to match PATTERN.
-Consults `orderless-component-matching-styles' to decide what to
-match."
-  (let ((components (split-string pattern orderless-component-separator t)))
-    (if orderless-component-matching-styles
-        (cl-loop for component in components
-                 collect
-                 (rx-to-string
-                  `(or
-                    ,@(cl-loop for style in orderless-component-matching-styles
-                               collect `(regexp ,(funcall style component))))))
-      components)))
+;;; Compiling patterns to lists of regexps
+
+(defun orderless-remove-transient-configuration ()
+  "Remove all transient orderless configuration.
+Meant to be added to `exit-minibuffer-hook'."
+  (setq orderless-transient-matching-styles nil
+        orderless-transient-component-separator nil))
+
+(defun orderless-dispatch (dispatchers default string &rest args)
+  "Run DISPATCHERS to compute matching styles for STRING.
+
+A style dispatcher is a function that takes a string and possibly
+some extra arguments.  It should either return (a) nil to
+indicate the dispatcher will not handle the string, (b) a new
+string to replace the current string and continue dispatch,
+or (c) the matching styles to use and, if needed, a new string to
+use in place of the current one (for example, a dispatcher can
+decide which style to use based on a suffix of the string and
+then it must also return the component stripped of the suffix).
+
+More precisely, the return value of a style dispatcher can be of
+one of the following forms:
+
+- nil (to continue dispatching)
+
+- a string (to replace the component and continue dispatching),
+
+- a matching style or non-empty list of matching styles to
+  return,
+
+- a `cons' whose `car' is either as in the previous case or
+  nil (to request returning the DEFAULT matching styles), and
+  whose `cdr' is a string (to replace the current one).
+
+This function tries all DISPATCHERS in sequence until one returns
+a list of styles (passing any extra ARGS to every style
+dispatcher).  When that happens it returns a `cons' of the list
+of styles and the possibly updated STRING.  If none of the
+DISPATCHERS returns a list of styles, the return value will use
+DEFAULT as the list of styles."
+  (cl-loop for dispatcher in dispatchers
+           for result = (apply dispatcher string args)
+           if (stringp result)
+           do (setq string result result nil)
+           else if (and (consp result) (null (car result)))
+           do (setf (car result) default)
+           else if (and (consp result) (stringp (cdr result)))
+           do (setq string (cdr result) result (car result))
+           when result return (cons result string)
+           finally (return (cons default string))))
+
+(defun orderless-default-pattern-compiler (pattern &optional styles 
dispatchers)
+  "Build regexps to match the components of PATTERN.
+Split PATTERN on `orderless-component-separator' and compute
+matching styles for each component.  For each component the style
+DISPATCHERS are run to determine the matching styles to be used;
+they are called with arguments the component, the 0-based index
+of the component and the total number of components.  If the
+DISPATCHERS decline to handle the component, then the list of
+matching STYLES is used.  See `orderless-dispatch' for details on
+dispatchers.
+
+The STYLES default to `orderless-matching-styles', and the
+DISPATCHERS default to `orderless-dipatchers'.  Since nil gets you
+the default, if want to no dispatchers to be run, use '(ignore)
+as the value of DISPATCHERS.
+
+The `orderless-transient-*' variables, when non-nil, override the
+corresponding value among `orderless-component-separator', STYLES
+and DISPATCHERS.
+
+This function is the default for `orderless-pattern-compiler' and
+might come in handy as a subroutine to implement other pattern
+compilers."
+  (unless styles (setq styles orderless-matching-styles))
+  (setq styles (or orderless-transient-matching-styles styles))
+  (unless dispatchers (setq dispatchers orderless-style-dispatchers))
+  (setq dispatchers (or orderless-transient-style-dispatchers dispatchers))
+  (cl-loop
+   with components = (split-string
+                      pattern
+                      (or orderless-transient-component-separator
+                          orderless-component-separator))
+   with total = (length components)
+   for component in components and index from 0
+   for (newstyles . newcomp) = (orderless-dispatch
+                                dispatchers styles component index total)
+   collect
+   (if (functionp newstyles)
+       (funcall newstyles newcomp)
+     (rx-to-string
+      `(or
+        ,@(cl-loop for style in newstyles
+                   collect `(regexp ,(funcall style newcomp))))))))
+
+;;; Completion style implementation
 
 (defun orderless--prefix+pattern (string table pred)
   "Split STRING into prefix and pattern according to TABLE.
@@ -270,7 +439,7 @@ The predicate PRED is used to constrain the entries in 
TABLE."
         (pcase-let* ((`(,prefix . ,pattern)
                       (orderless--prefix+pattern string table pred))
                      (completion-regexp-list
-                      (orderless--component-regexps pattern)))
+                      (funcall orderless-pattern-compiler pattern)))
           (all-completions prefix table pred)))
     (invalid-regexp nil)))
 
@@ -313,6 +482,8 @@ This function is part of the `orderless' completion style."
                orderless-try-completion orderless-all-completions
                "Completion of multiple components, in any order."))
 
+;;; Temporary separator change (does anyone use this?)
+
 (defvar orderless-old-component-separator nil
   "Stores the old value of `orderless-component-separator'.")
 (make-obsolete-variable 'orderless-old-component-separator
@@ -340,7 +511,7 @@ This function is part of the `orderless' completion style."
   (setq orderless-component-separator separator)
   (add-to-list 'minibuffer-exit-hook #'orderless--restore-component-separator))
 
-;;; ivy integration
+;;; Ivy integration
 
 (defvar ivy-regex)
 (defvar ivy-highlight-functions-alist)
@@ -350,7 +521,9 @@ This function is part of the `orderless' completion style."
   "Convert STR into regexps for use with ivy.
 This function is for integration of orderless with ivy, use it as
 a value in `ivy-re-builders-alist'."
-  (or (mapcar (lambda (x) (cons x t)) (orderless--component-regexps str)) ""))
+  (or (mapcar (lambda (x) (cons x t))
+              (funcall orderless-pattern-compiler str))
+      ""))
 
 (defun orderless-ivy-highlight (str)
   "Highlight a match in STR of each regexp in `ivy-regex'.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]