unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Augusto Stoffel <arstoffel@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: emacs-devel@gnu.org
Subject: Re: Toning down font-lock
Date: Thu, 10 Mar 2022 08:42:35 +0100	[thread overview]
Message-ID: <87bkye8d90.fsf@gmail.com> (raw)
In-Reply-To: <jwvh788ikro.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message of "Tue, 08 Mar 2022 10:18:36 -0500")

[-- Attachment #1: Type: text/plain, Size: 4839 bytes --]

On Tue,  8 Mar 2022 at 10:18, Stefan Monnier <monnier@iro.umontreal.ca> wrote:

>>> Nice.  I'd suggest you merge `font-lock-ignore--test` and
>>> `font-lock-ignore--test-one` into a single (recursive) function.
>> The exclamation marks are not a recursive thing.
>> But I added `and' and `not' rules that use recursion.
>
> Ah, I see I had misunderstood the syntax and semantics of your toplevel,
> so indeed to bring it into the recursion, you'd need a new thingy which
> might be called maybe `or` with a syntax like (or RULES .. [!] RULE ..).

It's the semantics of a gitignore file.  It think it's handy.

Now, in the syntax you suggest,

  (or RULE1 ! RULE2 RULE3)

means

  (or (and RULE1 (not RULE2)) RULE3)

Maybe this is a potentially confusing extension of such a familiar
construct like `or'?  This is why I originally allowed ! at top level
only (and didn't provide a recursive "or" operation, which seems a bit
of overkill).

But then I implemented what you suggest to see how it looks like.  I'll
let you emit an opinion.

> See some nitpicks below.
>
>
>         Stefan
>
>
>> +   - A symbol, say a face name.  It matches any font-lock rule
>> +     mentioning that symbol anywhere.  Asterisks are treated as
>> +     wildcards.
>
> I suggest we call it a glob pattern so it's a known pattern
> matching thingy and we don't need to reinvent and re-document how to
> make it match an actual `*`.

Great, that sounds better.  But just to make sure, do you like the basic
API here:

- A symbol (possibly wildcard characters) defines a rule that matches
  symbols in the font-lock keyword

- A string defines a rule that matches any font-lock keyword which
  matches the said string

>> @@ -1810,9 +1842,11 @@ font-lock-compile-keywords
>>        (error "Font-lock trying to use keywords before setting them up"))
>>    (if (eq (car-safe keywords) t)
>>        keywords
>> -    (setq keywords
>> -	  (cons t (cons keywords
>> -			(mapcar #'font-lock-compile-keyword keywords))))
>> +    (let ((compiled (mapcar #'font-lock-compile-keyword keywords)))
>> +      (setq keywords `(t ,keywords ,@(if (and font-lock-ignore
>> +                                              (not syntactic-keywords))
>> +                                         (font-lock--filter-keywords compiled)
>> +                                       compiled))))
>
> I think I'd move the test of `font-lock-ignore` into
> `font-lock--filter-keywords` so it's the only function which consults it.

All right.  I've also made the ignore rules apply to syntactic-keywords
as well, which I believe is harmless -- right?

>> @@ -1883,6 +1917,56 @@ font-lock-choose-keywords
>>  	(t
>>  	 (car keywords))))
>>  
>> +(defun font-lock--test-keyword (rule keyword)
>
> That sadly doesn't say whether it return nil when it matches or whether
> it returns non-nil when it matches.  I suggest to rename it to something
> like `font-lock--matches-keyword` so the name clearly say when we return
> nil and when we return non-nil.

Done.

>> +  "Test whether font-lock KEYWORD matches a RULE.
>> +See `font-lock-ignore' for the possible rules."
>
> Same comment ;-)

Done.  The docstring for `font-lock-ignore' can still use some
polishing.

>> +  (pcase-exhaustive rule
>> +    ('* t)
>> +    ((pred symbolp)
>> +     (let* ((name (symbol-name rule))
>> +            (regexp (when (string-match-p "\\*" name)
>> +                      (let* ((words (mapcar #'regexp-quote
>> +                                            (split-string name "\\*")))
>> +                             (joined (string-join words ".*")))
>> +                      (concat "\\`" joined "\\'")))))
>
> We can use `wildcard-to-regexp` here.

Ah, much better!  I swear I did M-x apropos \bglob\b.

>> +       (if regexp
>> +           (seq-some (lambda (obj)
>> +                       (when (symbolp obj)
>> +                         (string-match-p regexp (symbol-name obj))))
>> +                     (flatten-tree keyword))
>> +         (memq rule (flatten-tree keyword)))))
>
> Performance likely doesn't matter, but I suspect it'd be faster if we
> recursed over the data-structure rather than flattening it.
> E.g. something like
>
>     (named-let search ((obj keyword))
>       (cond
>        ((consp obj) (or (search (car obj)) (search (cdr obj))))
>        ((and obj (symbolp obj))
>         (string-match-p regexp (symbol-name obj)))))

Aesthetically, calling `flatten-tree' there bothers me too. But your
algorithm uses recursion even for a flat list...  And a `find-in-tree'
function is not available even in dash, so I guess adding one to subr.el
is out of the question.  (Also I'm already doing a bunch of extra work
by calling `wildcard-to-regexp' on each use of a rule, so it seems weird
to optimize this without also adding a compiler for the rules, which is
way over the top.)


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-New-customization-variable-font-lock-ignore.patch --]
[-- Type: text/x-patch, Size: 5505 bytes --]

From c8ff22f0d995eb4a5954d7ef122885f02e38a7f2 Mon Sep 17 00:00:00 2001
From: Augusto Stoffel <arstoffel@gmail.com>
Date: Tue, 8 Mar 2022 11:23:56 +0100
Subject: [PATCH] New customization variable 'font-lock-ignore'

---
 lisp/font-lock.el | 88 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 85 insertions(+), 3 deletions(-)

diff --git a/lisp/font-lock.el b/lisp/font-lock.el
index d8a1fe399b..8526f6ca6a 100644
--- a/lisp/font-lock.el
+++ b/lisp/font-lock.el
@@ -206,6 +206,7 @@
 \f
 ;;; Code:
 
+(require 'seq)
 (require 'syntax)
 (eval-when-compile (require 'cl-lib))
 
@@ -279,6 +280,42 @@ font-lock-maximum-decoration
 				      (integer :tag "level" 1)))))
   :group 'font-lock)
 
+(defcustom font-lock-ignore nil
+  "Rules to selectively disable font-lock keywords.
+This is a list of rule sets of the form
+
+  (MODE [!] RULE ...)
+
+where:
+
+ - MODE is a symbol, say a major or minor mode.  The subsequent
+   rules apply if the current major mode is derived from MODE or
+   MODE is bound and true as a variable.
+
+ - Each RULE can be one of the following:
+   - A symbol, say a face name.  It matches any font-lock keyword
+     containing the symbol in its definition.  The symbol is
+     interpreted as a glob pattern; in particular, `*' matches
+     everything.
+   - A string.  It matches any font-lock keyword defined by a regexp
+     that matches the string.
+   - A form (pred FUNCTION).  It matches if FUNCTION, which is called
+     with the font-lock keyword as single argument, returns non-nil.
+   - A form (not RULE).  It matches if RULE doesn't.
+   - A form (and RULE ...).  It matches if all the provided rules
+     match.
+   - A form (or [!] RULE ...).  It matches if any of the provided
+     rules match.  However, if a rule is preceded by `!', then the
+     rules preceding it don't apply for a font-lock keyword matching
+     the negated rule.
+
+In each buffer, font lock keywords that match at least one
+applicable rule are disabled.  Rules preceded by `!' reverse the
+effect of a previous exclusion."
+  :type '(alist :key-type symbol :value-type sexp)
+  :group 'font-lock
+  :version "29.1")
+
 (defcustom font-lock-verbose nil
   "If non-nil, means show status messages for buffer fontification.
 If a number, only buffers greater than this size have fontification messages."
@@ -1810,9 +1847,8 @@ font-lock-compile-keywords
       (error "Font-lock trying to use keywords before setting them up"))
   (if (eq (car-safe keywords) t)
       keywords
-    (setq keywords
-	  (cons t (cons keywords
-			(mapcar #'font-lock-compile-keyword keywords))))
+    (let ((compiled (mapcar #'font-lock-compile-keyword keywords)))
+      (setq keywords `(t ,keywords ,@(font-lock--filter-keywords compiled))))
     (if (and (not syntactic-keywords)
 	     (let ((beg-function (with-no-warnings syntax-begin-function)))
 	       (or (eq beg-function #'beginning-of-defun)
@@ -1883,6 +1919,52 @@ font-lock-choose-keywords
 	(t
 	 (car keywords))))
 
+(defun font-lock--match-keyword (rule keyword)
+  "Return non-nil if font-lock KEYWORD matches RULE.
+See `font-lock-ignore' for the possible rules."
+  (pcase-exhaustive rule
+    ('* t)
+    ((pred symbolp)
+     (if-let ((regexp (when (string-match-p "[*?]" (symbol-name rule))
+                        (wildcard-to-regexp (symbol-name rule)))))
+         (seq-some (lambda (obj)
+                     (when (symbolp obj)
+                       (string-match-p regexp (symbol-name obj))))
+                   (flatten-tree keyword))
+       (memq rule (flatten-tree keyword))))
+    ((pred stringp) (when (stringp (car keyword))
+                      (string-match-p (concat "\\`\\(?:" (car keyword) "\\)")
+                                      rule)))
+    (`(or . ,rules) (catch 'font-lock--match
+                      (let ((remaining (reverse rules)))
+                        (while remaining
+                          (pcase-exhaustive remaining
+                            (`(,rule ! . ,rest)
+                             (if (font-lock--match-keyword rule keyword)
+                                 (throw 'font-lock--match nil)
+                               (setq remaining rest)))
+                            (`(,rule . ,rest)
+                             (if (font-lock--match-keyword rule keyword)
+                                 (throw 'font-lock--match t)
+                               (setq remaining rest))))))))
+    (`(not ,rule) (not (font-lock--match-keyword rule keyword)))
+    (`(and . ,rules) (seq-every-p (lambda (rule)
+                                    (font-lock--match-keyword rule keyword))
+                                  rules))
+    (`(pred ,fun) (funcall fun keyword))))
+
+(defun font-lock--filter-keywords (keywords)
+  "Filter a list of KEYWORDS using `font-lock-ignore'."
+  (if-let ((rules (seq-mapcat (pcase-lambda (`(,mode . ,rules))
+                                (when (or (and (boundp mode) mode)
+                                          (derived-mode-p mode))
+                                  rules))
+                               font-lock-ignore)))
+      (seq-filter (lambda (keyword) (not (font-lock--match-keyword
+                                          `(or ,@rules) keyword)))
+                  keywords)
+    keywords))
+
 (defun font-lock-refresh-defaults ()
   "Restart fontification in current buffer after recomputing from defaults.
 Recompute fontification variables using `font-lock-defaults' and
-- 
2.35.1


[-- Attachment #3: Type: text/plain, Size: 95 bytes --]



PS: Can you add this trivial thingy to ELPA?

    https://github.com/astoff/flymake-luacheck

      reply	other threads:[~2022-03-10  7:42 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-05 18:03 Toning down font-lock Augusto Stoffel
2022-03-07 16:10 ` Stefan Monnier
2022-03-07 19:25   ` Augusto Stoffel
2022-03-07 23:55     ` Stefan Monnier
2022-03-08 11:50       ` Augusto Stoffel
2022-03-08 15:18         ` Stefan Monnier
2022-03-10  7:42           ` Augusto Stoffel [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87bkye8d90.fsf@gmail.com \
    --to=arstoffel@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).