unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Alan Mackenzie <acm@muc.de>
To: Stefan Monnier <monnier@IRO.UMontreal.CA>
Cc: emacs-devel@gnu.org
Subject: Re: An idea: combine-change-calls
Date: Mon, 26 Mar 2018 20:17:28 +0000	[thread overview]
Message-ID: <20180326201728.GA28620@ACM> (raw)
In-Reply-To: <jwvmuyworvh.fsf-monnier+emacs@gnu.org>

Hello, Stefan.

On Sun, Mar 25, 2018 at 16:05:40 -0400, Stefan Monnier wrote:
> > I've actually got a working implementation going.  It is this:

> >     (defmacro combine-change-calls (beg end &rest form)
> >       `(if (not inhibit-modification-hooks)
> >            (let* ((-beg- ,beg) (-end- ,end)
> >                   (end-marker (copy-marker -end-)))
> >              (run-hook-with-args 'before-change-functions beg end)
> >              (let ((inhibit-modification-hooks t))
> >                ,@form)
> >              (run-hook-with-args 'after-change-functions
> >                                  beg (marker-position end-marker)
> >                                  (- -end- -beg-)))
> >          ,@form))

> You need to evaluate `beg` and `end` even if inhibit-modification-hooks
> is set, otherwise someone will get bitten.

> I recommend you move the `form` to a lambda so you don't have to
> duplicate it:

>     `(let ((body (lambda () ,@form))
>            (-beg- ,beg)
>            (-end- ,end))
>        ...)

> Another benefit is that by moving `form` outside of the `let*`, you
> won't need to use gensym/make-symbol nor obfuscated names.

> I'd also recommend you check that `beg` hasn't changed position and that
> the distance between end-marker and point-max remained the same.

> >> Maybe combine-change-calls should also combine all those changes on the
> >> undo-list into a big "delete+insert" (of course, it could also try and
> >> keep the undo granularity but mark those undo entries so that they're
> >> undone within their own combine-change-calls).
> > :-)  Either of those would be quite a project, but possibly worth doing.

> Replacing the entries with a pair of delete+insert should be
> pretty easy.  Something like

>     (let ((old-buffer-undo-list buffer-undo-list)
>           (orig-text (buffer-substring beg end)))
>       ...
>       (setq buffer-undo-list
>             `((,(marker-position end-marker) ,beg)
>               (,orig-text . ,beg)
>               . ,old-buffer-undo-list)))

> modulo sanity checks (i.e. don't do it if undo is disabled and don't do
> it if old-buffer-undo-list is not within buffer-undo-list any more).

I'm experimenting with a different strategy: surrounding the mass of
elements in buffer-undo-list with a `(combine-change-begin ,beg ,end)
and a `(combine-change-end ,beg ,end).  This is less violent to the undo
mechanism, for example, still permitting programs to analyse the undo
list.

primitive-undo, when it meets the latter of these, calls
before-change-functions, binds inhibit-modification-hooks to t and calls
itself recursively.  This recursive invocation is terminated by the
combine-change-begin, after-change-functions being called immediately on
return.

The two arms inserted into the pcase form in primitive-undo look like:

          (`(combine-change-end
             ,(and beg (pred integerp))
             ,(and end (pred integerp)))
           (save-excursion
             (run-hook-with-args 'before-change-functions beg end))
           (setq old-len (- end beg))
           (let ((inhibit-modification-hooks t))
             (setq list (primitive-undo 1 list))))
          (`(combine-change-begin
             ,(and beg (pred integerp))
             ,(and end (pred integerp)))
           (if old-len
               ;; Non-nested invocation of `primitive-undo'.
               (save-excursion
                 (run-hook-with-args 'after-change-functions beg end old-len)
                 (setq old-len nil))
             ;; Nested invocation of `primitive-undo'.  Push the element back
             ;; on the list, and push nil to terminate this invocation.
             (push next list)
             (push nil list)))

(where `old-len' is an extra local variable bound to nil in the
surrounding `let' form).

The current version of combine-change-calls, incorporating (at least
most of) your suggestions now looks like:

   (defmacro combine-change-calls (beg end &rest form)
      `(let* ((-beg- ,beg)
              (-end- ,end)
              (body (lambda () ,@form))
              (end-marker (copy-marker -end-)))
         (if inhibit-modification-hooks
             (funcall body)
           (run-hook-with-args 'before-change-functions -beg- -end-)
           (unless (eq buffer-undo-list t)
             (push `(combine-change-begin ,-beg- ,-end-) buffer-undo-list))
           (unwind-protect
               (let ((inhibit-modification-hooks t))
                 (funcall body))
             (unless (eq buffer-undo-list t)
               (push `(combine-change-end ,-beg- ,(marker-position end-marker))
                     buffer-undo-list)))
           (run-hook-with-args 'after-change-functions
                               beg (marker-position end-marker)
                               (- -end- -beg-)))))
 
This makes undo blindingly fast after a large comment-region operation.
It doesn't always leave point in the right place (I understand why -
it's the C function record_point failing to record point because the top
element of buffer-undo-list is no longer nil; it's a
combine-change-begin list).

Do you have any more helpful suggestions for this idea?

Basically, the combine-change-calls idea works.  Given enough
encouragement, I will get my disorganised changes into a proper patch
with documentation, with a view to pushing it to master.

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



  reply	other threads:[~2018-03-26 20:17 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-03-24 13:50 An idea: combine-change-calls Alan Mackenzie
2018-03-24 22:18 ` Stefan Monnier
2018-03-25 19:14   ` Alan Mackenzie
2018-03-25 20:05     ` Stefan Monnier
2018-03-26 20:17       ` Alan Mackenzie [this message]
2018-03-26 21:07         ` Stefan Monnier
2018-03-27 16:58           ` Alan Mackenzie
2018-03-27 18:30             ` Stefan Monnier
2018-03-27 19:45               ` Alan Mackenzie
2018-03-27 20:24                 ` Stefan Monnier
2018-03-28 20:42                   ` Alan Mackenzie
2018-03-28 21:26                     ` Stefan Monnier
2018-03-29 15:10                       ` Alan Mackenzie
2018-03-29 15:40                         ` Stefan Monnier
2018-03-29 17:11                           ` Alan Mackenzie
2018-03-29 19:10                             ` Stefan Monnier
2018-03-30 11:46                               ` Alan Mackenzie
2018-03-30 15:05                                 ` Stefan Monnier
2018-03-31 21:00                                   ` Alan Mackenzie
2018-03-31 23:38                                     ` Stefan Monnier
2018-04-01 14:24                                       ` Alan Mackenzie
2018-04-01 19:22                                         ` Stefan Monnier
2018-03-30  9:12           ` Johan Bockgård
2018-03-30 13:04             ` Stefan Monnier
2018-04-02 16:25               ` Alan Mackenzie
2018-04-02 17:52                 ` Johan Bockgård
2018-04-03  0:41                 ` Stefan Monnier
2018-04-03  1:43                 ` Clément Pit-Claudel
2018-04-03  3:15                 ` Richard Stallman
2018-03-26 21:09         ` Stefan Monnier
2018-03-27  0:36         ` Stefan Monnier
2018-03-27 17:00           ` Alan Mackenzie

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=20180326201728.GA28620@ACM \
    --to=acm@muc.de \
    --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).