unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Alan Mackenzie <acm@muc.de>
To: Eli Zaretskii <eliz@gnu.org>
Cc: monnier@iro.umontreal.ca, emacs-devel@gnu.org
Subject: Re: bug-reference-prog-mode slows down CC Mode's scrolling by ~7%
Date: Fri, 3 Sep 2021 20:51:22 +0000	[thread overview]
Message-ID: <YTKKypeH8Qdyx7yf@ACM> (raw)
In-Reply-To: <YTJS5/+RYKG9H6FV@ACM>

Hello, Eli and Stefan.

On Fri, Sep 03, 2021 at 16:52:55 +0000, Alan Mackenzie wrote:
> On Fri, Sep 03, 2021 at 09:10:24 +0300, Eli Zaretskii wrote:
> > > Date: Thu, 2 Sep 2021 19:24:51 +0000
> > > From: Alan Mackenzie <acm@muc.de>
> > > Cc: emacs-devel@gnu.org

> > > In words (;-), only the first function on jit-lock-functions should
> > > be able to expand the region which gets `fontified' text properties.
> > > This expanded region will then be supplied to the subsequent
> > > functions.

> > > Given how little used the `jit-lock-bounds' mechanism is (there is
> > > one function in Emacs, font-lock-default-fontify-region, which uses
> > > it, and a web search revealed only a single other instance, in
> > > lsp-mode on git-hub), this shouldn't cause problems.  In fact, I'm
> > > not sure the lsp-mode use of it is even correct.

> > Bother: this is not future-proof, and certainly isn't scalable.  If we
> > are changing the protocol, can we instead come up with a more scalable
> > change, so that functions won't "fight" in the future who gets to be
> > "the first"?

> OK, so the problem is how do we cope with two (or even several) "first"
> functions?

> How about something like this:
> o - We still have a first function, font-lock-fontify-region.
> o - We call the next function with the jit-lock-bounds returned by the
>   previous one.  Repeat this step with steadily expanding bounds for
>   each function in jit-lock-functions.
> o - if (< BEG-final BEG-1) we go through jit-lock-functions again on the
>   region (BEG-final BEG-1).  Do the same for END-final.  During this
>   stage, we ignore the returned jit-lock-bounds.
> o - We set the `fontified' text properties on the region (BEG-final
>   END-final).

> This needn't actually be too complicated.  The current
> jit-lock--run-functions already calculates BEG-final and END-final (it
> calls them loose-beg and loose-end).

> For this mechanism, it would still be best to have all the functions
> using jit-lock-bounds together at the start of jit-lock-functions,
> though this would be an optimsation rather than essential to the
> functionality.

OK, I've hacked up a trial implementation, though I haven't started
testing it yet.  The key is to extract jit-lock--fontify-now-1 from
jit-lock-fontify-now, and have it call itself recursively to handle the
expansion of the region caused by a jit-lock-bounds returned by the
second or later function.

This should be easy to test and verify as correct, yet the recursion
should be invoked rarely enough that it won't lead to inefficiencies.

This implementation should resolve Eli's concerns about handling two
or more "first" functions in jit-lock-functions.



(defun jit-lock--run-functions (beg end)
  (let ((tight-beg nil) (tight-end nil) ; The region we have fully fontified.
        (loose-beg beg) (loose-end end)) ; The maximum region we have fontified
                                         ; with at least some of
                                         ; `jit-lock-functions'.
    (run-hook-wrapped
     'jit-lock-functions
     (lambda (fun)
       (pcase-let*
           ;; The first function in `jit-lock-functions' can expand
           ;; the region into `tight-beg' and `tight-end'.  These
           ;; arguments are passed to the next function (if any).
           ;; Subsequently, the expanded region from any function is
           ;; stored in `loose-beg' and `loose-end', and is likewise
           ;; passed to the next function.
           ((res (funcall fun loose-beg loose-end))
            (`(,this-beg . ,this-end)
             (if (eq (car-safe res) 'jit-lock-bounds)
                 (cdr res) (cons beg end))))
         (or tight-beg (setq tight-beg (min this-beg beg)))
         (or tight-end (setq tight-end (max this-end end)))
         (setq loose-beg (min loose-beg this-beg))
         (setq loose-end (max loose-end this-end))
         nil)))
    `(,(or tight-beg beg) ,(or tight-end end) ,loose-beg ,loose-end)))

(defun jit-lock--fontify-now-1 (start end)
  "Fontify current buffer from START to END, possibly more.
Return the list (RES-START RES-END), the entire region which was fontified."
  (let ((res-start start) (res-end end) next)
    ;; Fontify chunks beginning at START.  The end of a chunk is
    ;; either `end', or the start of a region before `end' that has
    ;; already been fontified.
    (while (and start (< start end))
      ;; Determine the end of this chunk.
      (setq next (or (text-property-any start end 'fontified t)
                     end))

      ;; Avoid unnecessary work if the chunk is empty (bug#23278).
     (when (> next start)
        ;; Fontify the chunk, and mark it as fontified.  We mark it
        ;; first, to make sure that we don't indefinitely re-execute
        ;; this fontification if an error occurs.
        (put-text-property start next 'fontified t)
        (pcase-let
            ;; `tight' is the part we've fully refontified, and
            ;; `loose' is the part we've partly refontified (some of
            ;; the functions have refontified it but maybe not all).
            ((`(,tight-beg ,tight-end ,loose-beg ,loose-end)
              (condition-case err
                  (jit-lock--run-functions start next)
                ;; If the user quits (which shouldn't happen in normal
                ;; on-the-fly jit-locking), make sure the fontification
                ;; will be performed before displaying the block again.
                (quit (put-text-property start next 'fontified nil)
                      (signal (car err) (cdr err))))))

          ;; In case we fontified more than requested, take advantage
          ;; of the good news.
          (when (or (< tight-beg start) (> tight-end next))
            (put-text-property tight-beg tight-end 'fontified t))

          ;; If we've partially fontified (i.e. only run some
          ;; `jit-lock-functions' on parts of the buffer beyond (START
          ;; END), refontify those parts entirely.
          (when (< loose-beg tight-beg)
            (pcase-let
                ((`(,sub-beg ,_)
                  (jit-lock--fontify-now-1 loose-beg tight-beg)))
              (setq tight-beg sub-beg)))
          (when (> loose-end tight-end)
            (pcase-let
                ((`(,_ ,sub-end)
                  (jit-lock--fontify-now-1 tight-end loose-end)))
              (setq tight-end sub-end)))

          (setq res-start (min res-start tight-beg)
                res-end (max res-end tight-end))))

      ;; Skip to the end of the fully refontified part.
      (setq start res-end)
      ;; Find the start of the next chunk, if any.
      (setq start (text-property-any start end 'fontified nil)))

    (list res-start res-end)))

(defun jit-lock-fontify-now (&optional start end)
  "Fontify current buffer from START to END.
Defaults to the whole buffer.  END can be out of bounds."
  (with-buffer-prepared-for-jit-lock
   (save-excursion
     (unless start (setq start (point-min)))
     (setq end (if end (min end (point-max)) (point-max)))
     (save-match-data
       (let ((orig-start start))
         (pcase-let
             ;; `tight' is the part we've fully refontified.
             ((`(,tight-beg ,tight-end)
               (jit-lock--fontify-now-1 start end)))

           ;; Make sure the contextual refontification doesn't re-refontify
           ;; what's already been refontified.
           (when (and jit-lock-context-unfontify-pos
                      (< jit-lock-context-unfontify-pos tight-end)
                      (>= jit-lock-context-unfontify-pos tight-beg)
                      ;; Don't move boundary forward if we have to
                      ;; refontify previous text.  Otherwise, we risk moving
                      ;; it past the end of the multiline property and thus
                      ;; forget about this multiline region altogether.
                      (not (get-text-property tight-beg
                                              'jit-lock-defer-multiline)))
             (setq jit-lock-context-unfontify-pos tight-end))

           ;; The redisplay engine has already rendered the buffer up-to
           ;; `orig-start' and won't notice if the above jit-lock-functions
           ;; changed the appearance of any part of the buffer prior
           ;; to that.  So if `tight-beg' is before `orig-start', we need to
           ;; cause a new redisplay cycle after this one so that the changes
           ;; are properly reflected on screen.
           ;; To make such repeated redisplay happen less often, we can
           ;; eagerly extend the refontified region with
           ;; jit-lock-after-change-extend-region-functions.
           (when (< tight-beg orig-start)
             (run-with-timer 0 nil #'jit-lock-force-redisplay
                             (copy-marker tight-beg)
                             (copy-marker orig-start)))))))))


-- 
Alan Mackenzie (Nuremberg, Germany).



  reply	other threads:[~2021-09-03 20:51 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-01 17:33 bug-reference-prog-mode slows down CC Mode's scrolling by ~7% Alan Mackenzie
2021-09-01 17:44 ` Eli Zaretskii
2021-09-01 17:55   ` Alan Mackenzie
2021-09-01 18:01     ` Eli Zaretskii
2021-09-01 18:20       ` Alan Mackenzie
2021-09-01 18:28         ` Eli Zaretskii
2021-09-01 19:19           ` Alan Mackenzie
2021-09-01 20:59 ` Stefan Monnier
2021-09-02  6:26   ` Eli Zaretskii
2021-09-02 16:57   ` Alan Mackenzie
2021-09-02 18:46     ` Stefan Monnier
2021-09-02 19:24       ` Alan Mackenzie
2021-09-02 21:08         ` Alan Mackenzie
2021-09-03  6:16           ` Eli Zaretskii
2021-09-03 12:30             ` Stefan Monnier
2021-09-03 12:38               ` Eli Zaretskii
2021-09-03 22:25                 ` Stefan Monnier
2021-09-04  6:13                   ` Eli Zaretskii
2021-09-04 13:36                     ` Stefan Monnier
2021-09-04 13:55                       ` Eli Zaretskii
2021-09-04 14:44                         ` Stefan Monnier
2021-09-04 14:56                           ` Eli Zaretskii
2021-09-04 15:55                             ` Stefan Monnier
2021-09-04 16:12                               ` Eli Zaretskii
2021-09-04 16:24                                 ` Stefan Monnier
2021-09-04 16:28                                   ` Eli Zaretskii
2021-09-04 16:40                                     ` Stefan Monnier
2021-09-03  6:10         ` Eli Zaretskii
2021-09-03 10:47           ` Alan Mackenzie
2021-09-03 11:24             ` Eli Zaretskii
2021-09-03 16:15               ` Alan Mackenzie
2021-09-03 12:27             ` Stefan Monnier
2021-09-03 12:19           ` Stefan Monnier
2021-09-03 12:35             ` Eli Zaretskii
2021-09-03 16:52           ` Alan Mackenzie
2021-09-03 20:51             ` Alan Mackenzie [this message]
2021-09-04  6:09               ` Eli Zaretskii
2021-09-04 14:50                 ` Alan Mackenzie
2021-09-04 15:00                   ` Stefan Monnier
2021-09-04 15:32                     ` Alan Mackenzie
2021-09-04 15:36                       ` Eli Zaretskii
2021-09-04 15:43                         ` Alan Mackenzie
2021-09-04 15:48                           ` Eli Zaretskii
2021-09-04 16:05                             ` Alan Mackenzie
2021-09-04 16:15                               ` Eli Zaretskii
2021-09-06 10:46                                 ` Alan Mackenzie
2021-09-06 11:10                                   ` Eli Zaretskii
2021-09-06 19:08                                     ` Alan Mackenzie
2021-09-06 19:23                                       ` Eli Zaretskii
2021-09-18 11:37                                         ` Alan Mackenzie
2021-09-18 11:59                                           ` Eli Zaretskii
2021-09-06 21:59                                       ` andrés ramírez
2021-09-07 19:47                                         ` Alan Mackenzie
2021-09-07 17:57                                           ` andrés ramírez
2021-09-06 13:24                                   ` Stefan Monnier
2021-09-04 16:06                       ` Stefan Monnier
2021-09-04 16:23                         ` Eli Zaretskii
2021-09-04 16:39                           ` Stefan Monnier
2021-09-04 17:19                             ` Eli Zaretskii
2021-09-04 17:47                               ` Stefan Monnier
2021-09-04 18:10                                 ` Eli Zaretskii
2021-09-04 18:40                                   ` Stefan Monnier
2021-09-11 12:49                                     ` Eli Zaretskii
2021-09-11 17:04                                       ` Stefan Monnier
2021-09-11 17:17                                         ` Eli Zaretskii
2021-09-11 18:00                                           ` Stefan Monnier
2021-09-11 18:16                                             ` Eli Zaretskii
2021-09-11 19:55                                               ` Stefan Monnier
2021-09-12  3:51                                                 ` Eli Zaretskii
2021-09-12 16:41                                                   ` Stefan Monnier
2021-09-12 16:53                                                     ` Eli Zaretskii
2021-09-12 17:41                                                       ` Stefan Monnier
2021-09-12 17:55                                                         ` Eli Zaretskii
2021-09-12 21:11                                                           ` Stefan Monnier

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=YTKKypeH8Qdyx7yf@ACM \
    --to=acm@muc.de \
    --cc=eliz@gnu.org \
    --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).