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: Sun, 1 Apr 2018 14:24:10 +0000 [thread overview]
Message-ID: <20180401142410.GA9027@ACM> (raw)
In-Reply-To: <jwvvadb3k1t.fsf-monnier+emacs@gnu.org>
Hello, Stefan.
On Sat, Mar 31, 2018 at 19:38:03 -0400, Stefan Monnier wrote:
> > > (defun wrap-and-run-primitive-undo (beg end list)
> > > (combine-change-calls
> > That's a refinement I haven't managed to get around to, yet.
> Any difficulty there?
> Naively, I'd expect that it's just a matter of:
> (defun undo--wrap-and-run-primitive-undo (beg end list)
> (combine-change-calls
> (while list
> (setq list (primitive-undo 1 list)))))
> [ and of course, making sure this function is defined after the
> combine-change-calls macro. ]
It was exactly that. No difficulties.
[ .... ]
> > So, I think this exercise is worthwhile. I have added some doc strings
> > to the functions. I have introduced a variable to hinder a "recursive"
> > undo--wrap-and-run-primitive-undo appearing in the undo list. This
> > happened with uncomment-region calling comment-region.
> That's weird: shouldn't the inhibit-modification-hooks already play this role?
I don't think so. i-m-h is purely to do with whether the hooks should
be run now. That has nothing to do with how items should be added to
the undo list.
> Oh, wait, I see you don't test inhibit-modification-hooks any more but
> you test undo--combining-change-calls instead. Any particular reason
> for this change?
inhibit-modification-hooks is tested around the run-hooks-with-args
calls. If i-m-h is set to non-nil by some function, and comment-region
is run, we still want the undo records to be enclosed by an (apply ...)
form.
> > Sometimes, point ends up at the wrong place after an undo (probably
> > because of primitive-undo removing POSITION elements from undo-lists, as
> > we've already discussed.)
> The code that removes those "position" elements is in `undo`, not in
> `primitive-undo`, so what you're describing must come from something
> else (unless you're seeing this when *redoing* or *reundoing*).
I'm not sure. I think it's in re-undoing. As you've already guessed, I
would be in favour of removing that code from `undo' that we don't see
what it's for.
Anyhow, here's another version of the code (below), with the following
changes:
1/- undo--w-a-r-p-undo is now a macro call of c-c-c.
2/- end-marker is now of type t, so that it is relocated when text is
inserted at the postion it points to. (DELTA was coming out wrong.)
3/- end-marker is now set to point nowhere at the end of the function.
4/- A whole lot of `prog1's have been replaced by the variable
RESULT.
5/- c-c-c-1 no longer incorporates a timestamp undo record ((t . ...))
into the (apply ...) form. This just seemed wrong.
I think it's almost time to commit this to master. I propose that
combine-change-calls{-1,} go into subr.el, beside
combine-after-change-calls, and undo--wrap-and-run-primitive-undo go
into simple.el, beside the rest of the undo stuff.
I will introduce the variable comment-combine-change-calls in
newcomment.el, and modify {,un}comment-region to test it, and if set, to
use c-c-c.
I will document c-c-c in the Elisp manual on page "Change Hooks".
For (almost) the last time, here's the current version of the code:
(defvar undo--combining-change-calls nil
"Non-nil when `combine-change-calls' is running.")
(defun combine-change-calls-1 (beg end body)
"Evaluate BODY, running the change hooks just once, for region \(BEG END).
Firstly, `before-change-functions' is invoked for the region
\(BEG END), then BODY (a function) is evaluated with
`inhibit-modification-hooks' non-nil, then finally
`after-change-functions' is invoked on the updated region (BEG
NEW-END) with a calculated OLD-LEN argument. If
`inhibit-modification-hooks' is initially non-nil, the change
hooks are not run.
The result of `comebine-change-calls-1' is the value returned by
BODY. BODY must not make a different buffer current, except
temporarily. It must not make any changes to the buffer outside
the specified region. It must not change
`before-change-functions' or `after-change-functions'.
Additionally, the buffer modifications of BODY are recorded on
the buffer's undo list as a single \(apply ...) entry containing
the function `undo--wrap-and-run-primitive-undo'."
(let ((old-bul buffer-undo-list)
(end-marker (copy-marker end t))
result)
(if undo--combining-change-calls
(setq result (funcall body))
(let ((undo--combining-change-calls t))
(if (not inhibit-modification-hooks)
(run-hook-with-args 'before-change-functions beg end))
(if (eq buffer-undo-list t)
(setq result (funcall body))
(let ((inhibit-modification-hooks t))
(setq result (funcall body)))
(let ((ap-elt
(list 'apply
(- end end-marker)
beg
(marker-position end-marker)
#'undo--wrap-and-run-primitive-undo
beg (marker-position end-marker) buffer-undo-list))
(ptr buffer-undo-list))
(if (not (eq buffer-undo-list old-bul))
(progn
(while (and (not (eq (cdr ptr) old-bul))
;; In case garbage collection has removed OLD-BUL.
(cdr ptr)
;; Don't include a timestamp entry.
(not (and (consp (cdr ptr))
(consp (cadr ptr))
(eq (caadr ptr) t)
(setq old-bul (cdr ptr)))))
(setq ptr (cdr ptr)))
(unless (cdr ptr)
(message "combine-change-calls: buffer-undo-list broken"))
(setcdr ptr nil)
(push ap-elt buffer-undo-list)
(setcdr buffer-undo-list old-bul)))))
(if (not inhibit-modification-hooks)
(run-hook-with-args 'after-change-functions
beg (marker-position end-marker)
(- end beg)))))
(set-marker end-marker nil)
result))
(defmacro combine-change-calls (beg end &rest forms)
"Evaluate FORMS, running the change hooks just once.
Firstly, `before-change-functions' is invoked for the region
\(BEG END), then the FORMS are evaluated with
`inhibit-modification-hooks' non-nil, and finally
`after-change-functions' is invoked on the updated region. The
change hooks are not run if `inhibit-modification-hooks' is
non-nil.
The result of `combine-change-calls' is the value returned by the
last of the FORMS to be evaluated. FORMS may not make a
different buffer current, except temporarily. FORMS may not
change the buffer outside the specified region. It must not
change `before-change-functions' or `after-change-functions'.
Additionally, the buffer modifications of BODY are recorded on
the buffer's undo list as a single \(apply ...) entry containing
the function `undo--wrap-and-run-primitive-undo'. "
`(combine-change-calls-1 ,beg ,end (lambda () ,@forms)))
(defun undo--wrap-and-run-primitive-undo (beg end list)
"Call `primitive-undo' on the undo elements in LIST.
This function is intended to be called purely by `undo' as the
function in an \(apply DELTA BEG END FUNNAME . ARGS) undo
element. It invokes `before-change-functions' and
`after-change-functions' once each for the entire region \(BEG
END) rather than once for each individual change.
Additionally the fresh \"redo\" elements which are generated on
`buffer-undo-list' will themselves be \"enclosed\" in
`undo--wrap-and-run-primitive-undo'.
Undo elements of this form are generated by the macro
`combine-change-calls'."
(combine-change-calls beg end
(while list
(setq list (primitive-undo 1 list)))))
> Stefan
--
Alan Mackenzie (Nuremberg, Germany).
next prev parent reply other threads:[~2018-04-01 14:24 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
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 [this message]
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=20180401142410.GA9027@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).