Received: (at 70077) by debbugs.gnu.org; 9 Apr 2024 03:56:30 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 23:56:30 2024 Received: from localhost ([127.0.0.1]:48001 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1ru2b0-0005Uq-3e for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 23:56:30 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:47476) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1ru2av-0005Tp-SO for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 23:56:28 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1ru2af-0002i1-DA; Mon, 08 Apr 2024 23:56:09 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=iyy0Zl8A4/1Ds6HZjGUNuNy3AGqvxlAR1M1laPwo0Wo=; b=eqnOaBH2KCs4 b0gbALxp5wgyZKam3EXlD8KpqoMx6s/48a0CYGG2IrlPORgpv+9MTp9kkRS2Jc+SCAXg/KPXdOTlp bl18R0pAcyZDuF3iUrQrivkKLZjrS87h6UPOrRyCJ+9PoamQvH3Ft5OK+c8+7gGmo5CCeJdal7hKJ zsMbSD2MwGzy/WGNqC56pykt+VyiZG3c1HXQB1eQtdMtdYLP0Wp8OjlZg5M19SX3q3mYG1TRON7gt GQqivq4XS2MVbasvHotFtNfQSsDaubQ/Ze85uqGT6+4KuEqtuZv1WUr6jBGmJ45ggFsoKKv0yvfIi mQiCBHnEHf0elpjBzge2dg==; Date: Tue, 09 Apr 2024 06:56:07 +0300 Message-Id: <865xwrxk88.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwv1q7fd1p5.fsf-monnier+emacs@HIDDEN> (message from Stefan Monnier on Mon, 08 Apr 2024 16:45:39 -0400) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> <jwv1q7fd1p5.fsf-monnier+emacs@HIDDEN> X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > From: Stefan Monnier <monnier@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, acm@HIDDEN, yantar92@HIDDEN > Date: Mon, 08 Apr 2024 16:45:39 -0400 > > >> Last, but not least: this needs suitable changes in NEWS and ELisp > >> manual. > > Working on it. > > Here it is (and aso on `scratch/track-changes`). Thanks. > +Using @code{before-change-functions} and @code{after-change-functions} > +can be difficult in practice because of a number of pitfalls, such as > +the fact that the two calls are not always properly paired, or some > +calls may be missing, either because of bugs in the C code or because of > +inappropriate use of @code{inhibit-modification-hooks}. I don't think we should talk about bugs in C code in the manual, at least not so explicitly. I would rephrase the fact that the two calls are not always properly paired, or some calls may be missing, either because some Emacs primitives cannot properly pair them or because of incorrect use of @code{inhibit-modification-hooks}. > +The start tracking changes, you have to call ^^^^^^^^^ "To start" > +@code{track-changes-register}, passing it a @var{signal} function as > +argument. This will return a tracker @var{id} which is used to identify > +your tracker to the other functions of the library. The other main > +function of the library is @code{track-changes-fetch} which lets you > +fetch the changes you have not yet processed. The last sentence is redundant, since you are about to describe track-changes-fetch shortly. > +When the buffer is modified, the library will call the @var{signal} > +function to inform you of that change and will immediately start > +accumulating subsequent changes into a single combined change. > +The @var{signal} function serves only to warn that a modification > +occurred but does not receive a description of the change. Also the > +library will not call it again until after you processed > +the change. The last sentence should IMO say "...until after you retrieved the change by calling @code{track-changes-fetch}." The important part here is to say what "process" means in practice, instead of leaving it unsaid. > +To process changes, you need to call @code{track-changes-fetch}, which That's not really "processing", that's "retrieval", right? Processing is what the program does after it retrieves the changes. > +@defun track-changes-register signal &key nobefore disjoint immediate > +This function creates a new @emph{tracker}. Trackers are kept abstract, I suggest to use "change tracker" instead of just "tracker". On my daytime job, "tracker" has a very different meaning, so I stumble each time I see this used like that. Also, I suggest to use @dfn for its markup (and add a @cindex for it for good measure). > +By default, the call to the @var{signal} function does not happen > +immediately, but is instead postponed with a 0 seconds timer. ^^^^^^^^^^^^^^^ A cross-reference to where timers are described is in order there. > +usually desired to make sure the @var{signal} function is not called too > +frequently and runs in a permissive context, freeing the client from > +performance concerns or worries about which operations might be > +problematic. If a client wants to have more control, they can provide > +a non-nil value as the @var{immediate} argument in which case the ^^^ @code{nil} > +If you're not interested in the actual previous content of the buffer, > +but are using this library only for its ability to combine many small > +changes into a larger one and to delay the processing to a more > +convenient time, you can specify a non-nil value for the @var{before} ^^^ Likewise. > +While you may like to accumulate many small changes into larger ones, > +you may not want to do that if the changes are too far apart. If you > +specify a non-nil value for the @var{disjoint} argument, the library ^^^ And likewise. > +modified region, but if you specified a non-nil @var{nobefore} argument ^^^ And likewise. > +In case no changes occurred since the last call, > +@code{track-changes-fetch} simply does not call @var{func} and returns > +nil. If changes did occur, it calls @var{func} and returns the value ^^^ And likewise. > +Once @var{func} finishes, @code{track-changes-fetch} re-enables the > +@var{signal} function so that it will be called the next time a change > +occurs. This is the reason why it calls @var{func} instead of returning > +a description: it makes sure that the @var{signal} function will not be > +called while you're still processing past changes. I think there's a subtler issue here that needs to be described explicitly: if the entire processing of the change is not done inside FUNC, there's no guarantee that by the time some other function processes it, the change is still valid and in particular SIGNAL will not have been called again. This is not a trivial aspect, since a program can use FUNC to do just some partial processing, like squirrel the changes to some buffer for later processing. > * New Modes and Packages in Emacs 30.1 > > +** New package Track-Changes. This should be marked with "+++".
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 20:57:46 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 16:57:46 2024 Received: from localhost ([127.0.0.1]:47792 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtw3j-0002rE-QK for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 16:57:46 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:63779) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtw3T-0002pl-Ku for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 16:57:42 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 5341C441C54; Mon, 8 Apr 2024 16:57:14 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712609833; bh=SEMtbhWii2mjvSKh0w5Kj+9mZeud33f6DM8yMXHbscE=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=Ha6W90gS/bChxi+sEeVO8RFJEnLCwNAItSHzTQ/dScW5HPy90vn8KnWUhrEOFfBtz dsYk74HDUHiKUl2PXa1p+/GfEbGJ+HM2Uvs36SU8CyUXIIIR53+v1Y/Zl15MgwrQ/Q wVd2amhPz10ymY5CLTP113ZDwjT9S8YFOJz9i6LyjLxA092fFZOuau9N5Bp3SUq7D7 WV6NjQeAfiG65AD3Ji+bBmtmxGO/mWn4Cw0OjbJWb+U41Yx/iuPR1oeVudzOf5SwWq LOWwlRiyEUn9X5Q+dQG5cG5W5ekeI0MwM9v1fiGJsp5l+87G2nQQ9H5HcbID+df9GG i3a951jdBq9Tg== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id ECF5E441C2A; Mon, 8 Apr 2024 16:57:12 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id BC7B2120185; Mon, 8 Apr 2024 16:57:12 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86il0rya4m.fsf@HIDDEN> (Eli Zaretskii's message of "Mon, 08 Apr 2024 21:36:41 +0300") Message-ID: <jwvv84rbmze.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> <86msq3yhot.fsf@HIDDEN> <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> <86il0rya4m.fsf@HIDDEN> Date: Mon, 08 Apr 2024 16:57:11 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.025 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > If this indicates that the slots are of built-in-class type, why do we > show the cryptic t there? No, what it's saying is that these slots can contain values of any type (since any type is a subtype of t). This `Type` information is a way to document what kind of values can be found in those slots. Very often we don't bother specifying it, in which case `t` is used as a default. >> >> >> +By default SIGNAL is called as soon as convenient after a change, which is >> >> > ^^^^^^^^^^^^^^^^^^^^^ >> >> > "as soon as it's convenient", I presume? >> >> Other than the extra " it's", what is the difference? >> > Nothing. I indeed thing "it's" is missing there. >> My local native-English representative says that "both work fine" >> (somewhat dismissively, I must add). > In that case I'll yield, but do note that it got me stumbled. Wow. To me this is just as natural as "as soon as possible", tho used admittedly less frequently. >> > In general, when I want to create a clean slate, I don't care too much >> > about the dirt I remove. Why is it important to signal errors because >> > a state I am dumping had some errors? >> I don't understand why you think it will signal an error? > Doesn't cl-assert signal an error if the condition is false? Yes. What makes you think it will be false? >> More to the point, it should signal an error only if I made a mistake in >> `track-changes.el` or if you messed with the internals. > I have the latter possibility in mind, yes. Why catch me doing that > when I'm cleaning up my mess, _after_ all the damage, such as it is, > was already done? But `track-changes--clean-state` is not a function to "clean up the mess" any more than, say, `track-changes-fetch` or `track-changes--before`. >> >> >> +;;;; Extra candidates for the API. >> >> >> +;; This could be a good alternative to using a temp-buffer like I used in >> >> > ^^^^^^ >> >> > "I"? >> >> Yes, that refers to the code I wrote. >> > We don't usually leave such style in long-term comments and >> > documentation. >> `grep " I " lisp/**/*.el` suggests otherwise. > "A journey of a thousand miles begins with one first step." I disagree with the goal, tho. If you want, I can add something like "--Stef" at the end to clarify who is this "I", tho nowadays I tend to rely on `C-x v h` to find that kind of information. The text there is just recording my thoughts about this part of the design of the API. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 20:46:19 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 16:46:18 2024 Received: from localhost ([127.0.0.1]:47785 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtvsb-0001h4-FE for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 16:46:18 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:15124) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtvsR-0001f6-LN for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 16:46:10 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 5D45C441C2A; Mon, 8 Apr 2024 16:45:49 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712609141; bh=DefC+tGrm+5mDXXBCXt5f+tPfvKyMAXy9fX0ww7LxEA=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=AbSH/5S1kChQKq7DaBZYFTXLVNpOBbewigUrcJMGkdOz9roYHRpvV6ZyXuINhqMAp 5synTrW/P5wjJZavLNFqBpiEbpJa9J89LromNW9bgFJJ24RKsftjkM3OG9xvKN32He AF1l2cE6pgpKYtQv1RRm2jfZCGNSu67EmxtWmlZYrrBdN0qtCY3n+bPef++qVlnc+X YJNbmcudvDTaaBjcCaj/sv9gkkEgc5HhADIxEs2lEyuH1Ajxprnj9VFVJklqzjXKXq onErKB+0BsLqW93cq++mB6bCCnQuxmm2fHJV6qV29X+bXNrz8SNoqm9lRvNzI+BsyP /CfzAiNvjycnA== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 6F5464413F4; Mon, 8 Apr 2024 16:45:41 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 1FA35120370; Mon, 8 Apr 2024 16:45:41 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvplv0c662.fsf-monnier+emacs@HIDDEN> (Stefan Monnier's message of "Mon, 08 Apr 2024 11:24:38 -0400") Message-ID: <jwv1q7fd1p5.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> Date: Mon, 08 Apr 2024 16:45:39 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.026 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) --=-=-= Content-Type: text/plain >> Last, but not least: this needs suitable changes in NEWS and ELisp >> manual. > Working on it. Here it is (and aso on `scratch/track-changes`). Stefan --=-=-= Content-Type: text/x-diff; charset=iso-8859-1 Content-Disposition: inline; filename=0001-lisp-emacs-lisp-track-changes.el-New-file.patch Content-Transfer-Encoding: quoted-printable From b676b0ff3f046a1456a433a4b7741599c7ae4714 Mon Sep 17 00:00:00 2001 From: Stefan Monnier <monnier@HIDDEN> Date: Fri, 5 Apr 2024 17:37:32 -0400 Subject: [PATCH] lisp/emacs-lisp/track-changes.el: New file This new package provides an API that is easier to use right than our `*-change-functions` hooks. The patch includes changes to `diff-mode.el` and `eglot.el` to make use of this new package. * lisp/emacs-lisp/track-changes.el: New file. * test/lisp/emacs-lisp/track-changes-tests.el: New file. * doc/lispref/text.texi (Tracking changes): New subsection. * lisp/progmodes/eglot.el: Require `track-changes`. (eglot--virtual-pos-to-lsp-position): New function. (eglot--track-changes): New var. (eglot--managed-mode): Use `track-changes-register` i.s.o `after/before-change-functions` when available. (eglot--track-changes-signal): New function, partly extracted from `eglot--after-change`. (eglot--after-change): Use it. (eglot--track-changes-fetch): New function. (eglot--signal-textDocument/didChange): Use it. * lisp/vc/diff-mode.el: Require `track-changes`. Also require `easy-mmode` before the `eval-when-compile`s. (diff-unhandled-changes): Delete variable. (diff-after-change-function): Delete function. (diff--track-changes-function): Rename from `diff-post-command-hook` and adjust to new calling convention. (diff--track-changes): New variable. (diff--track-changes-signal): New function. (diff-mode, diff-minor-mode): Use it with `track-changes-register`. --- doc/lispref/text.texi | 141 +++++ etc/NEWS | 11 + lisp/emacs-lisp/track-changes.el | 599 ++++++++++++++++++++ lisp/progmodes/eglot.el | 64 ++- lisp/vc/diff-mode.el | 85 ++- test/lisp/emacs-lisp/track-changes-tests.el | 156 +++++ 6 files changed, 1003 insertions(+), 53 deletions(-) create mode 100644 lisp/emacs-lisp/track-changes.el create mode 100644 test/lisp/emacs-lisp/track-changes-tests.el diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 18f0ee88fe5..2875f6f6ba8 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -6375,3 +6375,144 @@ Change Hooks use @code{combine-change-calls} or @code{combine-after-change-calls} instead. @end defvar + +@node Tracking changes +@subsection Tracking changes +@cindex track-changes + +Using @code{before-change-functions} and @code{after-change-functions} +can be difficult in practice because of a number of pitfalls, such as +the fact that the two calls are not always properly paired, or some +calls may be missing, either because of bugs in the C code or because of +inappropriate use of @code{inhibit-modification-hooks}. Furthermore, +many restrictions apply to those hook functions, such as the fact that +they basically should never modify the current buffer, nor use an +operation that may block, and they proceed quickly because +some commands may call these hooks a large number of times. + +The Track-Changes library fundamentally provides an alternative API, +built on top of those hooks. Compared to @code{after-change-functions}, +the first important difference is that, instead of providing the bounds +of the change and the previous length, it provides the bounds of the +change and the actual previous content of that region. The need to +extract information from the original contents of the buffer is one of +the main reasons why some packages need to use both +@code{before-change-functions} and @code{after-change-functions} and +then try to match them up. + +The second difference is that it decouples the notification of a change +from the act of processing it, and it automatically combines into +a single change operation all the changes that occur between the first +change and the actual processing. This makes it natural and easy to +process the changes at a larger granularity, such as once per command, +and eliminates most of the restrictions that apply to the usual change +hook functions, making it possible to use blocking operations or to +modify the buffer + +The start tracking changes, you have to call +@code{track-changes-register}, passing it a @var{signal} function as +argument. This will return a tracker @var{id} which is used to identify +your tracker to the other functions of the library. The other main +function of the library is @code{track-changes-fetch} which lets you +fetch the changes you have not yet processed. + +When the buffer is modified, the library will call the @var{signal} +function to inform you of that change and will immediately start +accumulating subsequent changes into a single combined change. +The @var{signal} function serves only to warn that a modification +occurred but does not receive a description of the change. Also the +library will not call it again until after you processed +the change. + +To process changes, you need to call @code{track-changes-fetch}, which +will provide you with the bounds of the changes accumulated since the +last call, as well as the previous content of that region. It will also +``re-arm'' the @var{signal} function so that the library will call it +again after the next buffer modification. + +@defun track-changes-register signal &key nobefore disjoint immediate +This function creates a new @emph{tracker}. Trackers are kept abstract, +so we refer to them as mere identities, and the function thus returns +the tracker's @var{id}. + +@var{signal} is a function that the library will call to notify of +a change. It will sometimes call it with a single argument and +sometimes with two. Upon the first change to the buffer since this +tracker last called @code{track-changes-fetch}, the library calls this +@var{signal} function with a single argument holding the @var{id} of +the tracker. + +By default, the call to the @var{signal} function does not happen +immediately, but is instead postponed with a 0 seconds timer. This is +usually desired to make sure the @var{signal} function is not called too +frequently and runs in a permissive context, freeing the client from +performance concerns or worries about which operations might be +problematic. If a client wants to have more control, they can provide +a non-nil value as the @var{immediate} argument in which case the +library will call the @var{signal} function directly from +@code{after-change-functions}. Beware that it means that the +@var{signal} function has to be careful not to modify the buffer or use +operations that may block. + +If you're not interested in the actual previous content of the buffer, +but are using this library only for its ability to combine many small +changes into a larger one and to delay the processing to a more +convenient time, you can specify a non-nil value for the @var{before} +argument. This will make it so the library provides you only with the +length of the previous content, just like +@code{after-change-functions}. It will also allow the library to save +some work. + +While you may like to accumulate many small changes into larger ones, +you may not want to do that if the changes are too far apart. If you +specify a non-nil value for the @var{disjoint} argument, the library +will let you know when a change is about to occur ``far'' from the +currently pending ones by calling the @var{signal} function right away, +passing it two arguments this time: the @var{id} of the tracker, and the +number of characters that separates the upcoming change from the +already pending changes. This in itself does not prevent combining this +new change with the previous ones, so if you think the upcoming change +is indeed too far, you need to call @code{track-change-fetch} +right away. +Beware that when the @var{signal} function is called because of +a disjoint change, this happens directly from +@code{before-change-functions}, so the usual restrictions apply about +modifying the buffer or using operations that may block. +@end defun + +@defun track-changes-fetch id func +This is the function that lets you find out what has changed in the +buffer. By providing the tracker @var{id} you let the library figure +out which changes have already been seen by your tracker. Instead of +returning a description of the changes, @code{track-changes-fetch} calls +the @var{func} function with that description in the form of +3 arguments: @var{beg}, @var{end}, and @var{before}, where +@code{@var{beg}..@var{end}} delimit the region that was modified and +@var{before} describes the previous content of that region. +Usually @var{before} is a string containing the previous text of the +modified region, but if you specified a non-nil @var{nobefore} argument +to @code{track-changes-register}, then it is replaced by the number of +characters of that previous text. + +In case no changes occurred since the last call, +@code{track-changes-fetch} simply does not call @var{func} and returns +nil. If changes did occur, it calls @var{func} and returns the value +returned by @var{func}. But note that @var{func} is called just once +regardless of how many changes occurred: those are summarized into +a single @var{beg}/@var{end}/@var{before} triplet. + +Once @var{func} finishes, @code{track-changes-fetch} re-enables the +@var{signal} function so that it will be called the next time a change +occurs. This is the reason why it calls @var{func} instead of returning +a description: it makes sure that the @var{signal} function will not be +called while you're still processing past changes. +@end defun + +@defun track-changes-unregister id +This function tells the library that the tracker @var{id} does not need +to know about buffer changes any more. Most clients will never want to +stop tracking changes, but for clients such as minor modes, it is +important to call this function when the minor mode is disabled, +otherwise the tracker will keep accumulating changes and consume more +and more resources. +@end defun diff --git a/etc/NEWS b/etc/NEWS index b2543ae77d9..d85b65abd0b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1569,6 +1569,17 @@ This allows disabling JavaScript in xwidget Webkit s= essions. * New Modes and Packages in Emacs 30.1 =20 +** New package Track-Changes. +This library is a layer of abstraction above 'before-change-functions' +and 'after-change-functions' which provides a superset of +the functionality of 'after-change-functions': +- It provides the actual previous text rather than only its length. +- It takes care of accumulating and bundling changes until a time when + its client finds it convenient to react to them. +- It detects most cases where some changes were not properly + reported (calls to 'before/after-change-functions' that are + incorrectly paired, missing, etc...) and reports them adequately. + ** New major modes based on the tree-sitter library =20 +++ diff --git a/lisp/emacs-lisp/track-changes.el b/lisp/emacs-lisp/track-chang= es.el new file mode 100644 index 00000000000..fef74074582 --- /dev/null +++ b/lisp/emacs-lisp/track-changes.el @@ -0,0 +1,599 @@ +;;; track-changes.el --- API to react to buffer modifications -*- lexical= -binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; Author: Stefan Monnier <monnier@HIDDEN> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This library is a layer of abstraction above `before-change-functions' +;; and `after-change-functions' which takes care of accumulating changes +;; until a time when its client finds it convenient to react to them. +;; +;; It provides an API that is easier to use correctly than our +;; `*-change-functions' hooks. Problems that it claims to solve: +;; +;; - Before and after calls are not necessarily paired. +;; - The beg/end values don't always match. +;; - There's usually only one call to the hooks per command but +;; there can be thousands of calls from within a single command, +;; so naive users will tend to write code that performs poorly +;; in those rare cases. +;; - The hooks are run at a fairly low-level so there are things they +;; really shouldn't do, such as modify the buffer or wait. +;; - The after call doesn't get enough info to rebuild the before-change s= tate, +;; so some callers need to use both before-c-f and after-c-f (and then +;; deal with the first two points above). +;; +;; The new API is almost like `after-change-functions' except that: +;; - It provides the "before string" (i.e. the previous content of +;; the changed area) rather than only its length. +;; - It can combine several changes into larger ones. +;; - Clients do not have to process changes right away, instead they +;; can let changes accumulate (by combining them into a larger change) +;; until it is convenient for them to process them. +;; - By default, changes are signaled at most once per command. + +;; The API consists in the following functions: +;; +;; (track-changes-register SIGNAL &key NOBEFORE DISJOINT IMMEDIATE) +;; (track-changes-fetch ID FUNC) +;; (track-changes-unregister ID) +;; +;; A typical use case might look like: +;; +;; (defvar my-foo--change-tracker nil) +;; (define-minor-mode my-foo-mode +;; "Fooing like there's no tomorrow." +;; (if (null my-foo-mode) +;; (when my-foo--change-tracker +;; (track-changes-unregister my-foo--change-tracker) +;; (setq my-foo--change-tracker nil)) +;; (unless my-foo--change-tracker +;; (setq my-foo--change-tracker +;; (track-changes-register +;; (lambda (id) +;; (track-changes-fetch +;; id (lambda (beg end before) +;; ..DO THE THING..)))))))) + +;;; Code: + +(require 'cl-lib) + +;;;; Internal types and variables. + +(cl-defstruct (track-changes--tracker + (:noinline t) + (:constructor nil) + (:constructor track-changes--tracker ( signal state + &optional + nobefore immediate))) + signal state nobefore immediate) + +(cl-defstruct (track-changes--state + (:noinline t) + (:constructor nil) + (:constructor track-changes--state ())) + "Object holding a description of a buffer state. +BEG..END is the area that was changed and BEFORE is its previous content. +If the current buffer currently holds the content of the next state, you c= an +get the contents of the previous state with: + + (concat (buffer-substring (point-min) beg) + before + (buffer-substring end (point-max))) + +NEXT is the next state object (i.e. a more recent state). +If NEXT is nil it means it's most recent state and it may be incomplete +\(BEG/END/BEFORE may be nil), in which case those fields will take their +values from `track-changes--before-(beg|end|before)' when the next +state is create." + (beg (point-max)) + (end (point-min)) + (before nil) + (next nil)) + +(defvar-local track-changes--trackers () + "List of trackers currently registered in the buffer.") +(defvar-local track-changes--clean-trackers () + "List of trackers that are clean. +Those are the trackers that get signaled when a change is made.") + +(defvar-local track-changes--disjoint-trackers () + "List of trackers that want to react to disjoint changes. +These trackers are signaled every time track-changes notices +that some upcoming changes touch another \"distant\" part of the buffer.") + +(defvar-local track-changes--state nil) + +;; `track-changes--before-*' keep track of the content of the +;; buffer when `track-changes--state' was cleaned. +(defvar-local track-changes--before-beg 0 + "Beginning position of the remembered \"before string\".") +(defvar-local track-changes--before-end 0 + "End position of the text replacing the \"before string\".") +(defvar-local track-changes--before-string "" + "String holding some contents of the buffer before the current change. +This string is supposed to cover all the already modified areas plus +the upcoming modifications announced via `before-change-functions'. +If all trackers are `nobefore', then this holds the `buffer-size' before +the current change.") +(defvar-local track-changes--before-no t + "If non-nil, all the trackers are `nobefore'. +Should be equal to (memq #\\=3D'track-changes--before before-change-functi= ons).") + +(defvar-local track-changes--before-clean 'unset + "Status of `track-changes--before-*' vars. +More specifically it indicates which \"before\" they hold. +- nil: The vars hold the \"before\" info of the current state. +- `unset': The vars hold the \"before\" info of some older state. + This is what it is set to right after creating a fresh new state. +- `set': Like nil but the state is still clean because the buffer has not + been modified yet. This is what it is set to after the first + `before-change-functions' but before an `after-change-functions'.") + +(defvar-local track-changes--buffer-size nil + "Current size of the buffer, as far as this library knows. +This is used to try and detect cases where buffer modifications are \"lost= \".") + +;;;; Exposed API. + +(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) + "Register a new tracker whose change-tracking function is SIGNAL. +Return the ID of the new tracker. + +SIGNAL is a function that will be called with one argument (the tracker ID) +after the current buffer is modified, so that it can react to the change. +Once called, SIGNAL is not called again until `track-changes-fetch' +is called with the corresponding tracker ID. + +If optional argument NOBEFORE is non-nil, it means that this tracker does +not need the BEFORE strings (it will receive their size instead). + +If optional argument DISJOINT is non-nil, SIGNAL is called every time just +before combining changes from \"distant\" parts of the buffer. +This is needed when combining disjoint changes into one bigger change +is unacceptable, typically for performance reasons. +These calls are distinguished from normal calls by calling SIGNAL with +a second argument which is the distance between the upcoming change and +the previous changes. +BEWARE: In that case SIGNAL is called directly from `before-change-functio= ns' +and should thus be extra careful: don't modify the buffer, don't call a fu= nction +that may block, ... +In order to prevent the upcoming change from being combined with the previ= ous +changes, SIGNAL needs to call `track-changes-fetch' before it returns. + +By default SIGNAL is called after a change via a 0 seconds timer. +If optional argument IMMEDIATE is non-nil it means SIGNAL should be called +as soon as a change is detected, +BEWARE: In that case SIGNAL is called directly from `after-change-function= s' +and should thus be extra careful: don't modify the buffer, don't call a fu= nction +that may block, do as little work as possible, ... +When IMMEDIATE is non-nil, the SIGNAL should probably not always call +`track-changes-fetch', since that would defeat the purpose of this library= ." + (when (and nobefore disjoint) + ;; FIXME: Without `before-change-functions', we can discover + ;; a disjoint change only after the fact, which is not good enough. + ;; But we could use a stripped down before-change-function, + (error "`disjoint' not supported for `nobefore' trackers")) + (track-changes--clean-state) + (unless nobefore + (setq track-changes--before-no nil) + (add-hook 'before-change-functions #'track-changes--before nil t)) + (add-hook 'after-change-functions #'track-changes--after nil t) + (let ((tracker (track-changes--tracker signal track-changes--state + nobefore immediate))) + (push tracker track-changes--trackers) + (push tracker track-changes--clean-trackers) + (when disjoint + (push tracker track-changes--disjoint-trackers)) + tracker)) + +(defun track-changes-unregister (id) + "Remove the tracker denoted by ID. +Trackers can consume resources (especially if `track-changes-fetch' is +not called), so it is good practice to unregister them when you don't +need them any more." + (unless (memq id track-changes--trackers) + (error "Unregistering a non-registered tracker: %S" id)) + (setq track-changes--trackers (delq id track-changes--trackers)) + (setq track-changes--clean-trackers (delq id track-changes--clean-tracke= rs)) + (setq track-changes--disjoint-trackers + (delq id track-changes--disjoint-trackers)) + (when (cl-every #'track-changes--tracker-nobefore track-changes--tracker= s) + (setq track-changes--before-no t) + (remove-hook 'before-change-functions #'track-changes--before t)) + (when (null track-changes--trackers) + (mapc #'kill-local-variable + '(track-changes--before-beg + track-changes--before-end + track-changes--before-string + track-changes--buffer-size + track-changes--before-clean + track-changes--state)) + (remove-hook 'after-change-functions #'track-changes--after t))) + +(defun track-changes-fetch (id func) + "Fetch the pending changes for tracker ID pass them to FUNC. +ID is the tracker ID returned by a previous `track-changes-register'. +FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) +where BEGIN..END delimit the region that was changed since the last +time `track-changes-fetch' was called and BEFORE is a string containing +the previous content of that region (or just its length as an integer +if the tracker ID was registered with the `nobefore' option). +If track-changes detected that some changes were missed, then BEFORE will +be the symbol `error' to indicate that the buffer got out of sync. +This reflects a bug somewhere, so please report it when it happens. + +If no changes occurred since the last time, it doesn't call FUNC and +returns nil, otherwise it returns the value returned by FUNC +and re-enable the TRACKER corresponding to ID." + (cl-assert (memq id track-changes--trackers)) + (unless (equal track-changes--buffer-size (buffer-size)) + (track-changes--recover-from-error)) + (let ((beg nil) + (end nil) + (before t) + (lenbefore 0) + (states ())) + ;; Transfer the data from `track-changes--before-string' + ;; to the tracker's state object, if needed. + (track-changes--clean-state) + ;; We want to combine the states from most recent to oldest, + ;; so reverse them. + (let ((state (track-changes--tracker-state id))) + (while state + (push state states) + (setq state (track-changes--state-next state)))) + + (cond + ((eq (car states) track-changes--state) + (cl-assert (null (track-changes--state-before (car states)))) + (setq states (cdr states))) + (t + ;; The states are disconnected from the latest state because + ;; we got out of sync! + (cl-assert (eq (track-changes--state-before (car states)) 'error)) + (setq beg (point-min)) + (setq end (point-max)) + (setq before 'error) + (setq states nil))) + + (dolist (state states) + (let ((prevbeg (track-changes--state-beg state)) + (prevend (track-changes--state-end state)) + (prevbefore (track-changes--state-before state))) + (if (eq before t) + (progn + ;; This is the most recent change. Just initialize the vars. + (setq beg prevbeg) + (setq end prevend) + (setq lenbefore + (if (stringp prevbefore) (length prevbefore) prevbefor= e)) + (setq before + (unless (track-changes--tracker-nobefore id) prevbefor= e))) + (let ((endb (+ beg lenbefore))) + (when (< prevbeg beg) + (if (not before) + (setq lenbefore (+ (- beg prevbeg) lenbefore)) + (setq before + (concat (buffer-substring-no-properties + prevbeg beg) + before)) + (setq lenbefore (length before))) + (setq beg prevbeg) + (cl-assert (=3D endb (+ beg lenbefore)))) + (when (< endb prevend) + (let ((new-end (+ end (- prevend endb)))) + (if (not before) + (setq lenbefore (+ lenbefore (- new-end end))) + (setq before + (concat before + (buffer-substring-no-properties + end new-end))) + (setq lenbefore (length before))) + (setq end new-end) + (cl-assert (=3D prevend (+ beg lenbefore))) + (setq endb (+ beg lenbefore)))) + (cl-assert (<=3D beg prevbeg prevend endb)) + ;; The `prevbefore' is covered by the new one. + (if (not before) + (setq lenbefore + (+ (- prevbeg beg) + (if (stringp prevbefore) + (length prevbefore) prevbefore) + (- endb prevend))) + (setq before + (concat (substring before 0 (- prevbeg beg)) + prevbefore + (substring before (- (length before) + (- endb prevend))))) + (setq lenbefore (length before))))))) + (if (null beg) + (progn + (cl-assert (null states)) + (cl-assert (memq id track-changes--clean-trackers)) + (cl-assert (eq (track-changes--tracker-state id) + track-changes--state)) + ;; Nothing to do. + nil) + (cl-assert (<=3D (point-min) beg end (point-max))) + ;; Update the tracker's state *before* running `func' so we don't ri= sk + ;; mistakenly replaying the changes in case `func' exits non-locally. + (setf (track-changes--tracker-state id) track-changes--state) + (unwind-protect (funcall func beg end (or before lenbefore)) + ;; Re-enable the tracker's signal only after running `func', so + ;; as to avoid recursive invocations. + (cl-pushnew id track-changes--clean-trackers))))) + +;;;; Auxiliary functions. + +(defun track-changes--clean-state () + (cond + ((null track-changes--state) + (cl-assert track-changes--before-clean) + (cl-assert (null track-changes--buffer-size)) + ;; No state has been created yet. Do it now. + (setq track-changes--buffer-size (buffer-size)) + (when track-changes--before-no + (setq track-changes--before-string (buffer-size))) + (setq track-changes--state (track-changes--state))) + (track-changes--before-clean nil) + (t + (cl-assert (<=3D (track-changes--state-beg track-changes--state) + (track-changes--state-end track-changes--state))) + (let ((actual-beg (track-changes--state-beg track-changes--state)) + (actual-end (track-changes--state-end track-changes--state))) + (if track-changes--before-no + (progn + (cl-assert (integerp track-changes--before-string)) + (setf (track-changes--state-before track-changes--state) + (- track-changes--before-string + (- (buffer-size) (- actual-end actual-beg)))) + (setq track-changes--before-string (buffer-size))) + (cl-assert (<=3D track-changes--before-beg + actual-beg actual-end + track-changes--before-end)) + (cl-assert (null (track-changes--state-before track-changes--state= ))) + ;; The `track-changes--before-*' vars can cover more text than the + ;; actually modified area, so trim it down now to the relevant par= t. + (unless (=3D (- track-changes--before-end track-changes--before-be= g) + (- actual-end actual-beg)) + (setq track-changes--before-string + (substring track-changes--before-string + (- actual-beg track-changes--before-beg) + (- (length track-changes--before-string) + (- track-changes--before-end actual-end)))) + (setq track-changes--before-beg actual-beg) + (setq track-changes--before-end actual-end)) + (setf (track-changes--state-before track-changes--state) + track-changes--before-string))) + ;; Note: We preserve `track-changes--before-*' because they may still + ;; be needed, in case `after-change-functions' are run before the next + ;; `before-change-functions'. + ;; Instead, we set `track-changes--before-clean' to `unset' to mean th= at + ;; `track-changes--before-*' can be reset at the next + ;; `before-change-functions'. + (setq track-changes--before-clean 'unset) + (let ((new (track-changes--state))) + (setf (track-changes--state-next track-changes--state) new) + (setq track-changes--state new))))) + +(defvar track-changes--disjoint-threshold 100 + "Number of chars below which changes are not considered disjoint.") + +(defvar track-changes--error-log () + "List of errors encountered. +Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).") + +(defun track-changes--recover-from-error () + ;; We somehow got out of sync. This is usually the result of a bug + ;; elsewhere that causes the before-c-f and after-c-f to be improperly + ;; paired, or to be skipped altogether. + ;; Not much we can do, other than force a full re-synchronization. + (warn "Missing/incorrect calls to `before/after-change-functions'!! +Details logged to `track-changes--error-log'") + (push (list (buffer-name) + (backtrace-frames 'track-changes--recover-from-error) + (recent-keys 'include-cmds)) + track-changes--error-log) + (setq track-changes--before-clean 'unset) + (setq track-changes--buffer-size (buffer-size)) + ;; Create a new state disconnected from the previous ones! + ;; Mark the previous one as junk, just to be clear. + (setf (track-changes--state-before track-changes--state) 'error) + (setq track-changes--state (track-changes--state))) + +(defun track-changes--before (beg end) + (cl-assert track-changes--state) + (cl-assert (<=3D beg end)) + (let* ((size (- end beg)) + (reset (lambda () + (cl-assert track-changes--before-clean) + (setq track-changes--before-clean 'set) + (setf track-changes--before-string + (buffer-substring-no-properties beg end)) + (setf track-changes--before-beg beg) + (setf track-changes--before-end end))) + + (signal-if-disjoint + (lambda (pos1 pos2) + (let ((distance (- pos2 pos1))) + (when (> distance + (max track-changes--disjoint-threshold + ;; If the distance is smaller than the size of= the + ;; current change, then we may as well conside= r it + ;; as "near". + (length track-changes--before-string) + size + (- track-changes--before-end + track-changes--before-beg))) + (dolist (tracker track-changes--disjoint-trackers) + (funcall (track-changes--tracker-signal tracker) + tracker distance)) + ;; Return non-nil if the state was cleaned along the way. + track-changes--before-clean))))) + + (if track-changes--before-clean + (progn + ;; Detect disjointness with previous changes here as well, + ;; so that if a client calls `track-changes-fetch' all the time, + ;; it doesn't prevent others from getting a disjointness signal. + (when (and track-changes--before-beg + (let ((found nil)) + (dolist (tracker track-changes--disjoint-trackers) + (unless (memq tracker track-changes--clean-tracke= rs) + (setq found t))) + found)) + ;; There's at least one `tracker' that wants to know about dis= joint + ;; changes *and* it has unseen pending changes. + ;; FIXME: This can occasionally signal a tracker that's clean. + (if (< beg track-changes--before-beg) + (funcall signal-if-disjoint end track-changes--before-beg) + (funcall signal-if-disjoint track-changes--before-end beg))) + (funcall reset)) + (cl-assert (save-restriction + (widen) + (<=3D (point-min) + track-changes--before-beg + track-changes--before-end + (point-max)))) + (when (< beg track-changes--before-beg) + (if (and track-changes--disjoint-trackers + (funcall signal-if-disjoint end track-changes--before-beg= )) + (funcall reset) + (let* ((old-bbeg track-changes--before-beg) + ;; To avoid O(N=B2) behavior when faced with many small c= hanges, + ;; we copy more than needed. + (new-bbeg (min (max (point-min) + (- old-bbeg + (length track-changes--before-stri= ng))) + beg))) + (setf track-changes--before-beg new-bbeg) + (cl-callf (lambda (old new) (concat new old)) + track-changes--before-string + (buffer-substring-no-properties new-bbeg old-bbeg))))) + + (when (< track-changes--before-end end) + (if (and track-changes--disjoint-trackers + (funcall signal-if-disjoint track-changes--before-end beg= )) + (funcall reset) + (let* ((old-bend track-changes--before-end) + ;; To avoid O(N=B2) behavior when faced with many small c= hanges, + ;; we copy more than needed. + (new-bend (max (min (point-max) + (+ old-bend + (length track-changes--before-stri= ng))) + end))) + (setf track-changes--before-end new-bend) + (cl-callf concat track-changes--before-string + (buffer-substring-no-properties old-bend new-bend)))))))) + +(defun track-changes--after (beg end len) + (cl-assert track-changes--state) + (and (eq track-changes--before-clean 'unset) + (not track-changes--before-no) + ;; This can be a sign that a `before-change-functions' went missing, + ;; or that we called `track-changes--clean-state' between + ;; a `before-change-functions' and `after-change-functions'. + (track-changes--before beg end)) + (setq track-changes--before-clean nil) + (let ((offset (- (- end beg) len))) + (cl-incf track-changes--before-end offset) + (cl-incf track-changes--buffer-size offset) + (if (not (or track-changes--before-no + (save-restriction + (widen) + (<=3D (point-min) + track-changes--before-beg + beg end + track-changes--before-end + (point-max))))) + ;; BEG..END is not covered by previous `before-change-functions'!! + (track-changes--recover-from-error) + ;; Note the new changes. + (when (< beg (track-changes--state-beg track-changes--state)) + (setf (track-changes--state-beg track-changes--state) beg)) + (cl-callf (lambda (old-end) (max end (+ old-end offset))) + (track-changes--state-end track-changes--state)) + (cl-assert (or track-changes--before-no + (<=3D track-changes--before-beg + (track-changes--state-beg track-changes--state) + beg end + (track-changes--state-end track-changes--state) + track-changes--before-end))))) + (while track-changes--clean-trackers + (let ((tracker (pop track-changes--clean-trackers))) + (if (track-changes--tracker-immediate tracker) + (funcall (track-changes--tracker-signal tracker) tracker) + (run-with-timer 0 nil #'track-changes--call-signal + (current-buffer) tracker))))) + +(defun track-changes--call-signal (buf tracker) + (when (buffer-live-p buf) + (with-current-buffer buf + ;; Silence ourselves if `track-changes-fetch' was called in the mean= time. + (unless (memq tracker track-changes--clean-trackers) + (funcall (track-changes--tracker-signal tracker) tracker))))) + +;;;; Extra candidates for the API. + +;; This could be a good alternative to using a temp-buffer like I used in +;; Eglot, since presumably we've just been changing this very area of the +;; buffer, so the gap should be ready nearby, +;; It may seem silly to go back to the previous state, since we could have +;; used `before-change-functions' to run FUNC right then when we were in +;; that state. The advantage is that with track-changes we get to decide +;; retroactively which state is the one for which we want to call FUNC and +;; which BEG..END to use: when that state was current we may have known +;; then that it would be "the one" but we didn't know what BEG and END +;; should be because those depend on the changes that came afterwards. +(defun track-changes--in-revert (beg end before func) + "Call FUNC with the buffer contents temporarily reverted to BEFORE. +FUNC is called with no arguments and with point right after BEFORE. +FUNC is not allowed to modify the buffer and it should refrain from using +operations that use a cache populated from the buffer's content, +such as `syntax-ppss'." + (catch 'track-changes--exit + (with-silent-modifications ;; This has to be outside `atomic-change-gr= oup'. + (atomic-change-group + (goto-char end) + (insert-before-markers before) + (delete-region beg end) + (throw 'track-changes--exit + (let ((inhibit-read-only nil) + (buffer-read-only t)) + (funcall func))))))) + +(defun track-changes--reset (id) + "Mark all past changes as handled for tracker ID. +Does not re-enable ID's signal." + (track-changes--clean-state) + (setf (track-changes--tracker-state id) track-changes--state)) + +(defun track-changes--pending-p (id) + "Return non-nil if there are pending changes for tracker ID." + (not (memq id track-changes--clean-trackers))) + +(defmacro with--track-changes (id vars &rest body) + (declare (indent 2) (debug (form sexp body))) + `(track-changes-fetch ,id (lambda ,vars ,@body))) + +(provide 'track-changes) +;;; track-changes.el end here. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 7f4284bf09d..478e7687bb3 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -110,6 +110,7 @@ (require 'text-property-search nil t) (require 'diff-mode) (require 'diff) +(require 'track-changes nil t) =20 ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite @@ -1732,6 +1733,9 @@ eglot-utf-16-linepos "Calculate number of UTF-16 code units from position given by LBP. LBP defaults to `eglot--bol'." (/ (- (length (encode-coding-region (or lbp (eglot--bol)) + ;; FIXME: How could `point' ever be + ;; larger than `point-max' (sounds l= ike + ;; a bug in Emacs). ;; Fix github#860 (min (point) (point-max)) 'utf-16 t)) 2) @@ -1749,6 +1753,24 @@ eglot--pos-to-lsp-position :character (progn (when pos (goto-char pos)) (funcall eglot-current-linepos-function))))) =20 +(defun eglot--virtual-pos-to-lsp-position (pos string) + "Return the LSP position at the end of STRING if it were inserted at POS= ." + (eglot--widening + (goto-char pos) + (forward-line 0) + ;; LSP line is zero-origin; Emacs is one-origin. + (let ((posline (1- (line-number-at-pos nil t))) + (linebeg (buffer-substring (point) pos)) + (colfun eglot-current-linepos-function)) + ;; Use a temp buffer because: + ;; - I don't know of a fast way to count newlines in a string. + ;; - We currently don't have `eglot-current-linepos-function' for str= ings. + (with-temp-buffer + (insert linebeg string) + (goto-char (point-max)) + (list :line (+ posline (1- (line-number-at-pos nil t))) + :character (funcall colfun)))))) + (defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos "Function to move to a position within a line reported by the LSP server. =20 @@ -1946,6 +1968,8 @@ eglot-managed-mode-hook "A hook run by Eglot after it started/stopped managing a buffer. Use `eglot-managed-p' to determine if current buffer is managed.") =20 +(defvar-local eglot--track-changes nil) + (define-minor-mode eglot--managed-mode "Mode for source buffers managed by some Eglot project." :init-value nil :lighter nil :keymap eglot-mode-map @@ -1959,8 +1983,13 @@ eglot--managed-mode ("utf-8" (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-li= nepos) (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-= utf-8-linepos))) - (add-hook 'after-change-functions #'eglot--after-change nil t) - (add-hook 'before-change-functions #'eglot--before-change nil t) + (if (fboundp 'track-changes-register) + (unless eglot--track-changes + (setq eglot--track-changes + (track-changes-register + #'eglot--track-changes-signal :disjoint t))) + (add-hook 'after-change-functions #'eglot--after-change nil t) + (add-hook 'before-change-functions #'eglot--before-change nil t)) (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) ;; Prepend "didClose" to the hook after the "nonoff", so it will run f= irst (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil = t) @@ -1998,6 +2027,9 @@ eglot--managed-mode buffer (eglot--managed-buffers (eglot-current-server))))) (t + (when eglot--track-changes + (track-changes-unregister eglot--track-changes) + (setq eglot--track-changes nil)) (remove-hook 'after-change-functions #'eglot--after-change t) (remove-hook 'before-change-functions #'eglot--before-change t) (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) @@ -2588,7 +2620,6 @@ eglot--document-changed-hook (defun eglot--after-change (beg end pre-change-length) "Hook onto `after-change-functions'. Records BEG, END and PRE-CHANGE-LENGTH locally." - (cl-incf eglot--versioned-identifier) (pcase (car-safe eglot--recent-changes) (`(,lsp-beg ,lsp-end (,b-beg . ,b-beg-marker) @@ -2616,6 +2647,29 @@ eglot--after-change `(,lsp-beg ,lsp-end ,pre-change-length ,(buffer-substring-no-properties beg end))))) (_ (setf eglot--recent-changes :emacs-messup))) + (eglot--track-changes-signal nil)) + +(defun eglot--track-changes-fetch (id) + (if (eq eglot--recent-changes :pending) (setq eglot--recent-changes nil)) + (track-changes-fetch + id (lambda (beg end before) + (cond + ((eq eglot--recent-changes :emacs-messup) nil) + ((eq before 'error) (setf eglot--recent-changes :emacs-messup)) + (t (push `(,(eglot--pos-to-lsp-position beg) + ,(eglot--virtual-pos-to-lsp-position beg before) + ,(length before) + ,(buffer-substring-no-properties beg end)) + eglot--recent-changes)))))) + +(defun eglot--track-changes-signal (id &optional distance) + (cl-incf eglot--versioned-identifier) + (cond + (distance (eglot--track-changes-fetch id)) + (eglot--recent-changes nil) + ;; Note that there are pending changes, for the benefit of those + ;; who check it as a boolean. + (t (setq eglot--recent-changes :pending))) (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) (let ((buf (current-buffer))) (setq eglot--change-idle-timer @@ -2729,6 +2783,8 @@ eglot-handle-request (defun eglot--signal-textDocument/didChange () "Send textDocument/didChange to server." (when eglot--recent-changes + (when eglot--track-changes + (eglot--track-changes-fetch eglot--track-changes)) (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) (sync-kind (if (numberp sync-capability) sync-capability @@ -2750,7 +2806,7 @@ eglot--signal-textDocument/didChange ;; empty entries in `eglot--before-change' calls ;; without an `eglot--after-change' reciprocal. ;; Weed them out here. - when (numberp len) + when (numberp len) ;FIXME: Not needed with `track-chang= es'. vconcat `[,(list :range `(:start ,beg :end ,end) :rangeLength len :text text)])))) (setq eglot--recent-changes nil) diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 66043059d14..0a618dc8f39 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -53,9 +53,10 @@ ;; - Handle `diff -b' output in context->unified. =20 ;;; Code: +(require 'easy-mmode) +(require 'track-changes) (eval-when-compile (require 'cl-lib)) (eval-when-compile (require 'subr-x)) -(require 'easy-mmode) =20 (autoload 'vc-find-revision "vc") (autoload 'vc-find-revision-no-save "vc") @@ -1431,38 +1432,23 @@ diff-write-contents-hooks (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max))) nil) =20 -;; It turns out that making changes in the buffer from within an -;; *-change-function is asking for trouble, whereas making them -;; from a post-command-hook doesn't pose much problems -(defvar diff-unhandled-changes nil) -(defun diff-after-change-function (beg end _len) - "Remember to fixup the hunk header. -See `after-change-functions' for the meaning of BEG, END and LEN." - ;; Ignoring changes when inhibit-read-only is set is strictly speaking - ;; incorrect, but it turns out that inhibit-read-only is normally not set - ;; inside editing commands, while it tends to be set when the buffer gets - ;; updated by an async process or by a conversion function, both of which - ;; would rather not be uselessly slowed down by this hook. - (when (and (not undo-in-progress) (not inhibit-read-only)) - (if diff-unhandled-changes - (setq diff-unhandled-changes - (cons (min beg (car diff-unhandled-changes)) - (max end (cdr diff-unhandled-changes)))) - (setq diff-unhandled-changes (cons beg end))))) - -(defun diff-post-command-hook () - "Fixup hunk headers if necessary." - (when (consp diff-unhandled-changes) - (ignore-errors - (save-excursion - (goto-char (car diff-unhandled-changes)) - ;; Maybe we've cut the end of the hunk before point. - (if (and (bolp) (not (bobp))) (backward-char 1)) - ;; We used to fixup modifs on all the changes, but it turns out that - ;; it's safer not to do it on big changes, e.g. when yanking a big - ;; diff, or when the user edits the header, since we might then - ;; screw up perfectly correct values. --Stef - (diff-beginning-of-hunk t) +(defvar-local diff--track-changes nil) + +(defun diff--track-changes-signal (tracker) + (cl-assert (eq tracker diff--track-changes)) + (track-changes-fetch tracker #'diff--track-changes-function)) + +(defun diff--track-changes-function (beg end _before) + (with-demoted-errors "%S" + (save-excursion + (goto-char beg) + ;; Maybe we've cut the end of the hunk before point. + (if (and (bolp) (not (bobp))) (backward-char 1)) + ;; We used to fixup modifs on all the changes, but it turns out that + ;; it's safer not to do it on big changes, e.g. when yanking a big + ;; diff, or when the user edits the header, since we might then + ;; screw up perfectly correct values. --Stef + (when (ignore-errors (diff-beginning-of-hunk t)) (let* ((style (if (looking-at "\\*\\*\\*") 'context)) (start (line-beginning-position (if (eq style 'context) 3 2= ))) (mid (if (eq style 'context) @@ -1470,17 +1456,16 @@ diff-post-command-hook (re-search-forward diff-context-mid-hunk-header-= re nil t))))) (when (and ;; Don't try to fixup changes in the hunk header. - (>=3D (car diff-unhandled-changes) start) + (>=3D beg start) ;; Don't try to fixup changes in the mid-hunk header eith= er. (or (not mid) - (< (cdr diff-unhandled-changes) (match-beginning 0)) - (> (car diff-unhandled-changes) (match-end 0))) + (< end (match-beginning 0)) + (> beg (match-end 0))) (save-excursion - (diff-end-of-hunk nil 'donttrustheader) + (diff-end-of-hunk nil 'donttrustheader) ;; Don't try to fixup changes past the end of the hunk. - (>=3D (point) (cdr diff-unhandled-changes)))) - (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))) - (setq diff-unhandled-changes nil)))) + (>=3D (point) end))) + (diff-fixup-modifs (point) end))))))) =20 (defun diff-next-error (arg reset) ;; Select a window that displays the current buffer so that point @@ -1560,9 +1545,8 @@ diff-mode ;; setup change hooks (if (not diff-update-on-the-fly) (add-hook 'write-contents-functions #'diff-write-contents-hooks nil = t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t)) + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal :nobefore t= ))) =20 ;; add-log support (setq-local add-log-current-defun-function #'diff-current-defun) @@ -1581,12 +1565,15 @@ diff-minor-mode \\{diff-minor-mode-map}" :group 'diff-mode :lighter " Diff" ;; FIXME: setup font-lock - ;; setup change hooks - (if (not diff-update-on-the-fly) - (add-hook 'write-contents-functions #'diff-write-contents-hooks nil = t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t))) + (when diff--track-changes (track-changes-unregister diff--track-changes)) + (remove-hook 'write-contents-functions #'diff-write-contents-hooks t) + (when diff-minor-mode + (if (not diff-update-on-the-fly) + (add-hook 'write-contents-functions #'diff-write-contents-hooks ni= l t) + (unless diff--track-changes + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal + :nobefore t)))))) =20 ;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;= ;;; =20 diff --git a/test/lisp/emacs-lisp/track-changes-tests.el b/test/lisp/emacs-= lisp/track-changes-tests.el new file mode 100644 index 00000000000..eab9197030f --- /dev/null +++ b/test/lisp/emacs-lisp/track-changes-tests.el @@ -0,0 +1,156 @@ +;;; track-changes-tests.el --- tests for emacs-lisp/track-changes.el -*- = lexical-binding:t -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'track-changes) +(require 'cl-lib) +(require 'ert) + +(defun track-changes-tests--random-word () + (let ((chars ())) + (dotimes (_ (1+ (random 12))) + (push (+ ?A (random (1+ (- ?z ?A)))) chars)) + (apply #'string chars))) + +(defvar track-changes-tests--random-verbose nil) + +(defun track-changes-tests--message (&rest args) + (when track-changes-tests--random-verbose (apply #'message args))) + +(defvar track-changes-tests--random-seed + (let ((seed (number-to-string (random (expt 2 24))))) + (message "Random seed =3D %S" seed) + seed)) + +(ert-deftest track-changes-tests--random () + ;; Keep 2 buffers in sync with a third one as we make random + ;; changes to that 3rd one. + ;; We have 3 trackers: a "normal" one which we sync + ;; at random intervals, one which syncs via the "disjoint" signal, + ;; plus a third one which verifies that "nobefore" gets + ;; information consistent with the "normal" tracker. + (with-temp-buffer + (dotimes (_ 100) + (insert (track-changes-tests--random-word) "\n")) + (let* ((buf1 (generate-new-buffer " *tc1*")) + (buf2 (generate-new-buffer " *tc2*")) + (char-counts (make-vector 2 0)) + (sync-counts (make-vector 2 0)) + (print-escape-newlines t) + (file (make-temp-file "tc")) + (id1 (track-changes-register #'ignore)) + (id3 (track-changes-register #'ignore :nobefore t)) + (sync + (lambda (id buf n) + (track-changes-tests--message "!! SYNC %d !!" n) + (track-changes-fetch + id (lambda (beg end before) + (when (eq n 1) + (track-changes-fetch + id3 (lambda (beg3 end3 before3) + (should (eq beg3 beg)) + (should (eq end3 end)) + (should (eq before3 + (if (symbolp before) + before (length before))))))) + (cl-incf (aref sync-counts (1- n))) + (cl-incf (aref char-counts (1- n)) (- end beg)) + (let ((after (buffer-substring beg end))) + (track-changes-tests--message + "Sync:\n %S\n=3D> %S\nat %d .. %d" + before after beg end) + (with-current-buffer buf + (if (eq before 'error) + (erase-buffer) + (should (equal before + (buffer-substring + beg (+ beg (length before))))) + (delete-region beg (+ beg (length before)))) + (goto-char beg) + (insert after))) + (should (equal (buffer-string) + (with-current-buffer buf + (buffer-string)))))))) + (id2 (track-changes-register + (lambda (id2 &optional distance) + (when distance + (track-changes-tests--message "Disjoint distance: %d" + distance) + (funcall sync id2 buf2 2))) + :disjoint t))) + (write-region (point-min) (point-max) file) + (insert-into-buffer buf1) + (insert-into-buffer buf2) + (should (equal (buffer-hash) (buffer-hash buf1))) + (should (equal (buffer-hash) (buffer-hash buf2))) + (message "seeding with: %S" track-changes-tests--random-seed) + (random track-changes-tests--random-seed) + (dotimes (_ 1000) + (pcase (random 15) + (0 + (track-changes-tests--message "Manual sync1") + (funcall sync id1 buf1 1)) + (1 + (track-changes-tests--message "Manual sync2") + (funcall sync id2 buf2 2)) + ((pred (< _ 5)) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 100))) (point-max)))) + (track-changes-tests--message "Fill %d .. %d" beg end) + (fill-region-as-paragraph beg end))) + ((pred (< _ 8)) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 12))) (point-max)))) + (track-changes-tests--message "Delete %S at %d .. %d" + (buffer-substring beg end) beg = end) + (delete-region beg end))) + ((and 8 (guard (=3D (random 50) 0))) + (track-changes-tests--message "Silent insertion") + (let ((inhibit-modification-hooks t)) + (insert "a"))) + ((and 8 (guard (=3D (random 10) 0))) + (track-changes-tests--message "Revert") + (insert-file-contents file nil nil nil 'replace)) + ((and 8 (guard (=3D (random 3) 0))) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 12))) (point-max))) + (after (eq (random 2) 0))) + (track-changes-tests--message "Bogus %S %d .. %d" + (if after 'after 'before) beg e= nd) + (if after + (run-hook-with-args 'after-change-functions + beg end (- end beg)) + (run-hook-with-args 'before-change-functions beg end)))) + (_ + (goto-char (+ (point-min) (random (1+ (buffer-size))))) + (let ((word (track-changes-tests--random-word))) + (track-changes-tests--message "insert %S at %d" word (point)) + (insert word "\n"))))) + (message "SCOREs: default: %d/%d=3D%d disjoint: %d/%d=3D%d" + (aref char-counts 0) (aref sync-counts 0) + (/ (aref char-counts 0) (aref sync-counts 0)) + (aref char-counts 1) (aref sync-counts 1) + (/ (aref char-counts 1) (aref sync-counts 1)))))) + + + +;;; track-changes-tests.el ends here --=20 2.43.0 --=-=-=--
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 18:37:06 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 14:37:06 2024 Received: from localhost ([127.0.0.1]:47616 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rttrd-0004cC-70 for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 14:37:06 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:44470) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rttrX-0004au-UA for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 14:37:03 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rttrI-0003Fp-Jy; Mon, 08 Apr 2024 14:36:44 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From: Date; bh=F1Wuv5edgL3s0imPNyBoYbOmI1JZHfkG494N5pHsBb8=; b=WK5YhrUU/vmzCnEL7X03 szsTn0d2kSOSN8gQInEyw5YgXbgThxZ3wgAQ8eS+Q2ATgPbDSqAhtA+5UlkmW3ZR8oLwoeb6NTF+B /FZug3I2QaqDR8FL3lup0klf8Crym+PGKGRL0dfB+qHThGDN9BbcOWQoocxpXk98y8unsTfMUx3Jv OelMbwrSpNajX4A1MJWQxhDBaoqcj6lKnKDk0jJk7othXy9/URdCOs6wW5CLLtSveHH3Cqv3gkKmd ejfRsGh2MqO4IPT8E72NoR0SX/VOToMcm9pSwBkcqGkjfI7QxpWdJeWJyeS5vRYhjDzze9IjU0sc3 +vTJslWEu+7zXw==; Date: Mon, 08 Apr 2024 21:36:41 +0300 Message-Id: <86il0rya4m.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> (message from Stefan Monnier on Mon, 08 Apr 2024 13:17:37 -0400) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> <86msq3yhot.fsf@HIDDEN> <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> MIME-version: 1.0 Content-type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > From: Stefan Monnier <monnier@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, acm@HIDDEN, yantar92@HIDDEN > Date: Mon, 08 Apr 2024 13:17:37 -0400 > > > Name Type Default > > ———— ———— ——————— > > beg t (point-max) > > end t (point-min) > > before t nil > > next t nil > > > > BEG..END is the area that was changed and BEFORE is its previous > > content[...] > > OK, I'll switch the two, thanks. > > > (Btw, those "t" under "Type" are also somewhat mysterious. What do > > they signify?) > > `C-h o t RET` says: > > t’s value is t > > Not documented as a variable. > > Probably introduced at or before Emacs version 16. > > > > t is also a type. > > t is a type (of kind ‘built-in-class’). > Children ‘sequence’, ‘atom’. > > Abstract supertype of everything. > > This is a built-in type. If this indicates that the slots are of built-in-class type, why do we show the cryptic t there? > >> >> +By default SIGNAL is called as soon as convenient after a change, which is > >> > ^^^^^^^^^^^^^^^^^^^^^ > >> > "as soon as it's convenient", I presume? > >> Other than the extra " it's", what is the difference? > > Nothing. I indeed thing "it's" is missing there. > > My local native-English representative says that "both work fine" > (somewhat dismissively, I must add). In that case I'll yield, but do note that it got me stumbled. > > In general, when I want to create a clean slate, I don't care too much > > about the dirt I remove. Why is it important to signal errors because > > a state I am dumping had some errors? > > I don't understand why you think it will signal an error? Doesn't cl-assert signal an error if the condition is false? > More to the point, it should signal an error only if I made a mistake in > `track-changes.el` or if you messed with the internals. I have the latter possibility in mind, yes. Why catch me doing that when I'm cleaning up my mess, _after_ all the damage, such as it is, was already done? > >> >> +;;;; Extra candidates for the API. > >> >> +;; This could be a good alternative to using a temp-buffer like I used in > >> > ^^^^^^ > >> > "I"? > >> Yes, that refers to the code I wrote. > > We don't usually leave such style in long-term comments and > > documentation. > > `grep " I " lisp/**/*.el` suggests otherwise. "A journey of a thousand miles begins with one first step."
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 17:28:18 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 13:28:18 2024 Received: from localhost ([127.0.0.1]:47561 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtsn3-0007Jg-NJ for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 13:28:18 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:56008) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <acorallo@HIDDEN>) id 1rtsmu-0007HC-TU for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 13:28:11 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <acorallo@HIDDEN>) id 1rtsmg-0008UV-5S; Mon, 08 Apr 2024 13:27:54 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:Date:References:In-Reply-To:Subject:To: From; bh=XWmI97MSED0EBPvzrrhjZYNH+Tl9StLVLhe1LOLXVjA=; b=AY1DQQWO5XTXM3DEbfFP HZuWXkvvtWa1fOIsiegmem2cG1IEU2K+oLUzw5bAUYlcRxwkTI6hmsErdIwmgQpfSS2WPuqHc95ni iOiAvRgYTnw//KHoHdS0xPag23wOEsIaNjJRZkwH7N3YfVBnZT71UF7S6wbVvaP+HDfi12/KLUX2x 6PyxKk4wIfddfGUZcubGXZ8121l0I8AeLD9Y8AiqnLg3XLR9SBd/74sZB1SicBIoU1tN+dbOeqTpu nmro+xy8dAvVORL7dodKtUVho7hoWsmwu1gKSQU8XCAXGZrx/ExDJzOD8yyRDkgH2IJv8X2vqqEjh IBl167CifOmTYQ==; Received: from acorallo by fencepost.gnu.org with local (Exim 4.90_1) (envelope-from <acorallo@HIDDEN>) id 1rtsmZ-0007Rb-NL; Mon, 08 Apr 2024 13:27:49 -0400 From: Andrea Corallo <acorallo@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> (Stefan Monnier via's message of "Mon, 08 Apr 2024 13:17:37 -0400") References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> <86msq3yhot.fsf@HIDDEN> <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> Date: Mon, 08 Apr 2024 13:27:47 -0400 Message-ID: <yp1o7ajkbn0.fsf@HIDDEN> User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, Stefan Monnier <monnier@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> writes: >>> Maybe `describe-type` should lists the slots first and the docstring >>> underneath rather than other way around? >> >> That'd also be good. Then the doc string should say something like >> >> Object holding a description of a buffer state. >> It has the following Allocated Slots: >> >> Name Type Default >> =E2=80=94=E2=80=94=E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80= =94=E2=80=94 >> beg t (point-max) >> end t (point-min) >> before t nil >> next t nil >> >> BEG..END is the area that was changed and BEFORE is its previous >> content[...] > > OK, I'll switch the two, thanks. > >> (Btw, those "t" under "Type" are also somewhat mysterious. What do >> they signify?) > > `C-h o t RET` says: > > t=E2=80=99s value is t >=20=20=20=20=20 > Not documented as a variable. >=20=20=20=20=20 > Probably introduced at or before Emacs version 16. >=20=20=20=20=20 >=20=20=20=20=20 >=20=20=20=20=20 > t is also a type. >=20=20=20=20=20 > t is a type (of kind =E2=80=98built-in-class=E2=80=99). > Children =E2=80=98sequence=E2=80=99, =E2=80=98atom=E2=80=99. >=20=20=20=20=20 > Abstract supertype of everything. >=20=20=20=20=20 > This is a built-in type. >=20=20=20=20=20 > [back] > > We could put buttons in the "Type" column, but I'm not sure it'd be > better or worse (I'm worried about turning everything into a button). FWIW I'd vote for buttonify every type in our *Help* buffer. Andrea
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 17:17:59 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 13:17:59 2024 Received: from localhost ([127.0.0.1]:47538 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtsd5-0006Z5-65 for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 13:17:59 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:36878) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtsd1-0006Yj-1e for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 13:17:58 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 386E21000DD; Mon, 8 Apr 2024 13:17:41 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712596660; bh=xGWypuhh6SbD6ZvI4IwvfhQ+fbQU9hlauG6lKdXLz9w=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=QR42H4bZV1p907abQXrasSZiQVEOFjoDJqQvqlxI1vzy8HtiRl/attQqsmIv2PHsa usM13qPvXi57s7Ct0JhcqB3mmAD2ZBuV90B+1V6H2HlxvrQY5iGWppyPjVIM+lcbDG eKN74zq/5dYqbpzZs9NO1rbC2U/lYXVuhz89mVecpS71arZk8nmT97qRCQ8N2338VK Gqde0pU3AgJDx0BNsb2ScbTuiJUDaaWsZlqQXWSc2WXm6sRlgwonfDFnntsNHAitXH XhMdBNHf4+rrJeBTyCsMKr1aWo/mHXU7OZVWvd/8BsETV5lMv5TV37KyrFwfdaGmoO CdloiK11Cuvlg== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 3A25C100048; Mon, 8 Apr 2024 13:17:40 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 0F79B1202A2; Mon, 8 Apr 2024 13:17:40 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86msq3yhot.fsf@HIDDEN> (Eli Zaretskii's message of "Mon, 08 Apr 2024 18:53:22 +0300") Message-ID: <jwvcyqzdcmx.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> <86msq3yhot.fsf@HIDDEN> Date: Mon, 08 Apr 2024 13:17:37 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.017 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) >> Maybe `describe-type` should lists the slots first and the docstring >> underneath rather than other way around? > > That'd also be good. Then the doc string should say something like > > Object holding a description of a buffer state. > It has the following Allocated Slots: > > Name Type Default > =E2=80=94=E2=80=94=E2=80=94=E2=80=94 =E2=80=94=E2=80=94=E2= =80=94=E2=80=94 =E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=94= =E2=80=94 > beg t (point-max) > end t (point-min) > before t nil > next t nil > > BEG..END is the area that was changed and BEFORE is its previous > content[...] OK, I'll switch the two, thanks. > (Btw, those "t" under "Type" are also somewhat mysterious. What do > they signify?) `C-h o t RET` says: t=E2=80=99s value is t =20=20=20=20 Not documented as a variable. =20=20=20=20 Probably introduced at or before Emacs version 16. =20=20=20=20 =20=20=20=20 =20=20=20=20 t is also a type. =20=20=20=20 t is a type (of kind =E2=80=98built-in-class=E2=80=99). Children =E2=80=98sequence=E2=80=99, =E2=80=98atom=E2=80=99. =20=20=20=20 Abstract supertype of everything. =20=20=20=20 This is a built-in type. =20=20=20=20 [back] We could put buttons in the "Type" column, but I'm not sure it'd be better or worse (I'm worried about turning everything into a button). >> >> +(cl-defun track-changes-register ( signal &key nobefore disjoint imm= ediate) >> >> + "Register a new tracker and return a new tracker ID. >> > Please mention SIGNAL in the first line of the doc string. >> Hmm... having trouble making that fit on a single line. > Register a new tracker whose change-tracking function is SIGNAL. > Return the ID of the new tracker. Thanks. >> >> +By default SIGNAL is called as soon as convenient after a change, wh= ich is >> > ^^^^^^^^^^^^^^^^^^^^^ >> > "as soon as it's convenient", I presume? >> Other than the extra " it's", what is the difference? > Nothing. I indeed thing "it's" is missing there. My local native-English representative says that "both work fine" (somewhat dismissively, I must add). > My point is that by referencing funcall-later you can avoid the need > to explain what is already explained in that function's doc string. OK. >> >> +In order to prevent the upcoming change from being combined with the= previous >> >> +changes, SIGNAL needs to call `track-changes-fetch' before it return= s." >> > >> > This seems to contradict what the doc string says previously: that >> > SIGNAL should NOT call track-changes-fetch. >>=20 >> I don't kow where you see the docstring saying that. >> The closest I can find is: >>=20 >> When IMMEDIATE is non-nil, the SIGNAL should preferably not always c= all >> `track-changes-fetch', since that would defeat the purpose of this l= ibrary. >>=20 >> Note the "When IMMEDIATE is non-nil", "preferably", and "not always", >> and the fact that the reason is not that something will break but that >> some other solution would probably work better. > > Then maybe the sentence on which I commented should say > > Except when IMMEDIATE is non-nil, if SIGNAL needs to prevent the > upcoming change from being combined with the previous ones, it > should call `track-changes-fetch' before it returns. But that wouldn't say what I want to say. It'd want to call `track-changes-fetch' before it returns even if IMMEDIATE is non-nil. It just probably wouldn't want to do that for all calls to the signal. E.g. only for those calls where the second argument is non-nil (i.e. the disjointness signals). > In general, when I want to create a clean slate, I don't care too much > about the dirt I remove. Why is it important to signal errors because > a state I am dumping had some errors? I don't understand why you think it will signal an error? More to the point, it should signal an error only if I made a mistake in `track-changes.el` or if you messed with the internals. Note also that this function is itself internal. >> >> +;;;; Extra candidates for the API. >> >> +;; This could be a good alternative to using a temp-buffer like I us= ed in >> > ^^^= ^^^ >> > "I"? >> Yes, that refers to the code I wrote. > We don't usually leave such style in long-term comments and > documentation. `grep " I " lisp/**/*.el` suggests otherwise. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 16:06:34 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 12:06:34 2024 Received: from localhost ([127.0.0.1]:47481 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtrVw-00011u-Ur for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 12:06:34 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:18304) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtrVs-00010N-U2 for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 12:06:31 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 6848B1000DD; Mon, 8 Apr 2024 12:06:15 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712592374; bh=XrZIALznMd4ptZF6RpaujK5ZqFprQP2rWB3aztRKi80=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=kC0NMLAMeYZGhTChUwFcp5x9Ibeo4+mxGxFvYBVQ5ElLIbzdxYNDMpIiCDvceaC2H M51ZAIsniYD4DOgFyYErIva77A/R2jxmYViURtQ3F9pvlj5JZ1pa8/FsJK7wkemej3 wqnOFjCd0ICN2epBhOVNCYHvajkhsGObOhBL7AyjP+7oYnAF08CMcunUXw2GrJcUMk FXcHmkLJ/umCcEeyic5+7qBT7kmj/aeeGNPyMSedENmv4jy3nokspfFmtNJHwB+D4c ZZYPF+vDbSGhGjr2dkQQPzsL3k8mPjvQik0hwB71I0UoBsqx4nkh73czRirjDkxTrm 94aa1OM0GTwnQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 36431100048; Mon, 8 Apr 2024 12:06:14 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 0FD1D120495; Mon, 8 Apr 2024 12:06:14 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <87edbhnu53.fsf@localhost> (Ihor Radchenko's message of "Sun, 07 Apr 2024 14:07:36 +0000") Message-ID: <jwvjzl7dghm.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <87edbhnu53.fsf@localhost> Date: Mon, 08 Apr 2024 12:06:12 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.018 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) >> + "Object holding a description of a buffer state. >> +BEG..END is the area that was changed and BEFORE is its previous conten= t. >> +If the current buffer currently holds the content of the next state, yo= u can get >> +the contents of the previous state with: >> + >> + (concat (buffer-substring (point-min) beg) >> + before >> + (buffer-substring end (point-max))) >> + >> +NEXT is the next state object (i.e. a more recent state). >> +If NEXT is nil it means it's most recent state and it may be incomplete >> +\(BEG/END/BEFORE may be nil), in which case those fields will take their >> +values from `track-changes--before-(beg|end|before)' when the next >> +state is create." > > This docstring is a bit confusing. > If a state object is not the most recent, how come=20 > >> + (concat (buffer-substring (point-min) beg) >> + before >> + (buffer-substring end (point-max))) > > produces the previous content? Because it says "If the current buffer currently holds the content of the next state". [ "current ... currently" wasn't my best moment, admittedly. ] > And if the state object is the most recent, "it may be incomplete"... > So, when is it safe to use the above (concat ... ) call? You never want to use this call, it's only there to show in a concise manner how BEG/END/BEFORE relate and what information they're supposed to hold. >> +(defvar-local track-changes--before-beg (point-min) >> + "Beginning position of the remembered \"before string\".") >> +(defvar-local track-changes--before-end (point-min) >> + "End position of the text replacing the \"before string\".") > Why (point-min)? It will make the values dependent on the buffer > narrowing that happens to be active when the library if first loaded. > Which cannot be right. The precise value should hopefully never matter, barring bugs. I changed them to 0. >> +(defvar-local track-changes--buffer-size nil >> + "Current size of the buffer, as far as this library knows. >> +This is used to try and detect cases where buffer modifications are \"l= ost\".") > Just looking at the buffer size may miss unregistered edits that do not > change the total size of the buffer. Although I do not know a better > measure. `buffer-chars-modified-tic' may lead to false-positives > (Bug#51766). Yup, hence the "try to". >> +(cl-defun track-changes-register ( signal &key nobefore disjoint immedi= ate) >> + "Register a new tracker and return a new tracker ID. >> +SIGNAL is a function that will be called with one argument (the tracker= ID) >> +after the current buffer is modified, so that we can react to the chang= e. >> + ... >> +If optional argument DISJOINT is non-nil, SIGNAL is called every time w= e are >> +about to combine changes from \"distant\" parts of the buffer. >> +This is needed when combining disjoint changes into one bigger change >> +is unacceptable, typically for performance reasons. >> +These calls are distinguished from normal calls by calling SIGNAL with >> +a second argument which is the distance between the upcoming change and >> +the previous changes. > > This is a bit confusing. The first paragraph says that SIGNAL is called > with a single argument, but that it appears that two arguments may be > passed. I'd rather tell the calling convention early in the docstring. The second convention is used only when DISJOINT is non-nil, which is why it's described where we document the effect of DISJOINT. An alternative could be to make the `disjoint` arg hold the function to call to signal disjoint changes. >> + (unless nobefore >> + (setq track-changes--before-no nil) >> + (add-hook 'before-change-functions #'track-changes--before nil t)) >> + (add-hook 'after-change-functions #'track-changes--after nil t) > Note that all the changes made in indirect buffers will be missed. > See bug#60333. Yup. And misuses of `inhibit-modification-hooks` or `with-silent-modifications`. =F0=9F=99=81 >> +(defun track-changes-fetch (id func) >> ... >> + (unless (equal track-changes--buffer-size (buffer-size)) >> + (track-changes--recover-from-error)) >> + (let ((beg nil) >> + (end nil) >> + (before t) >> + (lenbefore 0) >> + (states ())) >> + ;; Transfer the data from `track-changes--before-string' >> + ;; to the tracker's state object, if needed. >> + (track-changes--clean-state) > >> +(defun track-changes--recover-from-error () >> ... >> + (setq track-changes--state (track-changes--state))) > > This will create a dummy state with > > (beg (point-max)) > (end (point-min)) > > such state will not pass (< beg end) assertion in > `track-changes--clean-state' called in `track-changes-fetch' immediately > after `track-changes--recover-from-error' I can't reproduce that. Do you have a recipe? AFAICT all the (< beg end) tests in `track-changes--clean-state` are conditional on `track-changes--before-clean` being nil, whereas `track-changes--recover-from-error` sets that var to `unset`. >> +(defun track-changes--in-revert (beg end before func) >> ... >> + (atomic-change-group >> + (goto-char end) >> + (insert-before-markers before) >> + (delete-region beg end) > > What happens if there are markers inside beg...end? During FUNC they're moved to BEG or END, and when we restore the original state, well... the undo machinery has some support to restore the markers where they were, but it's definitely not 100%. =F0=9F=99=81 >> +(defun track-changes-tests--random-word () >> + (let ((chars ())) >> + (dotimes (_ (1+ (random 12))) >> + (push (+ ?A (random (1+ (- ?z ?A)))) chars)) >> + (apply #'string chars))) > > If you are using random values for edits, how can such test be > reproduced? Luck? > Maybe first generate a random seed and then log it, so that the > failing test can be repeated if necessary with seed assigned manually. Good idea. But my attempt (see patch below) failed. I'm not sure what I'm doing wrong, but make test/lisp/emacs-lisp/track-changes-tests \ EMACS_EXTRAOPT=3D"--eval '(setq track-changes-tests--random-seed \= "15216888\")'" gives a different score each time. =F0=9F=99=81 Stefan diff --git a/test/lisp/emacs-lisp/track-changes-tests.el b/test/lisp/emacs-= lisp/track-changes-tests.el index cdccbe80299..eab9197030f 100644 --- a/test/lisp/emacs-lisp/track-changes-tests.el +++ b/test/lisp/emacs-lisp/track-changes-tests.el @@ -36,6 +36,11 @@ track-changes-tests--random-verbose (defun track-changes-tests--message (&rest args) (when track-changes-tests--random-verbose (apply #'message args))) =20 +(defvar track-changes-tests--random-seed + (let ((seed (number-to-string (random (expt 2 24))))) + (message "Random seed =3D %S" seed) + seed)) + (ert-deftest track-changes-tests--random () ;; Keep 2 buffers in sync with a third one as we make random ;; changes to that 3rd one. @@ -97,6 +102,8 @@ track-changes-tests--random (insert-into-buffer buf2) (should (equal (buffer-hash) (buffer-hash buf1))) (should (equal (buffer-hash) (buffer-hash buf2))) + (message "seeding with: %S" track-changes-tests--random-seed) + (random track-changes-tests--random-seed) (dotimes (_ 1000) (pcase (random 15) (0
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 15:53:46 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 11:53:46 2024 Received: from localhost ([127.0.0.1]:47451 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtrJW-0008Pz-Hp for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 11:53:45 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:37480) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rtrJU-0008Pb-FA for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 11:53:41 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rtrJF-0001ie-VX; Mon, 08 Apr 2024 11:53:26 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From: Date; bh=soUjVnmbpiQunzkAFi7eNvXJ9KyFxxLKVFXhWvos4U8=; b=V/9wpSM1SMevPJPHtKWz 6bG2ElQjvGrij4pmQR0/QqYRuwp1P0XcIY6PdEY+3EhfQPYhgSS8lvGkduRWh15gHmu3YYbx4ymb1 WfQVIUN/1sgtgGRdS3eYN85Cb9pwgzmgITFnSDlw4b22fJLKa9C95Gq/TXgYJP1pfHV4GXH2tVQj/ lIp56ZygpliubZy332psj3ieeiYsq+U0G5mCPGC186PM9o2CdGKNgq9yN/I/AqCrwoD624ZQtOkPy FAV38qN08nwbBGrTugyiNCIxxoxYOKImqcqkZ2nVFs23bmxP7UwRsUEsEKx5Oa0OEZJFQmQIHlNBw gLJwrILTXhnIyQ==; Date: Mon, 08 Apr 2024 18:53:22 +0300 Message-Id: <86msq3yhot.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwvplv0c662.fsf-monnier+emacs@HIDDEN> (message from Stefan Monnier on Mon, 08 Apr 2024 11:24:38 -0400) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> <jwvplv0c662.fsf-monnier+emacs@HIDDEN> MIME-version: 1.0 Content-type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > From: Stefan Monnier <monnier@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, acm@HIDDEN, yantar92@HIDDEN > Date: Mon, 08 Apr 2024 11:24:38 -0400 > > >> +(cl-defstruct (track-changes--state > >> + (:noinline t) > >> + (:constructor nil) > >> + (:constructor track-changes--state ())) > >> + "Object holding a description of a buffer state. > >> +BEG..END is the area that was changed and BEFORE is its previous content. > >> +If the current buffer currently holds the content of the next state, you can get > >> +the contents of the previous state with: > >> + > >> + (concat (buffer-substring (point-min) beg) > >> + before > >> + (buffer-substring end (point-max))) > >> + > >> +NEXT is the next state object (i.e. a more recent state). > >> +If NEXT is nil it means it's most recent state and it may be incomplete > >> +\(BEG/END/BEFORE may be nil), in which case those fields will take their > >> +values from `track-changes--before-(beg|end|before)' when the next > >> +state is create." > > > > This doc string should IMO include the form of the object, because > > without that BEG, END, BEFORE, and NEXT are "out of the blue", and > > it's entirely unclear what they allude to. > > AFAIK this docstring is displayed only by `describe-type` which shows > something like: > > track-changes--state is a type (of kind ‘cl-structure-class’) in ‘track-changes.el’. > Inherits from ‘cl-structure-object’. > > Object holding a description of a buffer state. > BEG..END is the area that was changed and BEFORE is its previous content. > If the current buffer currently holds the content of the next state, you can get > the contents of the previous state with: > > (concat (buffer-substring (point-min) beg) > before > (buffer-substring end (point-max))) > > NEXT is the next state object (i.e. a more recent state). > If NEXT is nil it means it's most recent state and it may be incomplete > (BEG/END/BEFORE may be nil), in which case those fields will take their > values from `track-changes--before-(beg|end|before)' when the next > state is create. > > Instance Allocated Slots: > > Name Type Default > ———— ———— ——————— > beg t (point-max) > end t (point-min) > before t nil > next t nil > > Specialized Methods: > > [...] > > so the "form of the object" is included. It's included, but _after_ explaining what each member of the object form means. That's bad from the methodological POV: we should first show the form and only afterwards describe each of its members. > Maybe `describe-type` should lists the slots first and the docstring > underneath rather than other way around? That'd also be good. Then the doc string should say something like Object holding a description of a buffer state. It has the following Allocated Slots: Name Type Default ———— ———— ——————— beg t (point-max) end t (point-min) before t nil next t nil BEG..END is the area that was changed and BEFORE is its previous content[...] (Btw, those "t" under "Type" are also somewhat mysterious. What do they signify?) > >> +(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) > >> + "Register a new tracker and return a new tracker ID. > > Please mention SIGNAL in the first line of the doc string. > > Hmm... having trouble making that fit on a single line. Register a new tracker whose change-tracking function is SIGNAL. Return the ID of the new tracker. > Could you clarify why you think SIGNAL needs to be on the first line? It's our convention to mention the mandatory arguments on the first line of the doc string. > The best I could come up with so far is: > > Register a new change tracker handled via SIGNAL. That's a good start, IMO, except that SIGNAL doesn't hadle the tracker, it handles changes, right? > >> +By default SIGNAL is called as soon as convenient after a change, which is > > ^^^^^^^^^^^^^^^^^^^^^ > > "as soon as it's convenient", I presume? > > Other than the extra " it's", what is the difference? Nothing. I indeed thing "it's" is missing there. > >> +usually right after the end of the current command. > > This should explicitly reference funcall-later, so that users could > > understand what "as soon as convenient" means. > > I don't see the point of telling which function we use: the programmers > who want to know that, can consult the code. As for explaining what "as > soon as convenient" means, that's what "usually right after the end of > the current command" is there for. My point is that by referencing funcall-later you can avoid the need to explain what is already explained in that function's doc string. You could, for example, simply say By default, SIGNAL is arranged to be called later by using `funcall-later'. > [ Also, I've changed the code to use `run-with-timer` in the mean time. ] That'd need a trivial change above, and I still think it's worthwhile, as it makes this doc string easier to grasp: if one already knows how run-with-timer works, they don't need anything else to be said; and if they don't, they can later read on that separately. IOW, you separate one complex description into two simpler ones: a win IME. > >> +In order to prevent the upcoming change from being combined with the previous > >> +changes, SIGNAL needs to call `track-changes-fetch' before it returns." > > > > This seems to contradict what the doc string says previously: that > > SIGNAL should NOT call track-changes-fetch. > > I don't kow where you see the docstring saying that. > The closest I can find is: > > When IMMEDIATE is non-nil, the SIGNAL should preferably not always call > `track-changes-fetch', since that would defeat the purpose of this library. > > Note the "When IMMEDIATE is non-nil", "preferably", and "not always", > and the fact that the reason is not that something will break but that > some other solution would probably work better. Then maybe the sentence on which I commented should say Except when IMMEDIATE is non-nil, if SIGNAL needs to prevent the upcoming change from being combined with the previous ones, it should call `track-changes-fetch' before it returns. > > Are all those assertions a good idea in this function? > > The library has a lot of `cl-assert`s which are all placed both as > documentation and as sanity checks. All those should hopefully be true > all the time barring bugs in the code. > > > I can envision using it as a cleanup, in which case assertions will > > not be appreciated. > > Not sure what you mean by "using it as a cleanup"? > Its purpose is not to cleanup the general state of track-changes, but > to create a new, clean `track-changes--state`. In general, when I want to create a clean slate, I don't care too much about the dirt I remove. Why is it important to signal errors because a state I am dumping had some errors? > >> +;;;; Extra candidates for the API. > >> +;; This could be a good alternative to using a temp-buffer like I used in > > ^^^^^^ > > "I"? > > Yes, that refers to the code I wrote. We don't usually leave such style in long-term comments and documentation. Thanks.
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 8 Apr 2024 15:25:16 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 08 11:25:16 2024 Received: from localhost ([127.0.0.1]:47387 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtqrx-0006Ac-Mw for submit <at> debbugs.gnu.org; Mon, 08 Apr 2024 11:25:16 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:26782) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtqrn-00068m-4q for 70077 <at> debbugs.gnu.org; Mon, 08 Apr 2024 11:25:13 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id A80A844170E; Mon, 8 Apr 2024 11:24:49 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712589887; bh=QrvdbIxaISx5Jy6QjtYdnyJxrb+xpLaFO1qeqETiNmI=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=mowZ3c/ze8WqYK7Zwqc3T7JKVkROEPdiB10YuSlGIHrBhgOSmYZGF6z3bKWl/LHql g2yugzfsmkj1u1agL80VqOkGOh520Vczg6q+9ECAlrOf8/2gJAbFQiKUI4RTIjei0a Lln28wJeH/V6t9qUw9qmIpG4xLe2l3grMPMvxYdd50bNNtwUjC1OBwqtGlSm8btFWD IvIjnpdJ3XdDbvRp7OPIy/qdq3wZhnpZn1NNWe1v70gMNsEjiLQnthDpHmsxBOA7u9 Fyj2oVaLN+MKEC6t5DGa9Xh3vC/uKNM8fDtma6uMnhdIHl0Tm2Pe0UEQnjd7qE9EFb E/jE1Qe1otsCQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 774C24416D2; Mon, 8 Apr 2024 11:24:47 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 48BC8120679; Mon, 8 Apr 2024 11:24:47 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86frvy51af.fsf@HIDDEN> (Eli Zaretskii's message of "Sat, 06 Apr 2024 11:43:36 +0300") Message-ID: <jwvplv0c662.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <86frvy51af.fsf@HIDDEN> Date: Mon, 08 Apr 2024 11:24:38 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.026 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) >> +(cl-defstruct (track-changes--state >> + (:noinline t) >> + (:constructor nil) >> + (:constructor track-changes--state ())) >> + "Object holding a description of a buffer state. >> +BEG..END is the area that was changed and BEFORE is its previous conten= t. >> +If the current buffer currently holds the content of the next state, yo= u can get >> +the contents of the previous state with: >> + >> + (concat (buffer-substring (point-min) beg) >> + before >> + (buffer-substring end (point-max))) >> + >> +NEXT is the next state object (i.e. a more recent state). >> +If NEXT is nil it means it's most recent state and it may be incomplete >> +\(BEG/END/BEFORE may be nil), in which case those fields will take their >> +values from `track-changes--before-(beg|end|before)' when the next >> +state is create." > > This doc string should IMO include the form of the object, because > without that BEG, END, BEFORE, and NEXT are "out of the blue", and > it's entirely unclear what they allude to. AFAIK this docstring is displayed only by `describe-type` which shows something like: track-changes--state is a type (of kind =E2=80=98cl-structure-class=E2= =80=99) in =E2=80=98track-changes.el=E2=80=99. Inherits from =E2=80=98cl-structure-object=E2=80=99. =20=20=20=20 Object holding a description of a buffer state. BEG..END is the area that was changed and BEFORE is its previous conten= t. If the current buffer currently holds the content of the next state, yo= u can get the contents of the previous state with: =20=20=20=20 (concat (buffer-substring (point-min) beg) before (buffer-substring end (point-max))) =20=20=20=20 NEXT is the next state object (i.e. a more recent state). If NEXT is nil it means it's most recent state and it may be incomplete (BEG/END/BEFORE may be nil), in which case those fields will take their values from `track-changes--before-(beg|end|before)' when the next state is create. =20=20=20=20 Instance Allocated Slots: =20=20=20=20 Name Type Default =E2=80=94=E2=80=94=E2=80=94=E2=80=94 =E2=80=94=E2=80=94=E2= =80=94=E2=80=94 =E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=94= =E2=80=94 beg t (point-max) end t (point-min) before t nil next t nil =20=20=20=20 Specialized Methods: =20=20=20=20 [...] so the "form of the object" is included. We don't have much practice with docstrings for `cl-defstruct`, but I tried to follow the same kind of conventions we use for functions, taking object slots as equivalent to formal arguments. Maybe `describe-type` should lists the slots first and the docstring underneath rather than other way around? >> +(cl-defun track-changes-register ( signal &key nobefore disjoint immedi= ate) >> + "Register a new tracker and return a new tracker ID. > Please mention SIGNAL in the first line of the doc string. Hmm... having trouble making that fit on a single line. Could you clarify why you think SIGNAL needs to be on the first line? The best I could come up with so far is: Register a new change tracker handled via SIGNAL. >> +By default SIGNAL is called as soon as convenient after a change, which= is > ^^^^^^^^^^^^^^^^^^^^^ > "as soon as it's convenient", I presume? Other than the extra " it's", what is the difference? >> +usually right after the end of the current command. > This should explicitly reference funcall-later, so that users could > understand what "as soon as convenient" means. I don't see the point of telling which function we use: the programmers who want to know that, can consult the code. As for explaining what "as soon as convenient" means, that's what "usually right after the end of the current command" is there for. [ Also, I've changed the code to use `run-with-timer` in the mean time. ] >> +In order to prevent the upcoming change from being combined with the pr= evious >> +changes, SIGNAL needs to call `track-changes-fetch' before it returns." > > This seems to contradict what the doc string says previously: that > SIGNAL should NOT call track-changes-fetch. I don't kow where you see the docstring saying that. The closest I can find is: When IMMEDIATE is non-nil, the SIGNAL should preferably not always call `track-changes-fetch', since that would defeat the purpose of this libr= ary. Note the "When IMMEDIATE is non-nil", "preferably", and "not always", and the fact that the reason is not that something will break but that some other solution would probably work better. [ I changed "preferably" into "probably" since it's just a guess of mine rather than a request: there might be a good reason out there to prefer track-changes even for such a use case. ] >> + ;; The states are disconnected from the latest state because >> + ;; we got out of sync! >> + (cl-assert (eq (track-changes--state-before (car states)) 'error)) > This seem to mean Emacs will signal an error in this case, not pass > 'error' in BEFORE? No, this verifies that the states were disconnected on purpose by `track-changes--recover-from-error` rather than due to some bug in the code. >> +(defun track-changes--clean-state () >> + (cond >> + ((null track-changes--state) >> + (cl-assert track-changes--before-clean) >> + (cl-assert (null track-changes--buffer-size)) >> + ;; No state has been created yet. Do it now. >> + (setq track-changes--buffer-size (buffer-size)) >> + (when track-changes--before-no >> + (setq track-changes--before-string (buffer-size))) >> + (setq track-changes--state (track-changes--state))) >> + (track-changes--before-clean nil) >> + (t >> + (cl-assert (<=3D (track-changes--state-beg track-changes--state) >> + (track-changes--state-end track-changes--state))) >> + (let ((actual-beg (track-changes--state-beg track-changes--state)) >> + (actual-end (track-changes--state-end track-changes--state))) >> + (if track-changes--before-no >> + (progn >> + (cl-assert (integerp track-changes--before-string)) >> + (setf (track-changes--state-before track-changes--state) >> + (- track-changes--before-string >> + (- (buffer-size) (- actual-end actual-beg)))) >> + (setq track-changes--before-string (buffer-size))) >> + (cl-assert (<=3D track-changes--before-beg >> + actual-beg actual-end >> + track-changes--before-end)) >> + (cl-assert (null (track-changes--state-before track-changes--st= ate))) > > Are all those assertions a good idea in this function? The library has a lot of `cl-assert`s which are all placed both as documentation and as sanity checks. All those should hopefully be true all the time barring bugs in the code. > I can envision using it as a cleanup, in which case assertions will > not be appreciated. Not sure what you mean by "using it as a cleanup"? Its purpose is not to cleanup the general state of track-changes, but to create a new, clean `track-changes--state`. >> +;;;; Extra candidates for the API. >> +;; This could be a good alternative to using a temp-buffer like I used = in > ^^^^^^ > "I"? Yes, that refers to the code I wrote. >> +;; Eglot, since presumably we've just been changing this very area of t= he >> +;; buffer, so the gap should be ready nearby, >> +;; It may seem silly to go back to the previous state, since we could h= ave >> +;; used `before-change-functions' to run FUNC right then when we were in >> +;; that state. The advantage is that with track-changes we get to deci= de >> +;; retroactively which state is the one for which we want to call FUNC = and >> +;; which BEG..END to use: when that state was current we may have known >> +;; then that it would be "the one" but we didn't know what BEG and END >> +;; should be because those depend on the changes that came afterwards. > > Suggest to reword (or remove) this comment, as it sounds like > development-time notes. This is in the "Extra candidates for the API" section, which holds a bunch of things which might be useful or might not. >> +(defun diff--track-changes-function (beg end _before) >> + (with-demoted-errors "%S" > Why did you need with-demoted-errors here? It used to be `ignore-errors` because back in 1999 we didn't have `with-demoted-errors`. > Last, but not least: this needs suitable changes in NEWS and ELisp > manual. Working on it. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 7 Apr 2024 15:48:22 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sun Apr 07 11:48:21 2024 Received: from localhost ([127.0.0.1]:44344 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtUkm-0006Ex-VM for submit <at> debbugs.gnu.org; Sun, 07 Apr 2024 11:48:21 -0400 Received: from wout3-smtp.messagingengine.com ([64.147.123.19]:47305) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <dmitry@HIDDEN>) id 1rtUkk-0006DQ-5l for 70077 <at> debbugs.gnu.org; Sun, 07 Apr 2024 11:48:19 -0400 Received: from compute6.internal (compute6.nyi.internal [10.202.2.47]) by mailout.west.internal (Postfix) with ESMTP id 9D9453200A16; Sun, 7 Apr 2024 11:48:04 -0400 (EDT) Received: from mailfrontend2 ([10.202.2.163]) by compute6.internal (MEProxy); Sun, 07 Apr 2024 11:48:05 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gutov.dev; h=cc :cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1712504884; x=1712591284; bh=BAJoCLaXy9xEiC7L9PrfUKbpV6z2Neymc/TM0bROAhc=; b= YAnvEkEHWiJ2InJJeIcBe0D2UEpvbk4dbdTGYfNHjvsZkn/QI5K7C9kzWZueSHK0 jE+ReyORYrZdbSa86XsJqcnCkZaLOH2jmziGEPMYuv9DsRa/CWOm39VEUKagjKzx rQNegIVrJAy34LAzd/ONtBE9JDggSty2G8ZGT5aj5Klb7oMIhe8oJJtNpiAh3gJT un4z2eb3eynGUQ7hUmi/t/78KZTilSVSgF6wpSrCRbdMkbNmwheR0oD6NPOkiqMk Bda0SOdKMs/hNVM3sg9uer/FBM8HCP1pIAivR+j7P0f/2Jrz7aX4uJ1YevXDcVKP yYWrrTyKW5KfV44ANe7wvw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t=1712504884; x= 1712591284; bh=BAJoCLaXy9xEiC7L9PrfUKbpV6z2Neymc/TM0bROAhc=; b=A Dlqki/6KJR9dnaSHkcjPk/GzT0ScbKIMcmPg/Fl+bxIz3zPs5Zyk+IseQ7Pi4Vwu J3nJ+NonpFR6FsX/jAIG9LctH0mFBX5ySjWZibAHZCaBbxBRDpN6+wE31Tv80fY8 zTUeUTHXmI9bskUhBWHh/t33fzV3ND/swJjGSZrt8mPlkwXN2LcICNS3QPfqvnXc Y1X5K/5FdwpWZhyUEZWYty16jJcHVdAzTr4qhZ6Egmp2+DseAtIFddMblfkfieCO VfoLvtVfN7XzwORIdSCxI1vxjKW0mfvzAMgy2Tl/9m3oQb32HBgX6IDRFavmTxie ieoQHDqAhHnzeOQlqGP8w== X-ME-Sender: <xms:M8ASZg-8cEheTD7ghITuxJoC2r4iZfMnffRqP6GxO9dKbQuQlJht6A> <xme:M8ASZouov8i6Y20_Gl542UIsPhKqcqAP3USuyVk4qJav6j_qZ9jLvokE6FDE3uLEi iz0BONoZVkv0lUXrb4> X-ME-Received: <xmr:M8ASZmD9i4yFkZzWrDwwlQJ9yMSPRCTrpqES44yxo0wCfSazxmBxnr1O1JhaHEs_z4pc> X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrudeggedgledvucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepkfffgggfuffvvehfhfgjtgfgsehtjeertddtvdejnecuhfhrohhmpeffmhhi thhrhicuifhuthhovhcuoegumhhithhrhiesghhuthhovhdruggvvheqnecuggftrfgrth htvghrnhepteduleejgeehtefgheegjeekueehvdevieekueeftddvtdevfefhvdevgedu jeehnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepug hmihhtrhihsehguhhtohhvrdguvghv X-ME-Proxy: <xmx:M8ASZgeOorZQqvCjB9HgKXLoDUvSY2sppQzdzKIt1EH9_OugV_RCOQ> <xmx:M8ASZlOHiAvAB-bgP6nF1lOQtuhADlltSAezkkcfCEx5nX0MQo8Rcw> <xmx:M8ASZqnMIM7wk1TcuRjH54hrqlKsPWV9P7wJylQaZKRYSBLcyZ2Blw> <xmx:M8ASZnuLQOjKmOsyQ9PK4BPGEDCBpEFHvWKeDaKbuVIrse8FcHc2Mg> <xmx:NMASZqruq-mBGjwiHo-opi8t2RQixe86S6NbqAYd3tyPdCEuFrPvMoNauNuN> Feedback-ID: i0e71465a:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Sun, 7 Apr 2024 11:48:01 -0400 (EDT) Message-ID: <b80a8d7a-e7cb-4057-a86e-e7d7012621ef@HIDDEN> Date: Sun, 7 Apr 2024 18:47:59 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: bug#70077: An easier way to track buffer changes Content-Language: en-US To: Stefan Monnier <monnier@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <a4cdc627-1513-4d71-99b6-0f3e95870fd5@HIDDEN> <jwvmsq6gtyv.fsf-monnier+emacs@HIDDEN> <jwvr0fhz1az.fsf-monnier+emacs@HIDDEN> From: Dmitry Gutov <dmitry@HIDDEN> In-Reply-To: <jwvr0fhz1az.fsf-monnier+emacs@HIDDEN> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) On 07/04/2024 17:40, Stefan Monnier wrote: >>> Eglot is a "ELPA core" package. >>> Will we put `track-changes' into ELPA as well? >> The part of the patch that touches `eglot.el` is not indispensable, but >> yes, that's indeed something I've been wondering as well, seeing how >> it could be useful for third party packages like Lsp-mode, Crdt, >> and more. > BTW, by that I meant that maybe it should live in `elpa.git` (i.e. in > GNU ELPA instead of in Emacs). This is especially since I'm not sure we > want to push this as "the" API. In that case, the change to Eglot might have to be postponed (since we can't make a built-in package depend on code that could be absent). I don't have an opinion on the API itself, FWIW.
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 7 Apr 2024 14:40:23 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sun Apr 07 10:40:23 2024 Received: from localhost ([127.0.0.1]:44296 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtTh0-0000HT-UY for submit <at> debbugs.gnu.org; Sun, 07 Apr 2024 10:40:23 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:7837) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtTgy-0000HD-BQ for 70077 <at> debbugs.gnu.org; Sun, 07 Apr 2024 10:40:21 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 1C8E544096B; Sun, 7 Apr 2024 10:40:07 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712500805; bh=PMnKQDiPaxHZbYnEEDCaypyXq1bK8Qz7/0sY/ereXkU=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=ctH4eLtSflRNs38skuqLjQcFvAaPj4IYuDFcxDAYeOSq5WNI2QJkrDiwHz3XsFkjs a/OHO+7zsCvcw1ZLAmDghdxSwybBKIu5clrp6wpNygz7/ls5eBv0j9zPik6dZ4uk67 mlOxDxBrnEIA4QQXfMd5KtLJEr2+r3RPt9lLlLKy6nFZ4J1O1/fI6OaoBMxv/fRm9V Im1OzoY8CiMFZnS3+iAiv7hGqP4wO2l+aXCEUt4N5yY/Axpe2gbhN21zbvZq09P3Q0 uMK2ExjBfsNfCsxkY3nbQjBxuQooiUemp2e/2tOILnV3wgsqs9kwtqfU+3/MFUso1C ulQxPRCCbARJQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id E700A44095E; Sun, 7 Apr 2024 10:40:05 -0400 (EDT) Received: from alfajor (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id B2AF6120484; Sun, 7 Apr 2024 10:40:05 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Dmitry Gutov <dmitry@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvmsq6gtyv.fsf-monnier+emacs@HIDDEN> (Stefan Monnier's message of "Sat, 06 Apr 2024 15:44:09 -0400") Message-ID: <jwvr0fhz1az.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <a4cdc627-1513-4d71-99b6-0f3e95870fd5@HIDDEN> <jwvmsq6gtyv.fsf-monnier+emacs@HIDDEN> Date: Sun, 07 Apr 2024 10:40:05 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.010 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) >> Eglot is a "ELPA core" package. >> Will we put `track-changes' into ELPA as well? > > The part of the patch that touches `eglot.el` is not indispensable, but > yes, that's indeed something I've been wondering as well, seeing how > it could be useful for third party packages like Lsp-mode, Crdt, > and more. BTW, by that I meant that maybe it should live in `elpa.git` (i.e. in GNU ELPA instead of in Emacs). This is especially since I'm not sure we want to push this as "the" API. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 7 Apr 2024 14:07:37 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sun Apr 07 10:07:37 2024 Received: from localhost ([127.0.0.1]:44261 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtTBG-0005ug-OY for submit <at> debbugs.gnu.org; Sun, 07 Apr 2024 10:07:36 -0400 Received: from mout01.posteo.de ([185.67.36.65]:33021) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rtTBB-0005tq-WC for 70077 <at> debbugs.gnu.org; Sun, 07 Apr 2024 10:07:32 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id C4C3C240029 for <70077 <at> debbugs.gnu.org>; Sun, 7 Apr 2024 16:07:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1712498836; bh=0q+XMW+bJ2nVOoAWS72MjFAriW4tJ+04YXa+tJ4erzU=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=OcccZB4hxJicjQVBmvGPEjYdzCPaTZ5ua7C6FgS9543MPBOQfnBE/LuR8CAnn5Orp FTTJkO2sWYC8H5tw06Ax5ZouWsa4eWwZnnI/0gMUI/2yEsFO2BhdPVC0lEqtZ+yPuU yGNg/7KsXv+sh9TxoSrvsU0DscW1doJEXfDPFhbZWlQ5/0Lj42xnJfmIntbZvx+bet 15G44rVsHCz0Giu8VphsjozHlLrlAs/6NAlYcEsOHSUBD8AhQp0AIrekxRTB3Bytf3 ns9cT5V+wPkmuD2cUjDuutWQBnTPWD2hewyeY1eMu94jpf7xKACCc2OjASDqdUdeas hGPEuEA/ZGegA== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4VCDYv5YKPz9rxK; Sun, 7 Apr 2024 16:07:15 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> Date: Sun, 07 Apr 2024 14:07:36 +0000 Message-ID: <87edbhnu53.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) Stefan Monnier <monnier@HIDDEN> writes: > +(cl-defstruct (track-changes--state > + (:noinline t) > + (:constructor nil) > + (:constructor track-changes--state ())) > + "Object holding a description of a buffer state. > +BEG..END is the area that was changed and BEFORE is its previous content. > +If the current buffer currently holds the content of the next state, you can get > +the contents of the previous state with: > + > + (concat (buffer-substring (point-min) beg) > + before > + (buffer-substring end (point-max))) > + > +NEXT is the next state object (i.e. a more recent state). > +If NEXT is nil it means it's most recent state and it may be incomplete > +\(BEG/END/BEFORE may be nil), in which case those fields will take their > +values from `track-changes--before-(beg|end|before)' when the next > +state is create." This docstring is a bit confusing. If a state object is not the most recent, how come > + (concat (buffer-substring (point-min) beg) > + before > + (buffer-substring end (point-max))) produces the previous content? And if the state object is the most recent, "it may be incomplete"... So, when is it safe to use the above (concat ... ) call? > +(defvar-local track-changes--before-beg (point-min) > + "Beginning position of the remembered \"before string\".") > +(defvar-local track-changes--before-end (point-min) > + "End position of the text replacing the \"before string\".") Why (point-min)? It will make the values dependent on the buffer narrowing that happens to be active when the library if first loaded. Which cannot be right. > +(defvar-local track-changes--buffer-size nil > + "Current size of the buffer, as far as this library knows. > +This is used to try and detect cases where buffer modifications are \"lost\".") Just looking at the buffer size may miss unregistered edits that do not change the total size of the buffer. Although I do not know a better measure. `buffer-chars-modified-tic' may lead to false-positives (Bug#51766). > +(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) > + "Register a new tracker and return a new tracker ID. > +SIGNAL is a function that will be called with one argument (the tracker ID) > +after the current buffer is modified, so that we can react to the change. > + ... > +If optional argument DISJOINT is non-nil, SIGNAL is called every time we are > +about to combine changes from \"distant\" parts of the buffer. > +This is needed when combining disjoint changes into one bigger change > +is unacceptable, typically for performance reasons. > +These calls are distinguished from normal calls by calling SIGNAL with > +a second argument which is the distance between the upcoming change and > +the previous changes. This is a bit confusing. The first paragraph says that SIGNAL is called with a single argument, but that it appears that two arguments may be passed. I'd rather tell the calling convention early in the docstring. > + (unless nobefore > + (setq track-changes--before-no nil) > + (add-hook 'before-change-functions #'track-changes--before nil t)) > + (add-hook 'after-change-functions #'track-changes--after nil t) Note that all the changes made in indirect buffers will be missed. See bug#60333. > +(defun track-changes-fetch (id func) > ... > + (unless (equal track-changes--buffer-size (buffer-size)) > + (track-changes--recover-from-error)) > + (let ((beg nil) > + (end nil) > + (before t) > + (lenbefore 0) > + (states ())) > + ;; Transfer the data from `track-changes--before-string' > + ;; to the tracker's state object, if needed. > + (track-changes--clean-state) > +(defun track-changes--recover-from-error () > ... > + (setq track-changes--state (track-changes--state))) This will create a dummy state with (beg (point-max)) (end (point-min)) such state will not pass (< beg end) assertion in `track-changes--clean-state' called in `track-changes-fetch' immediately after `track-changes--recover-from-error' > +(defun track-changes--in-revert (beg end before func) > ... > + (atomic-change-group > + (goto-char end) > + (insert-before-markers before) > + (delete-region beg end) What happens if there are markers inside beg...end? > +(defun track-changes-tests--random-word () > + (let ((chars ())) > + (dotimes (_ (1+ (random 12))) > + (push (+ ?A (random (1+ (- ?z ?A)))) chars)) > + (apply #'string chars))) If you are using random values for edits, how can such test be reproduced? Maybe first generate a random seed and then log it, so that the failing test can be repeated if necessary with seed assigned manually. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 6 Apr 2024 19:44:31 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Apr 06 15:44:31 2024 Received: from localhost ([127.0.0.1]:41001 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rtBxm-0001se-Pn for submit <at> debbugs.gnu.org; Sat, 06 Apr 2024 15:44:30 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:32148) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rtBxg-0001sN-K6 for 70077 <at> debbugs.gnu.org; Sat, 06 Apr 2024 15:44:28 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 019BA4414D6; Sat, 6 Apr 2024 15:44:12 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712432650; bh=r7urAk381/5UGnTb/PgeAQMP//aio0QtsFxm9KBVhh0=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=W9mHsHFfssX5Jk7TfAjoKBw8z58W52KDgFt3V6qtzwHSdZrqkENp101jXgsjPJ+g8 TPgzJvSS9Y1zoiwvMz8a7UxDMCZ7tZOHnNco3a7/X6qq0GUfhB4G08GIJy2QLcrJlN k3iBMmD1VkqBE7pAFgjIeBDT/RQIb+5kDuglQvtVR1Qnpm7FWYzSUiOOu9xxKwgnyw h1aj0UUl9NGjJDsEL2yAxzanNPx/rqEcx8kRz0I8AkEvmfnTpfGiKhSnrp8S3mHtgj ZmlOQMkKrC/v0oxgBWsR8BtTy47ePQtnWoNjfRWTNjJmyHwCVWkpjfMWcqffLXoOwI pWacVUVx/+nVg== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id C67C74414BF; Sat, 6 Apr 2024 15:44:10 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 93D8212024C; Sat, 6 Apr 2024 15:44:10 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Dmitry Gutov <dmitry@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <a4cdc627-1513-4d71-99b6-0f3e95870fd5@HIDDEN> (Dmitry Gutov's message of "Sat, 6 Apr 2024 20:37:29 +0300") Message-ID: <jwvmsq6gtyv.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> <a4cdc627-1513-4d71-99b6-0f3e95870fd5@HIDDEN> Date: Sat, 06 Apr 2024 15:44:09 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.010 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) >> diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el >> index 7f4284bf09d..00c09d7f06b 100644 >> --- a/lisp/progmodes/eglot.el >> +++ b/lisp/progmodes/eglot.el >> @@ -110,6 +110,7 @@ >> (require 'text-property-search nil t) >> (require 'diff-mode) >> (require 'diff) >> +(require 'track-changes) > Eglot is a "ELPA core" package. > Will we put `track-changes' into ELPA as well? The part of the patch that touches `eglot.el` is not indispensable, but yes, that's indeed something I've been wondering as well, seeing how it could be useful for third party packages like Lsp-mode, Crdt, and more. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 6 Apr 2024 17:37:49 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Apr 06 13:37:49 2024 Received: from localhost ([127.0.0.1]:40923 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rt9zA-0003HK-QK for submit <at> debbugs.gnu.org; Sat, 06 Apr 2024 13:37:49 -0400 Received: from wout2-smtp.messagingengine.com ([64.147.123.25]:34497) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <dmitry@HIDDEN>) id 1rt9z7-0003G6-0h for 70077 <at> debbugs.gnu.org; Sat, 06 Apr 2024 13:37:46 -0400 Received: from compute2.internal (compute2.nyi.internal [10.202.2.46]) by mailout.west.internal (Postfix) with ESMTP id B0B5332009F8; Sat, 6 Apr 2024 13:37:32 -0400 (EDT) Received: from mailfrontend2 ([10.202.2.163]) by compute2.internal (MEProxy); Sat, 06 Apr 2024 13:37:33 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gutov.dev; h=cc :cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1712425052; x=1712511452; bh=dsQC201Gioi/v8U+GO77GZcj49ZmKkQp05koM7v8aTk=; b= ocEAHTRMZ2rRP2P41qp9sQT8jjSDoj5Jw2QYCTDRphdrxJBPaWDUFairm/H7QqRX BT+iEXATJJJenLYivt4+RkK5YiNsMrK5knUld7piZQWIdYb0i2UmmWGGwZv0Hc8d sGHRaRu0DHwPUBfX8u1rrkRcRpf1nBGOVGd7TDKWvkiUiDiRsG9SyjbaMdODnwFn JJWxphqVEZnQRxDJhuAVOJ03GgBA1UrkERsLDHuELxxsSN08prBvwEracd90YB/r LdC/4gTVlCUU766MtJTk9WsprR+tsxba6NhRosvzvTchPVRKcrSYGLQDzigwTXR4 5XcWLbO1c/ax+RHW3mnh2w== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t=1712425052; x= 1712511452; bh=dsQC201Gioi/v8U+GO77GZcj49ZmKkQp05koM7v8aTk=; b=R 1H5MtAgAeLZ/lCw3b+/R3822SmnuDWSJfyx/ubg+CT4bpo12Pq7mwEQzV4FuPArY 0qQoODf/CI27Cj9pBauPAhoFfihqHdc3Y/8Pi0sWMSHJl5c0Qi5Cia0Gv2dO8S0M PkEbKOuNeAztCuA4ECaL/SWmXgGVNve51pyg5Vt+oPp5NS2e7I/Aa5EjVSUIocll mc8wIblG/6w3fPM/Lig5GTEM0Q9dozYUa7oqVNyRPt1jByIwFpeevtaiXt2yM3YV QLThEDXlRiTMyu26eHzKkCFzrqA+GTcnsClTvHGc2+jgSgxuZ0eA8r1BIC16NFL6 fI4O4JsU0JA2n5wmTX+qw== X-ME-Sender: <xms:W4gRZi9tKwRD3wC110LMQM878ng5PE8kOJtdZvUMDtm_gYPlVznaxw> <xme:W4gRZivT33tKlvnlK0h0Ae0GX64LpB-YILSypKQVX35jjVQi2FNz8buZRCGI7yFSz kHsZ5qxnHBGSQwVEzA> X-ME-Received: <xmr:W4gRZoDjCcOiBxgzREYTXmcPe-Y_tka78ZHH3f7Y297HtHLOrHTksDC0BAV8s1i53Oce> X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrudegvddgudduiecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enucfjughrpefkffggfgfuvfevfhfhjggtgfesthejredttddvjeenucfhrhhomhepffhm ihhtrhihucfiuhhtohhvuceoughmihhtrhihsehguhhtohhvrdguvghvqeenucggtffrrg htthgvrhhnpeetudeljeegheetgfehgeejkeeuhedvveeikeeufedtvddtveefhfdvveeg udejheenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpe gumhhithhrhiesghhuthhovhdruggvvh X-ME-Proxy: <xmx:W4gRZqdJfuyF1LqSR09Oa660kNZjjSjc--hXv4JtfDrmPutV8HRTVA> <xmx:W4gRZnMqKRzWfFKle9tI6jffw-oN9BrqsEuAYFr4pvLbr4c31-XhvA> <xmx:W4gRZkkBlVZnsem_2B3v56yiR0Tm1gQ4uvpJ3LDxMI7BG9jYwqJHrQ> <xmx:W4gRZpuzgqz4w7wosiAwY9phHJlBnpTtPt8NkrrAH4nfcsCf79ri9A> <xmx:XIgRZsr6vvl14u7mfqcNi9QLKpY158EHmD0yj5G8Hx039WHkFwKx7h5Lindw> Feedback-ID: i0e71465a:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Sat, 6 Apr 2024 13:37:30 -0400 (EDT) Message-ID: <a4cdc627-1513-4d71-99b6-0f3e95870fd5@HIDDEN> Date: Sat, 6 Apr 2024 20:37:29 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: bug#70077: An easier way to track buffer changes Content-Language: en-US To: Stefan Monnier <monnier@HIDDEN>, 70077 <at> debbugs.gnu.org References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> From: Dmitry Gutov <dmitry@HIDDEN> In-Reply-To: <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) On 06/04/2024 01:12, Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors wrote: > diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el > index 7f4284bf09d..00c09d7f06b 100644 > --- a/lisp/progmodes/eglot.el > +++ b/lisp/progmodes/eglot.el > @@ -110,6 +110,7 @@ > (require 'text-property-search nil t) > (require 'diff-mode) > (require 'diff) > +(require 'track-changes) Eglot is a "ELPA core" package. Will we put `track-changes' into ELPA as well?
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 6 Apr 2024 08:43:54 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Apr 06 04:43:54 2024 Received: from localhost ([127.0.0.1]:38206 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rt1eT-0005gC-UP for submit <at> debbugs.gnu.org; Sat, 06 Apr 2024 04:43:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:46238) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rt1eR-0005fL-3V for 70077 <at> debbugs.gnu.org; Sat, 06 Apr 2024 04:43:53 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rt1eE-0003Cf-VI; Sat, 06 Apr 2024 04:43:38 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=Jiu2fiCBqPhYIp0Vdazq4VEmBhaG6Ji5mcRroBuSdU4=; b=AVkBQKTKRwk9 PxedgmLasWc0LvWLX4fERpm17IFHfja+/PxpgqzlI5IMV5J6LX74cj86hoD26lt0IQFpGYqgcTYXO 5NhXlUFqxZDNr/1CRssou3PLr/p2z9TNWYMIl23sg9bUSgPk2haHFHCfXFvpUk8RheJMPHKJS5IWi heREzv4f2m7SiVb2GhYLCF7ioXzMvY/HsbLfHoivLWaszYkP+x2FiG192uEOwgDa04CjQLkz9RVXo C+ig4v28n/tu4nLZXcalUPRYRFHRiM24+kXGeGoSsywO/KLb16fSP4me5z3DqDGEFchGNmU2O+uqd n21+0IsKUgs0IWra8KSgdg==; Date: Sat, 06 Apr 2024 11:43:36 +0300 Message-Id: <86frvy51af.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> (bug-gnu-emacs@HIDDEN) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: acm@HIDDEN, yantar92@HIDDEN, 70077 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) > Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN> > Date: Fri, 05 Apr 2024 18:12:55 -0400 > From: Stefan Monnier via "Bug reports for GNU Emacs, > the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> > > +;; This library is a layer of abstraction above `before-change-functions' > +;; and `after-change-functions' which takes care of accumulating changes > +;; until a time when its client finds it convenient to react to them. > +;; > +;; It provides an API that is easier to use correctly than our > +;; `*-change-functions` hooks. Problems that it claims to solve: Please redo all the quoting from `like this` to one of the two styles we use in our documentation. > +(unless (fboundp 'funcall-later) > + (defun funcall-later (&rest args) > + ;; FIXME: Not sure if `run-with-timer' preserves ordering between > + ;; different calls with the same target time. > + (apply #'run-with-timer 0 nil args))) Both funcall-later and run-with-timer?? > + > +;;;; Internal types and variables. > + > +(cl-defstruct (track-changes--tracker > + (:noinline t) > + (:constructor nil) > + (:constructor track-changes--tracker ( signal state > + &optional > + nobefore immediate))) > + signal state nobefore immediate) > + > +(cl-defstruct (track-changes--state > + (:noinline t) > + (:constructor nil) > + (:constructor track-changes--state ())) > + "Object holding a description of a buffer state. > +BEG..END is the area that was changed and BEFORE is its previous content. > +If the current buffer currently holds the content of the next state, you can get > +the contents of the previous state with: > + > + (concat (buffer-substring (point-min) beg) > + before > + (buffer-substring end (point-max))) > + > +NEXT is the next state object (i.e. a more recent state). > +If NEXT is nil it means it's most recent state and it may be incomplete > +\(BEG/END/BEFORE may be nil), in which case those fields will take their > +values from `track-changes--before-(beg|end|before)' when the next > +state is create." This doc string should IMO include the form of the object, because without that BEG, END, BEFORE, and NEXT are "out of the blue", and it's entirely unclear what they allude to. Also, this doc string (and a few others) have very long lines, so please refill them. > +(defvar-local track-changes--trackers () > + "List of trackers currently registered in the current buffer.") ^^^^^^^^^^^^^^^^^^^^^ I think "in the buffer" is more accurate, since this is not limited to the current buffer. > +(defvar-local track-changes--disjoint-trackers () > + "List of trackers that want to react to disjoint changes. > +These trackers' are signaled every time track-changes notices ^ That apostrophe is redundant. > +(defvar-local track-changes--before-clean 'unset > + "If non-nil, the `track-changes--before-*' vars are old. > +More specifically it means they cover a part of the buffer relevant > +for the previous state. > +It can take two non-nil values: > +- `unset': Means that the vars cover some older state. > + This is what it is set right after creating a fresh new state. ^^^ "set to" > +(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) > + "Register a new tracker and return a new tracker ID. Please mention SIGNAL in the first line of the doc string. > +SIGNAL is a function that will be called with one argument (the tracker ID) > +after the current buffer is modified, so that we can react to the change. ^^ "we"? > +By default SIGNAL is called as soon as convenient after a change, which is ^^^^^^^^^^^^^^^^^^^^^ "as soon as it's convenient", I presume? > +usually right after the end of the current command. This should explicitly reference funcall-later, so that users could understand what "as soon as convenient" means. > +If optional argument DISJOINT is non-nil, SIGNAL is called every time we are > +about to combine changes from \"distant\" parts of the buffer. ^^ "we" again? > +In order to prevent the upcoming change from being combined with the previous > +changes, SIGNAL needs to call `track-changes-fetch' before it returns." This seems to contradict what the doc string says previously: that SIGNAL should NOT call track-changes-fetch. > +(defun track-changes-fetch (id func) > + "Fetch the pending changes. The first line of a doc string should mention the arguments. > +ID is the tracker ID returned by a previous `track-changes-register'. > +FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) > +where BEGIN..END delimit the region that was changed since the last > +time `track-changes-fetch' was called and BEFORE is a string containing > +the previous content of that region (or just its length as an integer > +If the tracker ID was registered with the `nobefore' option). ^^ "if", lower-case. > +If some error caused us to miss some changes, then BEFORE will be the ^^ "we" again? > +If no changes occurred since the last time, FUNC is not called and > +we return nil, otherwise we return the value returned by FUNC, ^^ And again. > + ;; The states are disconnected from the latest state because > + ;; we got out of sync! > + (cl-assert (eq (track-changes--state-before (car states)) 'error)) This seem to mean Emacs will signal an error in this case, not pass 'error' in BEFORE? > +(defun track-changes--clean-state () > + (cond > + ((null track-changes--state) > + (cl-assert track-changes--before-clean) > + (cl-assert (null track-changes--buffer-size)) > + ;; No state has been created yet. Do it now. > + (setq track-changes--buffer-size (buffer-size)) > + (when track-changes--before-no > + (setq track-changes--before-string (buffer-size))) > + (setq track-changes--state (track-changes--state))) > + (track-changes--before-clean nil) > + (t > + (cl-assert (<= (track-changes--state-beg track-changes--state) > + (track-changes--state-end track-changes--state))) > + (let ((actual-beg (track-changes--state-beg track-changes--state)) > + (actual-end (track-changes--state-end track-changes--state))) > + (if track-changes--before-no > + (progn > + (cl-assert (integerp track-changes--before-string)) > + (setf (track-changes--state-before track-changes--state) > + (- track-changes--before-string > + (- (buffer-size) (- actual-end actual-beg)))) > + (setq track-changes--before-string (buffer-size))) > + (cl-assert (<= track-changes--before-beg > + actual-beg actual-end > + track-changes--before-end)) > + (cl-assert (null (track-changes--state-before track-changes--state))) Are all those assertions a good idea in this function? I can envision using it as a cleanup, in which case assertions will not be appreciated. > +(defvar track-changes--disjoint-threshold 100 > + "Distance below which changes are not considered disjoint.") This should tell in what units the distance is measured. > +;;;; Extra candidates for the API. > + > +;; This could be a good alternative to using a temp-buffer like I used in ^^^^^^ "I"? > +;; Eglot, since presumably we've just been changing this very area of the > +;; buffer, so the gap should be ready nearby, > +;; It may seem silly to go back to the previous state, since we could have > +;; used `before-change-functions' to run FUNC right then when we were in > +;; that state. The advantage is that with track-changes we get to decide > +;; retroactively which state is the one for which we want to call FUNC and > +;; which BEG..END to use: when that state was current we may have known > +;; then that it would be "the one" but we didn't know what BEG and END > +;; should be because those depend on the changes that came afterwards. Suggest to reword (or remove) this comment, as it sounds like development-time notes. > +(defun diff--track-changes-function (beg end _before) > + (with-demoted-errors "%S" Why did you need with-demoted-errors here? Last, but not least: this needs suitable changes in NEWS and ELisp manual. Thanks.
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 5 Apr 2024 22:15:52 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Apr 05 18:15:52 2024 Received: from localhost ([127.0.0.1]:37693 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rsrqf-0004oa-VS for submit <at> debbugs.gnu.org; Fri, 05 Apr 2024 18:15:52 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:50849) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rsrqa-0004nq-83 for 70077 <at> debbugs.gnu.org; Fri, 05 Apr 2024 18:15:47 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id E73C51000FC; Fri, 5 Apr 2024 18:15:31 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712355328; bh=dGUcmgOriDzAfuJNhgDWYDl5M/7cskGka7aJLPW4U/4=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=EB/F2k0MA+8Pt/f9T90Brx3sXzUWP3qLSrYT2JSiaRc43IYSpz1W8vgrgGnsrnzkW /f6uR7dDeEOOo8428CI80S4ITJ/HWZfnc/rPsMhzuh0V+rzflimPFnBnM5zY9b5u7l 63I67OfvLBYqmG/QVM+9biB9iZacJxnM0+nlxQ3hFDjll26iKyKlJ1V1WoTOtwms7M VknyZEf4M6YS+XcJrDB5mTfIxSa18wDFZB3pH/0nwVZLq7Pt3Eoh+MOO1l8oImhEQe hBB1HWBwlaEjaD6xUIrcgWJ4/D5WGeSn5aZaRQv2+7ClSABGlVwb7Ftba3xfcjr6U+ NoPUajk0d/2YQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 3D82D100046; Fri, 5 Apr 2024 18:15:28 -0400 (EDT) Received: from lechazo (lechon.iro.umontreal.ca [132.204.27.242]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 19C61120667; Fri, 5 Apr 2024 18:15:28 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: 70077 <at> debbugs.gnu.org Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvle615806.fsf@HIDDEN> (Stefan Monnier's message of "Fri, 29 Mar 2024 12:15:53 -0400") Message-ID: <jwv7chba2wu.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> Date: Fri, 05 Apr 2024 18:12:55 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.105 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [gnu.org] X-SPAM-LEVEL: X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 70077 Cc: Alan Mackenzie <acm@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -3.3 (---) --=-=-= Content-Type: text/plain My PoC has matured into something quite usable. The maturing part included making it robust against mismatched or missing `*-change-functions` calls, which it tries to detect (the problem is then passed on to the client in the form of a change with an unknown "before" content). I also wrote a test which makes random changes and verifies that the clients receive correct descriptions. I currently use it only for Eglot and diff-mode, but I'm quite happy with it. For Eglot, it nicely packs up consecutive changes (like consecutive `self-insert-command`s) into a single change yet keeps changes to different parts of the buffer nicely separate. The API is still about the same as before except that `track-changes-register` now takes 3 options: - `immediate` to control when the presence of changes is signaled (default to use `funcall-later` but `immediate` makes it use `funcall` so there is no delay). - `disjoint` to prevent changes to different parts of the buffer from being combined into too large a change change. - `nobefore` which indicates that the client doesn't actually need the `before` contents, so will only get the length thereof (like `after-change-functions` does). I'm proposing we include it into `master`. I have pushed the patch to `scratch/track-changes` (and attached it below). Stefan --=-=-= Content-Type: text/x-diff; charset=iso-8859-1 Content-Disposition: inline; filename=0001-lisp-emacs-lisp-track-changes.el-New-file.patch Content-Transfer-Encoding: quoted-printable From 4fd5a97052472eb1c332ea9b3f9ff90e94ad0cd1 Mon Sep 17 00:00:00 2001 From: Stefan Monnier <monnier@HIDDEN> Date: Fri, 5 Apr 2024 17:37:32 -0400 Subject: [PATCH] lisp/emacs-lisp/track-changes.el: New file This new package provides an API that is easier to use right than our `*-change-functions` hooks. The patch includes changes to `diff-mode.el` and `eglot.el` to make use of this new package. * lisp/emacs-lisp/track-changes.el: New file. * test/lisp/emacs-lisp/track-changes-tests.el: New file. * lisp/progmodes/eglot.el: Require `track-changes`. (eglot--virtual-pos-to-lsp-position): New function. (eglot--track-changes): New var. (eglot--managed-mode): Use `track-changes-register` i.s.o `after/before-change-functions`. (eglot--before-change): Delete function. (eglot--track-changes-signal): Rename from `eglot--after-change` and adjust arguments accordingly. (eglot--track-changes-fetch): New function. (eglot--signal-textDocument/didChange): Call it and simplify now that corner-cases are handled by `track-changes`. * lisp/vc/diff-mode.el: Require `track-changes`. Also require `easy-mmode` before the `eval-when-compile`s. (diff-unhandled-changes): Delete variable. (diff-after-change-function): Delete function. (diff--track-changes-function): Rename from `diff-post-command-hook` and adjust to new calling convention. (diff--track-changes): New variable. (diff--track-changes-signal): New function. (diff-mode, diff-minor-mode): Use it with `track-changes-register`. --- lisp/emacs-lisp/track-changes.el | 605 ++++++++++++++++++++ lisp/progmodes/eglot.el | 105 ++-- lisp/vc/diff-mode.el | 107 ++-- test/lisp/emacs-lisp/track-changes-tests.el | 149 +++++ 4 files changed, 852 insertions(+), 114 deletions(-) create mode 100644 lisp/emacs-lisp/track-changes.el create mode 100644 test/lisp/emacs-lisp/track-changes-tests.el diff --git a/lisp/emacs-lisp/track-changes.el b/lisp/emacs-lisp/track-chang= es.el new file mode 100644 index 00000000000..7644a7de98d --- /dev/null +++ b/lisp/emacs-lisp/track-changes.el @@ -0,0 +1,605 @@ +;;; track-changes.el --- API to react to buffer modifications -*- lexical= -binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; Author: Stefan Monnier <monnier@HIDDEN> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This library is a layer of abstraction above `before-change-functions' +;; and `after-change-functions' which takes care of accumulating changes +;; until a time when its client finds it convenient to react to them. +;; +;; It provides an API that is easier to use correctly than our +;; `*-change-functions` hooks. Problems that it claims to solve: +;; +;; - Before and after calls are not necessarily paired. +;; - The beg/end values don't always match. +;; - There's usually only one call to the hooks per command but +;; there can be thousands of calls from within a single command, +;; so naive users will tend to write code that performs poorly +;; in those rare cases. +;; - The hooks are run at a fairly low-level so there are things they +;; really shouldn't do, such as modify the buffer or wait. +;; - The after call doesn't get enough info to rebuild the before-change s= tate, +;; so some callers need to use both before-c-f and after-c-f (and then +;; deal with the first two points above). +;; +;; The new API is almost like `after-change-functions` except that: +;; - It provides the "before string" (i.e. the previous content of +;; the changed area) rather than only its length. +;; - It can combine several changes into larger ones. +;; - Clients do not have to process changes right away, instead they +;; can let changes accumulate (by combining them into a larger change) +;; until it is convenient for them to process them. +;; - By default, changes are signaled at most once per command. + +;; The API consists in the following functions: +;; +;; (track-changes-register SIGNAL &key NOBEFORE DISJOINT IMMEDIATE) +;; (track-changes-fetch ID FUNC) +;; (track-changes-unregister ID) +;; +;; A typical use case might look like: +;; +;; (defvar my-foo--change-tracker nil) +;; (define-minor-mode my-foo-mode +;; "Fooing like there's no tomorrow." +;; (if (null my-foo-mode) +;; (when my-foo--change-tracker +;; (track-changes-unregister my-foo--change-tracker) +;; (setq my-foo--change-tracker nil)) +;; (unless my-foo--change-tracker +;; (setq my-foo--change-tracker +;; (track-changes-register +;; (lambda (id) +;; (track-changes-fetch +;; id (lambda (beg end before) +;; ..DO THE THING..)))))))) + +;;; Code: + +(require 'cl-lib) + +(unless (fboundp 'funcall-later) + (defun funcall-later (&rest args) + ;; FIXME: Not sure if `run-with-timer' preserves ordering between + ;; different calls with the same target time. + (apply #'run-with-timer 0 nil args))) + +;;;; Internal types and variables. + +(cl-defstruct (track-changes--tracker + (:noinline t) + (:constructor nil) + (:constructor track-changes--tracker ( signal state + &optional + nobefore immediate))) + signal state nobefore immediate) + +(cl-defstruct (track-changes--state + (:noinline t) + (:constructor nil) + (:constructor track-changes--state ())) + "Object holding a description of a buffer state. +BEG..END is the area that was changed and BEFORE is its previous content. +If the current buffer currently holds the content of the next state, you c= an get +the contents of the previous state with: + + (concat (buffer-substring (point-min) beg) + before + (buffer-substring end (point-max))) + +NEXT is the next state object (i.e. a more recent state). +If NEXT is nil it means it's most recent state and it may be incomplete +\(BEG/END/BEFORE may be nil), in which case those fields will take their +values from `track-changes--before-(beg|end|before)' when the next +state is create." + (beg (point-max)) + (end (point-min)) + (before nil) + (next nil)) + +(defvar-local track-changes--trackers () + "List of trackers currently registered in the current buffer.") +(defvar-local track-changes--clean-trackers () + "List of trackers that are clean. +Those are the trackers that get signaled when a change is made.") + +(defvar-local track-changes--disjoint-trackers () + "List of trackers that want to react to disjoint changes. +These trackers' are signaled every time track-changes notices +that some upcoming changes touch another \"distant\" part of the buffer.") + +(defvar-local track-changes--state nil) + +;; `track-changes--before-*' keep track of the content of the +;; buffer when `track-changes--state' was cleaned. +(defvar-local track-changes--before-beg (point-min) + "Beginning position of the remembered \"before string\".") +(defvar-local track-changes--before-end (point-min) + "End position of the text replacing the \"before string\".") +(defvar-local track-changes--before-string "" + "String holding some contents of the buffer before the current change. +This string is supposed to cover all the already modified areas plus +the upcoming modifications announced via `before-change-functions'. +If all trackers are `nobefore', then this holds the `buffer-size' before +the current change.") +(defvar-local track-changes--before-no t + "If non-nil, all the trackers are `nobefore'. +Should be equal to (memq #\\=3D'track-changes--before before-change-functi= ons).") + +(defvar-local track-changes--before-clean 'unset + "If non-nil, the `track-changes--before-*' vars are old. +More specifically it means they cover a part of the buffer relevant +for the previous state. +It can take two non-nil values: +- `unset': Means that the vars cover some older state. + This is what it is set right after creating a fresh new state. +- `set': Means the vars reflect the current buffer state. + This is what it is set to after the first `before-change-functions' + but before an `after-change-functions'.") + +(defvar-local track-changes--buffer-size nil + "Current size of the buffer, as far as this library knows. +This is used to try and detect cases where buffer modifications are \"lost= \".") + +;;;; Exposed API. + +(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) + "Register a new tracker and return a new tracker ID. +SIGNAL is a function that will be called with one argument (the tracker ID) +after the current buffer is modified, so that we can react to the change. +Once called, SIGNAL is not called again until `track-changes-fetch' +is called with the corresponding tracker ID. + +If optional argument NOBEFORE is non-nil, it means that this tracker does +not need the BEFORE strings (it will receive their size instead). + +By default SIGNAL is called as soon as convenient after a change, which is +usually right after the end of the current command. +If optional argument IMMEDIATE is non-nil it means SIGNAL should be called +as soon as a change is detected, +BEWARE: In that case SIGNAL is called directly from `after-change-function= s' +and should thus be extra careful: don't modify the buffer, don't call a fu= nction +that may block, do as little work as possible, ... +When IMMEDIATE is non-nil, the SIGNAL should preferably not always call +`track-changes-fetch', since that would defeat the purpose of this library. + +If optional argument DISJOINT is non-nil, SIGNAL is called every time we a= re +about to combine changes from \"distant\" parts of the buffer. +This is needed when combining disjoint changes into one bigger change +is unacceptable, typically for performance reasons. +These calls are distinguished from normal calls by calling SIGNAL with +a second argument which is the distance between the upcoming change and +the previous changes. +BEWARE: In that case SIGNAL is called directly from `before-change-functio= ns' +and should thus be extra careful: don't modify the buffer, don't call a fu= nction +that may block, ... +In order to prevent the upcoming change from being combined with the previ= ous +changes, SIGNAL needs to call `track-changes-fetch' before it returns." + (when (and nobefore disjoint) + ;; FIXME: Without `before-change-functions', we can only discover + ;; a disjoint change after the fact, which is not good enough. + ;; But we could use stripped down before-change-function, + (error "`disjoint' not supported for `nobefore' trackers")) + (track-changes--clean-state) + (unless nobefore + (setq track-changes--before-no nil) + (add-hook 'before-change-functions #'track-changes--before nil t)) + (add-hook 'after-change-functions #'track-changes--after nil t) + (let ((tracker (track-changes--tracker signal track-changes--state + nobefore immediate))) + (push tracker track-changes--trackers) + (push tracker track-changes--clean-trackers) + (when disjoint + (push tracker track-changes--disjoint-trackers)) + tracker)) + +(defun track-changes-unregister (id) + "Remove the tracker denoted by ID. +Trackers can consume resources (especially if `track-changes-fetch' is +not called), so it is good practice to unregister them when you don't +need them any more." + (unless (memq id track-changes--trackers) + (error "Unregistering a non-registered tracker: %S" id)) + (setq track-changes--trackers (delq id track-changes--trackers)) + (setq track-changes--clean-trackers (delq id track-changes--clean-tracke= rs)) + (setq track-changes--disjoint-trackers + (delq id track-changes--disjoint-trackers)) + (when (cl-every #'track-changes--tracker-nobefore track-changes--tracker= s) + (setq track-changes--before-no t) + (remove-hook 'before-change-functions #'track-changes--before t)) + (when (null track-changes--trackers) + (mapc #'kill-local-variable + '(track-changes--before-beg + track-changes--before-end + track-changes--before-string + track-changes--buffer-size + track-changes--before-clean + track-changes--state)) + (remove-hook 'after-change-functions #'track-changes--after t))) + +(defun track-changes-fetch (id func) + "Fetch the pending changes. +ID is the tracker ID returned by a previous `track-changes-register'. +FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) +where BEGIN..END delimit the region that was changed since the last +time `track-changes-fetch' was called and BEFORE is a string containing +the previous content of that region (or just its length as an integer +If the tracker ID was registered with the `nobefore' option). +If some error caused us to miss some changes, then BEFORE will be the +symbol `error' to indicate that the buffer got out of sync. +This reflects a bug somewhere, so please report it when it happens. + +If no changes occurred since the last time, FUNC is not called and +we return nil, otherwise we return the value returned by FUNC, +and re-enable the TRACKER corresponding to ID." + (cl-assert (memq id track-changes--trackers)) + (unless (equal track-changes--buffer-size (buffer-size)) + (track-changes--recover-from-error)) + (let ((beg nil) + (end nil) + (before t) + (lenbefore 0) + (states ())) + ;; Transfer the data from `track-changes--before-string' + ;; to the tracker's state object, if needed. + (track-changes--clean-state) + ;; We want to combine the states from most recent to oldest, + ;; so reverse them. + (let ((state (track-changes--tracker-state id))) + (while state + (push state states) + (setq state (track-changes--state-next state)))) + + (cond + ((eq (car states) track-changes--state) + (cl-assert (null (track-changes--state-before (car states)))) + (setq states (cdr states))) + (t + ;; The states are disconnected from the latest state because + ;; we got out of sync! + (cl-assert (eq (track-changes--state-before (car states)) 'error)) + (setq beg (point-min)) + (setq end (point-max)) + (setq before 'error) + (setq states nil))) + + (dolist (state states) + (let ((prevbeg (track-changes--state-beg state)) + (prevend (track-changes--state-end state)) + (prevbefore (track-changes--state-before state))) + (if (eq before t) + (progn + ;; This is the most recent change. Just initialize the vars. + (setq beg prevbeg) + (setq end prevend) + (setq lenbefore + (if (stringp prevbefore) (length prevbefore) prevbefor= e)) + (setq before + (unless (track-changes--tracker-nobefore id) prevbefor= e))) + (let ((endb (+ beg lenbefore))) + (when (< prevbeg beg) + (if (not before) + (setq lenbefore (+ (- beg prevbeg) lenbefore)) + (setq before + (concat (buffer-substring-no-properties + prevbeg beg) + before)) + (setq lenbefore (length before))) + (setq beg prevbeg) + (cl-assert (=3D endb (+ beg lenbefore)))) + (when (< endb prevend) + (let ((new-end (+ end (- prevend endb)))) + (if (not before) + (setq lenbefore (+ lenbefore (- new-end end))) + (setq before + (concat before + (buffer-substring-no-properties + end new-end))) + (setq lenbefore (length before))) + (setq end new-end) + (cl-assert (=3D prevend (+ beg lenbefore))) + (setq endb (+ beg lenbefore)))) + (cl-assert (<=3D beg prevbeg prevend endb)) + ;; The `prevbefore' is covered by the new one. + (if (not before) + (setq lenbefore + (+ (- prevbeg beg) + (if (stringp prevbefore) + (length prevbefore) prevbefore) + (- endb prevend))) + (setq before + (concat (substring before 0 (- prevbeg beg)) + prevbefore + (substring before (- (length before) + (- endb prevend))))) + (setq lenbefore (length before))))))) + (if (null beg) + (progn + (cl-assert (null states)) + (cl-assert (memq id track-changes--clean-trackers)) + (cl-assert (eq (track-changes--tracker-state id) + track-changes--state)) + ;; Nothing to do. + nil) + (cl-assert (<=3D (point-min) beg end (point-max))) + ;; Update the tracker's state *before* running `func' so we don't ri= sk + ;; mistakenly replaying the changes in case `func' exits non-locally. + (setf (track-changes--tracker-state id) track-changes--state) + (unwind-protect (funcall func beg end (or before lenbefore)) + ;; Re-enable the tracker's signal only after running `func', so + ;; as to avoid recursive invocations. + (cl-pushnew id track-changes--clean-trackers))))) + +;;;; Auxiliary functions. + +(defun track-changes--clean-state () + (cond + ((null track-changes--state) + (cl-assert track-changes--before-clean) + (cl-assert (null track-changes--buffer-size)) + ;; No state has been created yet. Do it now. + (setq track-changes--buffer-size (buffer-size)) + (when track-changes--before-no + (setq track-changes--before-string (buffer-size))) + (setq track-changes--state (track-changes--state))) + (track-changes--before-clean nil) + (t + (cl-assert (<=3D (track-changes--state-beg track-changes--state) + (track-changes--state-end track-changes--state))) + (let ((actual-beg (track-changes--state-beg track-changes--state)) + (actual-end (track-changes--state-end track-changes--state))) + (if track-changes--before-no + (progn + (cl-assert (integerp track-changes--before-string)) + (setf (track-changes--state-before track-changes--state) + (- track-changes--before-string + (- (buffer-size) (- actual-end actual-beg)))) + (setq track-changes--before-string (buffer-size))) + (cl-assert (<=3D track-changes--before-beg + actual-beg actual-end + track-changes--before-end)) + (cl-assert (null (track-changes--state-before track-changes--state= ))) + ;; The `track-changes--before-*' vars can cover more text than the + ;; actually modified area, so trim it down now to the relevant par= t. + (unless (=3D (- track-changes--before-end track-changes--before-be= g) + (- actual-end actual-beg)) + (setq track-changes--before-string + (substring track-changes--before-string + (- actual-beg track-changes--before-beg) + (- (length track-changes--before-string) + (- track-changes--before-end actual-end)))) + (setq track-changes--before-beg actual-beg) + (setq track-changes--before-end actual-end)) + (setf (track-changes--state-before track-changes--state) + track-changes--before-string))) + ;; Note: We preserve `track-changes--before-*' because they may still + ;; be needed, in case `after-change-functions' are run before the next + ;; `before-change-functions'. + ;; Instead, we set `track-changes--before-clean' to `unset' to mean th= at + ;; `track-changes--before-*' can be reset at the next + ;; `before-change-functions'. + (setq track-changes--before-clean 'unset) + (let ((new (track-changes--state))) + (setf (track-changes--state-next track-changes--state) new) + (setq track-changes--state new))))) + +(defvar track-changes--disjoint-threshold 100 + "Distance below which changes are not considered disjoint.") + +(defvar track-changes--error-log () + "List of errors encountered. +Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).") + +(defun track-changes--recover-from-error () + ;; We somehow got out of sync. This is usually the result of a bug + ;; elsewhere that causes the before-c-f and after-c-f to be improperly + ;; paired, or to be skipped altogether. + ;; Not much we can do, other than force a full re-synchronization. + (warn "Missing/incorrect calls to `before/after-change-functions'!! +Details logged to `track-changes--error-log'") + (push (list (buffer-name) + (backtrace-frames 'track-changes--recover-from-error) + (recent-keys 'include-cmds)) + track-changes--error-log) + (setq track-changes--before-clean 'unset) + (setq track-changes--buffer-size (buffer-size)) + ;; Create a new state disconnected from the previous ones! + ;; Mark the previous one as junk, just to be clear. + (setf (track-changes--state-before track-changes--state) 'error) + (setq track-changes--state (track-changes--state))) + +(defun track-changes--before (beg end) + (cl-assert track-changes--state) + (cl-assert (<=3D beg end)) + (let* ((size (- end beg)) + (reset (lambda () + (cl-assert track-changes--before-clean) + (setq track-changes--before-clean 'set) + (setf track-changes--before-string + (buffer-substring-no-properties beg end)) + (setf track-changes--before-beg beg) + (setf track-changes--before-end end))) + + (signal-if-disjoint + (lambda (pos1 pos2) + (let ((distance (- pos2 pos1))) + (when (> distance + (max track-changes--disjoint-threshold + ;; If the distance is smaller than the size of= the + ;; current change, then we may as well conside= r it + ;; as "near". + (length track-changes--before-string) + size + (- track-changes--before-end + track-changes--before-beg))) + (dolist (tracker track-changes--disjoint-trackers) + (funcall (track-changes--tracker-signal tracker) + tracker distance)) + ;; Return non-nil if the state was cleaned along the way. + track-changes--before-clean))))) + + (if track-changes--before-clean + (progn + ;; Detect disjointness with previous changes here as well, + ;; so that if a client calls `track-changes-fetch' all the time, + ;; it doesn't prevent others from getting a disjointness signal. + (when (and track-changes--before-beg + (let ((found nil)) + (dolist (tracker track-changes--disjoint-trackers) + (unless (memq tracker track-changes--clean-tracke= rs) + (setq found t))) + found)) + ;; There's at least one `tracker' that wants to know about dis= joint + ;; changes *and* it has unseen pending changes. + ;; FIXME: This can occasionally signal a tracker that's clean. + (if (< beg track-changes--before-beg) + (funcall signal-if-disjoint end track-changes--before-beg) + (funcall signal-if-disjoint track-changes--before-end beg))) + (funcall reset)) + (cl-assert (save-restriction + (widen) + (<=3D (point-min) + track-changes--before-beg + track-changes--before-end + (point-max)))) + (when (< beg track-changes--before-beg) + (if (and track-changes--disjoint-trackers + (funcall signal-if-disjoint end track-changes--before-beg= )) + (funcall reset) + (let* ((old-bbeg track-changes--before-beg) + ;; To avoid O(N=B2) behavior when faced with many small c= hanges, + ;; we copy more than needed. + (new-bbeg (min (max (point-min) + (- old-bbeg + (length track-changes--before-stri= ng))) + beg))) + (setf track-changes--before-beg new-bbeg) + (cl-callf (lambda (old new) (concat new old)) + track-changes--before-string + (buffer-substring-no-properties new-bbeg old-bbeg))))) + + (when (< track-changes--before-end end) + (if (and track-changes--disjoint-trackers + (funcall signal-if-disjoint track-changes--before-end beg= )) + (funcall reset) + (let* ((old-bend track-changes--before-end) + ;; To avoid O(N=B2) behavior when faced with many small c= hanges, + ;; we copy more than needed. + (new-bend (max (min (point-max) + (+ old-bend + (length track-changes--before-stri= ng))) + end))) + (setf track-changes--before-end new-bend) + (cl-callf concat track-changes--before-string + (buffer-substring-no-properties old-bend new-bend)))))))) + +(defun track-changes--after (beg end len) + (cl-assert track-changes--state) + (and (eq track-changes--before-clean 'unset) + (not track-changes--before-no) + ;; This can be a sign that a `before-change-functions' went missing, + ;; or that we called `track-changes--clean-state' between + ;; a `before-change-functions' and `after-change-functions'. + (track-changes--before beg end)) + (setq track-changes--before-clean nil) + (let ((offset (- (- end beg) len))) + (cl-incf track-changes--before-end offset) + (cl-incf track-changes--buffer-size offset) + (if (not (or track-changes--before-no + (save-restriction + (widen) + (<=3D (point-min) + track-changes--before-beg + beg end + track-changes--before-end + (point-max))))) + ;; BEG..END is not covered by previous `before-change-functions'!! + (track-changes--recover-from-error) + ;; Note the new changes. + (when (< beg (track-changes--state-beg track-changes--state)) + (setf (track-changes--state-beg track-changes--state) beg)) + (cl-callf (lambda (old-end) (max end (+ old-end offset))) + (track-changes--state-end track-changes--state)) + (cl-assert (or track-changes--before-no + (<=3D track-changes--before-beg + (track-changes--state-beg track-changes--state) + beg end + (track-changes--state-end track-changes--state) + track-changes--before-end))))) + (while track-changes--clean-trackers + (let ((tracker (pop track-changes--clean-trackers))) + (if (track-changes--tracker-immediate tracker) + (funcall (track-changes--tracker-signal tracker) tracker) + (funcall-later #'track-changes--call-signal + (current-buffer) tracker))))) + +(defun track-changes--call-signal (buf tracker) + (when (buffer-live-p buf) + (with-current-buffer buf + ;; Silence ourselves if `track-changes-fetch' was called in the mean= time. + (unless (memq tracker track-changes--clean-trackers) + (funcall (track-changes--tracker-signal tracker) tracker))))) + +;;;; Extra candidates for the API. + +;; This could be a good alternative to using a temp-buffer like I used in +;; Eglot, since presumably we've just been changing this very area of the +;; buffer, so the gap should be ready nearby, +;; It may seem silly to go back to the previous state, since we could have +;; used `before-change-functions' to run FUNC right then when we were in +;; that state. The advantage is that with track-changes we get to decide +;; retroactively which state is the one for which we want to call FUNC and +;; which BEG..END to use: when that state was current we may have known +;; then that it would be "the one" but we didn't know what BEG and END +;; should be because those depend on the changes that came afterwards. +(defun track-changes--in-revert (beg end before func) + "Call FUNC with the buffer contents temporarily reverted to BEFORE. +FUNC is called with no arguments and with point right after BEFORE. +FUNC is not allowed to modify the buffer and it should refrain from using +operations that use a cache populated from the buffer's content, +such as `syntax-ppss'." + (catch 'track-changes--exit + (with-silent-modifications ;; This has to be outside `atomic-change-gr= oup'. + (atomic-change-group + (goto-char end) + (insert-before-markers before) + (delete-region beg end) + (throw 'track-changes--exit + (let ((inhibit-read-only nil) + (buffer-read-only t)) + (funcall func))))))) + +(defun track-changes--reset (id) + "Mark all past changes as handled for tracker ID. +Does not re-enable ID's signal." + (track-changes--clean-state) + (setf (track-changes--tracker-state id) track-changes--state)) + +(defun track-changes--pending-p (id) + "Return non-nil if there are pending changes for tracker ID." + (not (memq id track-changes--clean-trackers))) + +(defmacro with--track-changes (id vars &rest body) + (declare (indent 2) (debug (form sexp body))) + `(track-changes-fetch ,id (lambda ,vars ,@body))) + +(provide 'track-changes) +;;; track-changes.el end here. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 7f4284bf09d..00c09d7f06b 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -110,6 +110,7 @@ (require 'text-property-search nil t) (require 'diff-mode) (require 'diff) +(require 'track-changes) =20 ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite @@ -1732,6 +1733,9 @@ eglot-utf-16-linepos "Calculate number of UTF-16 code units from position given by LBP. LBP defaults to `eglot--bol'." (/ (- (length (encode-coding-region (or lbp (eglot--bol)) + ;; FIXME: How could `point' ever be + ;; larger than `point-max' (sounds l= ike + ;; a bug in Emacs). ;; Fix github#860 (min (point) (point-max)) 'utf-16 t)) 2) @@ -1749,6 +1753,24 @@ eglot--pos-to-lsp-position :character (progn (when pos (goto-char pos)) (funcall eglot-current-linepos-function))))) =20 +(defun eglot--virtual-pos-to-lsp-position (pos string) + "Return the LSP position at the end of STRING if it were inserted at POS= ." + (eglot--widening + (goto-char pos) + (forward-line 0) + ;; LSP line is zero-origin; Emacs is one-origin. + (let ((posline (1- (line-number-at-pos nil t))) + (linebeg (buffer-substring (point) pos)) + (colfun eglot-current-linepos-function)) + ;; Use a temp buffer because: + ;; - I don't know of a fast way to count newlines in a string. + ;; - We currently don't have `eglot-current-linepos-function' for str= ings. + (with-temp-buffer + (insert linebeg string) + (goto-char (point-max)) + (list :line (+ posline (1- (line-number-at-pos nil t))) + :character (funcall colfun)))))) + (defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos "Function to move to a position within a line reported by the LSP server. =20 @@ -1946,6 +1968,8 @@ eglot-managed-mode-hook "A hook run by Eglot after it started/stopped managing a buffer. Use `eglot-managed-p' to determine if current buffer is managed.") =20 +(defvar-local eglot--track-changes nil) + (define-minor-mode eglot--managed-mode "Mode for source buffers managed by some Eglot project." :init-value nil :lighter nil :keymap eglot-mode-map @@ -1959,8 +1983,9 @@ eglot--managed-mode ("utf-8" (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-li= nepos) (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-= utf-8-linepos))) - (add-hook 'after-change-functions #'eglot--after-change nil t) - (add-hook 'before-change-functions #'eglot--before-change nil t) + (unless eglot--track-changes + (setq eglot--track-changes + (track-changes-register #'eglot--track-changes-signal :disjoin= t t))) (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) ;; Prepend "didClose" to the hook after the "nonoff", so it will run f= irst (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil = t) @@ -1998,8 +2023,9 @@ eglot--managed-mode buffer (eglot--managed-buffers (eglot-current-server))))) (t - (remove-hook 'after-change-functions #'eglot--after-change t) - (remove-hook 'before-change-functions #'eglot--before-change t) + (when eglot--track-changes + (track-changes-unregister eglot--track-changes) + (setq eglot--track-changes nil)) (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) (remove-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose t) (remove-hook 'before-revert-hook #'eglot--signal-textDocument/didClose= t) @@ -2568,54 +2594,29 @@ jsonrpc-connection-ready-p =20 (defvar-local eglot--change-idle-timer nil "Idle timer for didChange signa= ls.") =20 -(defun eglot--before-change (beg end) - "Hook onto `before-change-functions' with BEG and END." - (when (listp eglot--recent-changes) - ;; Records BEG and END, crucially convert them into LSP - ;; (line/char) positions before that information is lost (because - ;; the after-change thingy doesn't know if newlines were - ;; deleted/added). Also record markers of BEG and END - ;; (github#259) - (push `(,(eglot--pos-to-lsp-position beg) - ,(eglot--pos-to-lsp-position end) - (,beg . ,(copy-marker beg nil)) - (,end . ,(copy-marker end t))) - eglot--recent-changes))) - (defvar eglot--document-changed-hook '(eglot--signal-textDocument/didChang= e) "Internal hook for doing things when the document changes.") =20 -(defun eglot--after-change (beg end pre-change-length) - "Hook onto `after-change-functions'. -Records BEG, END and PRE-CHANGE-LENGTH locally." +(defun eglot--track-changes-fetch (id) + (if (eq eglot--recent-changes 'pending) (setq eglot--recent-changes nil)) + (track-changes-fetch + id (lambda (beg end before) + (if (stringp before) + (push `(,(eglot--pos-to-lsp-position beg) + ,(eglot--virtual-pos-to-lsp-position beg before) + ,(length before) + ,(buffer-substring-no-properties beg end)) + eglot--recent-changes) + (setf eglot--recent-changes :emacs-messup))))) + +(defun eglot--track-changes-signal (id &optional distance) (cl-incf eglot--versioned-identifier) - (pcase (car-safe eglot--recent-changes) - (`(,lsp-beg ,lsp-end - (,b-beg . ,b-beg-marker) - (,b-end . ,b-end-marker)) - ;; github#259 and github#367: with `capitalize-word' & friends, - ;; `before-change-functions' records the whole word's `b-beg' and - ;; `b-end'. Similarly, when `fill-paragraph' coalesces two - ;; lines, `b-beg' and `b-end' mark end of first line and end of - ;; second line, resp. In both situations, `beg' and `end' - ;; received here seemingly contradict that: they will differ by 1 - ;; and encompass the capitalized character or, in the coalescing - ;; case, the replacement of the newline with a space. We keep - ;; both markers and positions to detect and correct this. In - ;; this specific case, we ignore `beg', `len' and - ;; `pre-change-len' and send richer information about the region - ;; from the markers. I've also experimented with doing this - ;; unconditionally but it seems to break when newlines are added. - (if (and (=3D b-end b-end-marker) (=3D b-beg b-beg-marker) - (or (/=3D beg b-beg) (/=3D end b-end))) - (setcar eglot--recent-changes - `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker) - ,(buffer-substring-no-properties b-beg-marker - b-end-marker)= )) - (setcar eglot--recent-changes - `(,lsp-beg ,lsp-end ,pre-change-length - ,(buffer-substring-no-properties beg end))))) - (_ (setf eglot--recent-changes :emacs-messup))) + (cond + (distance (eglot--track-changes-fetch id)) + (eglot--recent-changes nil) + ;; Note that there are pending changes, for the benefit of those + ;; who check it as a boolean. + (t (setq eglot--recent-changes 'pending))) (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) (let ((buf (current-buffer))) (setq eglot--change-idle-timer @@ -2729,6 +2730,7 @@ eglot-handle-request (defun eglot--signal-textDocument/didChange () "Send textDocument/didChange to server." (when eglot--recent-changes + (eglot--track-changes-fetch eglot--track-changes) (let* ((server (eglot--current-server-or-lose)) (sync-capability (eglot-server-capable :textDocumentSync)) (sync-kind (if (numberp sync-capability) sync-capability @@ -2745,13 +2747,8 @@ eglot--signal-textDocument/didChange (buffer-substring-no-properties (point-min) (point-max))= ))) (cl-loop for (beg end len text) in (reverse eglot--recent-change= s) - ;; github#259: `capitalize-word' and commands based - ;; on `casify_region' will cause multiple duplicate - ;; empty entries in `eglot--before-change' calls - ;; without an `eglot--after-change' reciprocal. - ;; Weed them out here. - when (numberp len) vconcat `[,(list :range `(:start ,beg :end ,end) + ;; `rangeLength' is obsolete. :rangeLength len :text text)])))) (setq eglot--recent-changes nil) (jsonrpc--call-deferred server)))) diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 66043059d14..e7ac517b72f 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -53,9 +53,10 @@ ;; - Handle `diff -b' output in context->unified. =20 ;;; Code: +(require 'easy-mmode) +(require 'track-changes) (eval-when-compile (require 'cl-lib)) (eval-when-compile (require 'subr-x)) -(require 'easy-mmode) =20 (autoload 'vc-find-revision "vc") (autoload 'vc-find-revision-no-save "vc") @@ -1431,56 +1432,40 @@ diff-write-contents-hooks (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max))) nil) =20 -;; It turns out that making changes in the buffer from within an -;; *-change-function is asking for trouble, whereas making them -;; from a post-command-hook doesn't pose much problems -(defvar diff-unhandled-changes nil) -(defun diff-after-change-function (beg end _len) - "Remember to fixup the hunk header. -See `after-change-functions' for the meaning of BEG, END and LEN." - ;; Ignoring changes when inhibit-read-only is set is strictly speaking - ;; incorrect, but it turns out that inhibit-read-only is normally not set - ;; inside editing commands, while it tends to be set when the buffer gets - ;; updated by an async process or by a conversion function, both of which - ;; would rather not be uselessly slowed down by this hook. - (when (and (not undo-in-progress) (not inhibit-read-only)) - (if diff-unhandled-changes - (setq diff-unhandled-changes - (cons (min beg (car diff-unhandled-changes)) - (max end (cdr diff-unhandled-changes)))) - (setq diff-unhandled-changes (cons beg end))))) - -(defun diff-post-command-hook () - "Fixup hunk headers if necessary." - (when (consp diff-unhandled-changes) - (ignore-errors - (save-excursion - (goto-char (car diff-unhandled-changes)) - ;; Maybe we've cut the end of the hunk before point. - (if (and (bolp) (not (bobp))) (backward-char 1)) - ;; We used to fixup modifs on all the changes, but it turns out that - ;; it's safer not to do it on big changes, e.g. when yanking a big - ;; diff, or when the user edits the header, since we might then - ;; screw up perfectly correct values. --Stef - (diff-beginning-of-hunk t) - (let* ((style (if (looking-at "\\*\\*\\*") 'context)) - (start (line-beginning-position (if (eq style 'context) 3 2= ))) - (mid (if (eq style 'context) - (save-excursion - (re-search-forward diff-context-mid-hunk-header-= re - nil t))))) - (when (and ;; Don't try to fixup changes in the hunk header. - (>=3D (car diff-unhandled-changes) start) - ;; Don't try to fixup changes in the mid-hunk header eith= er. - (or (not mid) - (< (cdr diff-unhandled-changes) (match-beginning 0)) - (> (car diff-unhandled-changes) (match-end 0))) - (save-excursion - (diff-end-of-hunk nil 'donttrustheader) - ;; Don't try to fixup changes past the end of the hunk. - (>=3D (point) (cdr diff-unhandled-changes)))) - (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))) - (setq diff-unhandled-changes nil)))) +(defvar-local diff--track-changes nil) + +(defun diff--track-changes-signal (tracker) + (cl-assert (eq tracker diff--track-changes)) + (track-changes-fetch tracker #'diff--track-changes-function)) + +(defun diff--track-changes-function (beg end _before) + (with-demoted-errors "%S" + (save-excursion + (goto-char beg) + ;; Maybe we've cut the end of the hunk before point. + (if (and (bolp) (not (bobp))) (backward-char 1)) + ;; We used to fixup modifs on all the changes, but it turns out that + ;; it's safer not to do it on big changes, e.g. when yanking a big + ;; diff, or when the user edits the header, since we might then + ;; screw up perfectly correct values. --Stef + (diff-beginning-of-hunk t) + (let* ((style (if (looking-at "\\*\\*\\*") 'context)) + (start (line-beginning-position (if (eq style 'context) 3 2))) + (mid (if (eq style 'context) + (save-excursion + (re-search-forward diff-context-mid-hunk-header-re + nil t))))) + (when (and ;; Don't try to fixup changes in the hunk header. + (>=3D beg start) + ;; Don't try to fixup changes in the mid-hunk header either. + (or (not mid) + (< end (match-beginning 0)) + (> beg (match-end 0))) + (save-excursion + (diff-end-of-hunk nil 'donttrustheader) + ;; Don't try to fixup changes past the end of the hunk. + (>=3D (point) end))) + (diff-fixup-modifs (point) end)))))) =20 (defun diff-next-error (arg reset) ;; Select a window that displays the current buffer so that point @@ -1560,9 +1545,8 @@ diff-mode ;; setup change hooks (if (not diff-update-on-the-fly) (add-hook 'write-contents-functions #'diff-write-contents-hooks nil = t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t)) + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal :nobefore t= ))) =20 ;; add-log support (setq-local add-log-current-defun-function #'diff-current-defun) @@ -1581,12 +1565,15 @@ diff-minor-mode \\{diff-minor-mode-map}" :group 'diff-mode :lighter " Diff" ;; FIXME: setup font-lock - ;; setup change hooks - (if (not diff-update-on-the-fly) - (add-hook 'write-contents-functions #'diff-write-contents-hooks nil = t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t))) + (when diff--track-changes (track-changes-unregister diff--track-changes)) + (remove-hook 'write-contents-functions #'diff-write-contents-hooks t) + (when diff-minor-mode + (if (not diff-update-on-the-fly) + (add-hook 'write-contents-functions #'diff-write-contents-hooks ni= l t) + (unless diff--track-changes + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal + :nobefore t)))))) =20 ;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;= ;;; =20 diff --git a/test/lisp/emacs-lisp/track-changes-tests.el b/test/lisp/emacs-= lisp/track-changes-tests.el new file mode 100644 index 00000000000..cdccbe80299 --- /dev/null +++ b/test/lisp/emacs-lisp/track-changes-tests.el @@ -0,0 +1,149 @@ +;;; track-changes-tests.el --- tests for emacs-lisp/track-changes.el -*- = lexical-binding:t -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'track-changes) +(require 'cl-lib) +(require 'ert) + +(defun track-changes-tests--random-word () + (let ((chars ())) + (dotimes (_ (1+ (random 12))) + (push (+ ?A (random (1+ (- ?z ?A)))) chars)) + (apply #'string chars))) + +(defvar track-changes-tests--random-verbose nil) + +(defun track-changes-tests--message (&rest args) + (when track-changes-tests--random-verbose (apply #'message args))) + +(ert-deftest track-changes-tests--random () + ;; Keep 2 buffers in sync with a third one as we make random + ;; changes to that 3rd one. + ;; We have 3 trackers: a "normal" one which we sync + ;; at random intervals, one which syncs via the "disjoint" signal, + ;; plus a third one which verifies that "nobefore" gets + ;; information consistent with the "normal" tracker. + (with-temp-buffer + (dotimes (_ 100) + (insert (track-changes-tests--random-word) "\n")) + (let* ((buf1 (generate-new-buffer " *tc1*")) + (buf2 (generate-new-buffer " *tc2*")) + (char-counts (make-vector 2 0)) + (sync-counts (make-vector 2 0)) + (print-escape-newlines t) + (file (make-temp-file "tc")) + (id1 (track-changes-register #'ignore)) + (id3 (track-changes-register #'ignore :nobefore t)) + (sync + (lambda (id buf n) + (track-changes-tests--message "!! SYNC %d !!" n) + (track-changes-fetch + id (lambda (beg end before) + (when (eq n 1) + (track-changes-fetch + id3 (lambda (beg3 end3 before3) + (should (eq beg3 beg)) + (should (eq end3 end)) + (should (eq before3 + (if (symbolp before) + before (length before))))))) + (cl-incf (aref sync-counts (1- n))) + (cl-incf (aref char-counts (1- n)) (- end beg)) + (let ((after (buffer-substring beg end))) + (track-changes-tests--message + "Sync:\n %S\n=3D> %S\nat %d .. %d" + before after beg end) + (with-current-buffer buf + (if (eq before 'error) + (erase-buffer) + (should (equal before + (buffer-substring + beg (+ beg (length before))))) + (delete-region beg (+ beg (length before)))) + (goto-char beg) + (insert after))) + (should (equal (buffer-string) + (with-current-buffer buf + (buffer-string)))))))) + (id2 (track-changes-register + (lambda (id2 &optional distance) + (when distance + (track-changes-tests--message "Disjoint distance: %d" + distance) + (funcall sync id2 buf2 2))) + :disjoint t))) + (write-region (point-min) (point-max) file) + (insert-into-buffer buf1) + (insert-into-buffer buf2) + (should (equal (buffer-hash) (buffer-hash buf1))) + (should (equal (buffer-hash) (buffer-hash buf2))) + (dotimes (_ 1000) + (pcase (random 15) + (0 + (track-changes-tests--message "Manual sync1") + (funcall sync id1 buf1 1)) + (1 + (track-changes-tests--message "Manual sync2") + (funcall sync id2 buf2 2)) + ((pred (< _ 5)) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 100))) (point-max)))) + (track-changes-tests--message "Fill %d .. %d" beg end) + (fill-region-as-paragraph beg end))) + ((pred (< _ 8)) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 12))) (point-max)))) + (track-changes-tests--message "Delete %S at %d .. %d" + (buffer-substring beg end) beg = end) + (delete-region beg end))) + ((and 8 (guard (=3D (random 50) 0))) + (track-changes-tests--message "Silent insertion") + (let ((inhibit-modification-hooks t)) + (insert "a"))) + ((and 8 (guard (=3D (random 10) 0))) + (track-changes-tests--message "Revert") + (insert-file-contents file nil nil nil 'replace)) + ((and 8 (guard (=3D (random 3) 0))) + (let* ((beg (+ (point-min) (random (1+ (buffer-size))))) + (end (min (+ beg (1+ (random 12))) (point-max))) + (after (eq (random 2) 0))) + (track-changes-tests--message "Bogus %S %d .. %d" + (if after 'after 'before) beg e= nd) + (if after + (run-hook-with-args 'after-change-functions + beg end (- end beg)) + (run-hook-with-args 'before-change-functions beg end)))) + (_ + (goto-char (+ (point-min) (random (1+ (buffer-size))))) + (let ((word (track-changes-tests--random-word))) + (track-changes-tests--message "insert %S at %d" word (point)) + (insert word "\n"))))) + (message "SCOREs: default: %d/%d=3D%d disjoint: %d/%d=3D%d" + (aref char-counts 0) (aref sync-counts 0) + (/ (aref char-counts 0) (aref sync-counts 0)) + (aref char-counts 1) (aref sync-counts 1) + (/ (aref char-counts 1) (aref sync-counts 1)))))) + + + +;;; track-changes-tests.el ends here --=20 2.43.0 --=-=-=--
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 4 Apr 2024 17:58:43 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Thu Apr 04 13:58:43 2024 Received: from localhost ([127.0.0.1]:34681 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rsRMJ-0003dA-DU for submit <at> debbugs.gnu.org; Thu, 04 Apr 2024 13:58:43 -0400 Received: from mout01.posteo.de ([185.67.36.65]:35925) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rsRMF-0003cm-RT for 70077 <at> debbugs.gnu.org; Thu, 04 Apr 2024 13:58:42 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id DC37124002A for <70077 <at> debbugs.gnu.org>; Thu, 4 Apr 2024 19:58:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1712253508; bh=LjJ6XTN/1S5DHovyKjjogMsgzDn0Gj9/0CCWnmp7jYk=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: Content-Transfer-Encoding:From; b=ABK15oYEwPbBH29hcjPk/QxCtzKWOPAQY/2iLtnWa/l3l2HpVCGNlz0dsVuNj+zoq G+RT9AAGV4up2OOG2PUAbg8/ZCEV0OBdpy8RNA+DdEUO5T3aiBk9VU+p1mZKJbX6tn PCDn4ugJvyqat1VvrbObYCX81jRohlXHAdShy6wAZhcaVgrS7luc5neyZVkjMAz0nX rYbyvp+FwGGI1B52VXnhzo31Pz5gTFNwa69Mya3a53yOvlSZwjl36EdoKxkEpsjQfJ NGJkhOC5rLIza8FUSP7+aLhEvknTp4JSYZNGR/z5m3RsZEyKM+CPQSMtDr40MyjvSr ixdni0ACMWjeg== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V9Tr25R3Bz9rxP; Thu, 4 Apr 2024 19:58:26 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvmsqamxof.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> <87ttkjspkq.fsf@localhost> <jwvo7aroeqr.fsf-monnier+emacs@HIDDEN> <8734s2pqu0.fsf@localhost> <jwvmsqamxof.fsf-monnier+emacs@HIDDEN> Date: Thu, 04 Apr 2024 17:58:43 +0000 Message-ID: <87frw1t3fw.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Stefan Monnier <monnier@HIDDEN> writes: >> I am not 100% where the O(N^2) is coming from. > > If you add N times 1 char to an (initially empty) string, the total cost > of constructing the resulting N-char string is O(N=C2=B2). I guess that another approach is not concatenating the strings and instead accumulating them into a list (or two lists - before/after). That will get rid of logN multiplier :) --=20 Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 3 Apr 2024 12:46:17 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Wed Apr 03 08:46:16 2024 Received: from localhost ([127.0.0.1]:57321 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rs00O-0006Y4-Gu for submit <at> debbugs.gnu.org; Wed, 03 Apr 2024 08:46:16 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:61231) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rs00L-0006XE-K4 for 70077 <at> debbugs.gnu.org; Wed, 03 Apr 2024 08:46:15 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id A1AAF44156E; Wed, 3 Apr 2024 08:46:02 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712148361; bh=0NNAoRHup2M5HkdEB+wJIPY9juDlBcIMtAkJ8yVEbGM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=LbxrB7hPef1MTmf9S/9lTubFaUudpPAQInwqRwtUU3GKgXQGWREctXnq1Q6hbSImr VIyHVVFVxJnMqC32EurH5ZAF6iC3ErCNT4fVH1ZYg8y/xpHkzs0YEDg77KYDkeBgIE 45zhn0jOmckIXst2r/nF3nAGVh4e44FLxEOqTFlvAk3L3mcu49pggZFVCb8PuxBRWf L9hWh/RC9daVNzrT9Ftc1goOAHvHLKuPrraoA+nlsYRbXouyo3pIwhzf9MzS3RL021 cax0ZBvoBKbI9dSBJ2PmxyliQyeXFgXFWBUvkPLXt/lmS8Mrza6JJqDmcNy06S6qBS ZEMHj23u+12IA== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 5BE88441503; Wed, 3 Apr 2024 08:46:01 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id F35E9120828; Wed, 3 Apr 2024 08:46:00 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <8734s2pqu0.fsf@localhost> (Ihor Radchenko's message of "Wed, 03 Apr 2024 12:34:47 +0000") Message-ID: <jwvmsqamxof.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> <87ttkjspkq.fsf@localhost> <jwvo7aroeqr.fsf-monnier+emacs@HIDDEN> <8734s2pqu0.fsf@localhost> Date: Wed, 03 Apr 2024 08:45:59 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: quoted-printable X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.051 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > I am not 100% where the O(N^2) is coming from. If you add N times 1 char to an (initially empty) string, the total cost of constructing the resulting N-char string is O(N=B2). > In any case, while I do see where the idea of over-expanding the region > comes from, it is not ideal for my use-case in Org mode - it is often > critical to know precise region where the change happened. The reported changes are precise (modulo the fact that they are combined): the over-expanded `track-changes--before-string` is trimmed to the actual changes when it gets moved to a "state" (that's done in `track-changes--clean-state`). Also, note that in 99% of the cases, a command performs only a single change (insertion or deletion), in which case there's no combining nor over-expansion before the signal gets called. Of course, approximation may still happen if you decide not to act immediately when the signal is sent. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 3 Apr 2024 12:34:50 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Wed Apr 03 08:34:50 2024 Received: from localhost ([127.0.0.1]:57309 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrzpK-0005ax-Do for submit <at> debbugs.gnu.org; Wed, 03 Apr 2024 08:34:50 -0400 Received: from mout02.posteo.de ([185.67.36.66]:45619) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rrzpE-0005Zt-Lq for 70077 <at> debbugs.gnu.org; Wed, 03 Apr 2024 08:34:47 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id AFFDA240103 for <70077 <at> debbugs.gnu.org>; Wed, 3 Apr 2024 14:34:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1712147674; bh=Kn4hJ4Epmhcxy1k0wUp6vHoWYHsJbZ13QKr6Jw3RNgE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: Content-Transfer-Encoding:From; b=QBBwst8Rdda11QPafUdYgdL48AyKZg5/Kl1+/qL3HSYNROjarP6uYFwR5GnyaNrPS cNlQc3IjQ5Rwjvs0XYsgQAsb6XAxTLhdXs78P9VrJSSTUiWyYelADpV5Obt4TBJt1o dNcg/GaW5JdXTs7DFuyBedeCpOETuSB6Pv+zDbIvfaYeborVF5jBzlVK5tXcO17T7R ZYNx9v0WOVQMo+HJ6uVtToqG3x8R9IlYzQiY19m7ycrIfrkMC+HthEe+XIzx8uSeWM MGIPPOTEXxJNclefNi9qte9fK3QfmKzhqOGd3JifbBYorrwWWM9b8fl5uyRuDPjnVU h3gDpMeD5epAg== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V8khm0r61z9rxM; Wed, 3 Apr 2024 14:34:31 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvo7aroeqr.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> <87ttkjspkq.fsf@localhost> <jwvo7aroeqr.fsf-monnier+emacs@HIDDEN> Date: Wed, 03 Apr 2024 12:34:47 +0000 Message-ID: <8734s2pqu0.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Stefan Monnier <monnier@HIDDEN> writes: >> My reading of `track-changes--before' is that `concat' is called on >> every change except when a new change is already inside the previously >> recorded one. > > The "previously recorded one" (i.e. the info stored in > `track-changes--before-beg/end/string`) is a conservative > over-approximation of the changed area. We (at least) double its size > every time we have to grow it, specifically to reduce the number of > times we need to grow it (otherwise we quickly fall into the O(N=C2=B2) > trap). I am not 100% where the O(N^2) is coming from. I'd rather see the "doubling" explained better in the comment before the code. In any case, while I do see where the idea of over-expanding the region comes from, it is not ideal for my use-case in Org mode - it is often critical to know precise region where the change happened. Larger region spanning over more lines than needed can easily trigger re-parsing too much, especially when changes are being made near Org heading lines. In worst-case scenario, Org mode has to drop parser cache for the whole edited subtree repeatedly, slowing things down by orders of magnitude. And this is not a theoretical consideration - I had to tweak things considerably in the past for certain Org mass-editing commands in order to not slow them down because of repeated cache drops. --=20 Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 2 Apr 2024 17:52:00 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Apr 02 13:52:00 2024 Received: from localhost ([127.0.0.1]:56172 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rriIh-0001XP-I5 for submit <at> debbugs.gnu.org; Tue, 02 Apr 2024 13:52:00 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:19075) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rriIf-0001Wz-Dj for 70077 <at> debbugs.gnu.org; Tue, 02 Apr 2024 13:51:58 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 520DA442379; Tue, 2 Apr 2024 13:51:47 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712080305; bh=a/oiLDnMuo7F/+MgqslqKeDcbREiovQzSFZZfyaMFWM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=EqFYOoCMXO+apmnbz73SbAxvDyroxNSi8y+ESWH+UuhdwgN4WWhAryeWMXp+90eEN shZzaNvRUh8X5jk2ShmK0kwiQwCIPX4XYcUf2Gph6Pz4Vj/14m8Rawmqw0gAFj3yrN lLzZBvN0cCgxsnnjJuQIABf/MUzrXsuoTwSggOtjejVdIJMFKuiE+k1qnocL4XQfAY o/ylDq0PZ/kYdWcA+idozrgv8jQ8zy2fnX64MsdiHE3FIrhKYByI6RD3Buhw/uS3WN RuH/XM9iA/Z4eLmr02/6sDIPKrwGU3OLG2jwoXX9p1pipsLL0bJ97OS272O7X8GouN 2F8ZuQoTJOaEg== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 4809E441FC1; Tue, 2 Apr 2024 13:51:45 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id E41D5120682; Tue, 2 Apr 2024 13:51:44 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <87ttkjspkq.fsf@localhost> (Ihor Radchenko's message of "Tue, 02 Apr 2024 16:21:25 +0000") Message-ID: <jwvo7aroeqr.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> <87ttkjspkq.fsf@localhost> Date: Tue, 02 Apr 2024 13:51:42 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.057 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > I agree, but only when subsequent changes intersect each other. > When distant changes are combined together, they cover a lot of buffer > text that has not been changed - they may trigger a lot more work > compared to more granular changes. Consider, for example, two changes > near the beginning and the end of the buffer: > (1..10) and (1000000...(point-max)) > If such changes are tracked by tree-sitter or other parser, there is a > world of difference between requesting to re-parse individual segments > and the whole buffer. Oh, definitely. For some clients, the amount of work doesn't really depend on the size of the change, tho (OTOH the work done by `track-changes.el` *is* affected by the size of the change). Handling disjoint changes can be very important. I'm fairly satisfied with my `track-changes-register-disjoint` solution to this problem. >>> The changes are stored in strings, which get allocated and >>> re-allocated repeatedly. >> Indeed. Tho for N small changes, my code should perform only O(log N) >> re-allocations. > May you explain a bit more about this? > My reading of `track-changes--before' is that `concat' is called on > every change except when a new change is already inside the previously > recorded one. The "previously recorded one" (i.e. the info stored in `track-changes--before-beg/end/string`) is a conservative over-approximation of the changed area. We (at least) double its size every time we have to grow it, specifically to reduce the number of times we need to grow it (otherwise we quickly fall into the O(N=C2=B2) trap). > (aside: I have a hard time reading the code because of > confusing slot names: bbeg vs beg??) "bbeg/bend" stands for "before beg" and "before end" (i.e. the positions as they were before the change). BTW, those two slots don't exist any more in the latest code I sent (but vars with those names are still present here and there). >>>> We could expose a list of simultaneous (and thus disjoint) changes, >>>> which avoids the last problem. But it's a fair bit more work for us, = it >>>> makes the API more complex for the clients, and it's rarely what the >>>> clients really want anyway. >>> FYI, Org parser maintains such a list. >> Could you point me to the relevant code (I failed to find it)? > That code handles a lot more than just changes, so it might not be the > best reference. Anyway... > See the docstring of `org-element--cache-sync-requests', and > the branch in `org-element--cache-submit-request' starting from > ;; Current changes can be merged with first sync request: we > ;; can save a partial cache synchronization. Thanks. [ I see I did search the right file, but the code was too intertwined with other things for me to find that part. ] [ Further discussions of Org's code moved off-list. ] > Hmm. By referring to buffer-undo-list, I meant that intersecting edits > will be automatically merged, as it is usually done in undo system. [ Actually `buffer-undo-list` doesn't do very much merging, if any. AFAIK the only merging that happens there is to "amalgamate" consecutive `self-insert-command`s or consecutive single-char `delete-char`s. =F0=9F= =99=82 ] > I am also not 100% sure why edits being simultaneous is any relevant. If they're not simultaneous it means that they describe N different buffer states and that to interpret a specific change in the list (e.g. to convert buffer positions to line+col positions) you may have to reconstruct its before&after states by applying the other changes. For some use cases this is quite inconvenient. In any case, it's not very important, I think. > What I was talking about is (1) automatically merging subsequent edits > like 1..5 2..7 7..9 into 1..9. (2) if a subsequent edit does not > intersect with previous edited region, record it separately without > merging. That's what you can get now with `track-changes-register-disjoint`. >> The code changes were actually quite simple, so I have now included it. >> See my current PoC code below. > Am I missing something, or will `track-changes--signal-if-disjoint' > alter `track-changes--state' when it calls `track-changes-fetch' -> > `track-changes-reset'? [ I assume you meant `track-changes--clean-state` instead of `track-changes-reset`. ]=20 Yes it will (and there's a bug in `track-changes--before` because of that that I still need to fix =F0=9F=99=81), but that should not be a probl= em for the clients. > Won't that interfere with the information passed to > non-disjoint trackers? No: the separate states will be (re)combined when those trackers call `track-changes-fetch`. There is a nearby poor interference between clients, tho: if one client calls `track-changes-fetch` eagerly all the time, it may prevent another client from getting a "disjoint change" signal because disjointness is noticed only between changes that occurred since the last call to `track-changes--clean-state` (which is called mostly by `track-changes-fetch`). I guess this deserves a FIXME. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 2 Apr 2024 16:21:36 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Apr 02 12:21:36 2024 Received: from localhost ([127.0.0.1]:55826 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrgtB-0008Iv-Qx for submit <at> debbugs.gnu.org; Tue, 02 Apr 2024 12:21:35 -0400 Received: from mout01.posteo.de ([185.67.36.65]:49755) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rrgt7-0008I5-EQ for 70077 <at> debbugs.gnu.org; Tue, 02 Apr 2024 12:21:31 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id 68D2424002A for <70077 <at> debbugs.gnu.org>; Tue, 2 Apr 2024 18:21:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1712074879; bh=099D2+4u5fNlEq3tyHM7sYiP/H/3+hOxowmjOz+zMEE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=bqFrDQfgdM08afCXDiK1xF+cU2jG8zl00tJ+WBZh5q+J6Or37KxviLNfWIwuDGi/S AG2WXlsod+kN7e2dw4/ThTEC3SiCrUWsftBruUSQeCDaxkN9+8rBen0pFpRrd+xE+M VnUyZy0k0GMNDTiBtpia54djbCYADFS++IvthpLwBkS31lA9un4q/nP/oNBlwhlCM0 Kd90p3bNFglYmUVpy50jc6w2EhMPlNYiZtVZ7IY2nZdHzvGwEJGSQZdoZBXv3Rjaag zsODSfqm+TG9d9WfSW5xo2bIWKpnT5CocITFEoXsUS5C6TE6pZMx7lRbKAlS0Hi6H+ +2abXf0T9XrCg== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V8Cms4bw9z6tm8; Tue, 2 Apr 2024 18:21:17 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> Date: Tue, 02 Apr 2024 16:21:25 +0000 Message-ID: <87ttkjspkq.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 1.4 (+) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Stefan Monnier <monnier@HIDDEN> writes: >>> I'm not sure how to combine the benefits of combining small changes into >>> larger ones with the benefits of keeping distant changes separate. >> I am not sure if combining small changes into lar [...] Content analysis details: (1.4 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/, medium trust [185.67.36.65 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H3 RBL: Good reputation (+3) [185.67.36.65 listed in wl.mailspike.net] 0.1 URIBL_SBL_A Contains URL's A record listed in the Spamhaus SBL blocklist [URIs: microsoft.github.io] 0.6 URIBL_SBL Contains an URL's NS IP listed in the Spamhaus SBL blocklist [URIs: microsoft.github.io] 3.0 MANY_TO_CC Sent to 10+ recipients -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 RCVD_IN_MSPIKE_WL Mailspike good senders X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: 0.4 (/) Stefan Monnier <monnier@HIDDEN> writes: >>> I'm not sure how to combine the benefits of combining small changes into >>> larger ones with the benefits of keeping distant changes separate. >> I am not sure if combining small changes into larger ones is at all a >> good idea. > > In my experience, if the amount of work to be done "per change" is not > completely trivial, combining small changes is indispensable (the worst > part being that the cases where it's needed are sufficiently infrequent > that naive users of `*-change-functions` won't know about that, so we > end up with unacceptable slowdowns in corner cases). I agree, but only when subsequent changes intersect each other. When distant changes are combined together, they cover a lot of buffer text that has not been changed - they may trigger a lot more work compared to more granular changes. Consider, for example, two changes near the beginning and the end of the buffer: (1..10) and (1000000...(point-max)) If such changes are tracked by tree-sitter or other parser, there is a world of difference between requesting to re-parse individual segments and the whole buffer. >> The changes are stored in strings, which get allocated and >> re-allocated repeatedly. > > Indeed. Tho for N small changes, my code should perform only O(log N) > re-allocations. May you explain a bit more about this? My reading of `track-changes--before' is that `concat' is called on every change except when a new change is already inside the previously recorded one. (aside: I have a hard time reading the code because of confusing slot names: bbeg vs beg??) >> Repeated string allocations, especially when strings keep growing >> towards the buffer size, is likely going to increase consing and make >> GCs more frequent. > > Similar allocations presumably take place anyway while running the code > (e.g. for the `buffer-undo-list`), so I'm hoping the effect will be > "lost in the noise". I disagree. `buffer-undo-list' only includes the text that has been actually changed. It never creates strings that span between distant regions. As a terminal case, consider alternating between first and second half of the buffer, starting in the middle and editing towards the buffer boundaries - this will involve re-allocation of buffer-long strings on average. >>> We could expose a list of simultaneous (and thus disjoint) changes, >>> which avoids the last problem. But it's a fair bit more work for us, it >>> makes the API more complex for the clients, and it's rarely what the >>> clients really want anyway. >> FYI, Org parser maintains such a list. > > Could you point me to the relevant code (I failed to find it)? That code handles a lot more than just changes, so it might not be the best reference. Anyway... See the docstring of `org-element--cache-sync-requests', and the branch in `org-element--cache-submit-request' starting from ;; Current changes can be merged with first sync request: we ;; can save a partial cache synchronization. >> We previously discussed a similar API in >> https://yhetil.org/emacs-bugs/87o7iq1emo.fsf@localhost/ > > IIUC this discusses a *sequence* of edits. In the point to which you > replied I was discussing keeping a list of *simultaneous* edits. Hmm. By referring to buffer-undo-list, I meant that intersecting edits will be automatically merged, as it is usually done in undo system. I am also not 100% sure why edits being simultaneous is any relevant. Maybe we are talking about different things? What I was talking about is (1) automatically merging subsequent edits like 1..5 2..7 7..9 into 1..9. (2) if a subsequent edit does not intersect with previous edited region, record it separately without merging. > This said, the downside in both cases is that the specific data that we > need from such a list tends to depend on the user. E.g. you suggest > > (BEG END_BEFORE END_AFTER COUNTER) > > but that is not sufficient reconstruct the corresponding buffer state, > so things like Eglot/CRDT can't use it. Ideally for CRDT I think you'd > want a sequence of > > (BEG END-BEFORE STRING-AFTER) Right. It's just that Org mode does not need STRING-AFTER, which is why I did not think about it in my proposal. Of course, having STRING-AFTER is required to get full reconstruction of the buffer state. > but for Eglot this is not sufficient because Eglot needs to convert BEG > and END_BEFORE into LSP positions (i.e. "line+col") and for that it > needs to reproduce the past buffer state. So instead, what Eglot needs > (and does indeed build using `*-change-functions`) is a sequence of > > (LSP-BEG LSP-END-BEFORE STRING-AFTER) > > [ Tho it seems it also needs a "LENGTH" of the deleted chunk, not sure > exactly why, but I guess it's a piece of redundant info the servers > can use to sanity-check the data? ] It is to support deprecated LSP spec (I presume that older LSP servers may still be using the old spec): https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/ export type TextDocumentContentChangeEvent = { * The range of the document that changed. range: Range; * The optional length of the range that got replaced. * @deprecated use range instead. rangeLength?: number; * The new text for the provided range. text: string; > The code changes were actually quite simple, so I have now included it. > See my current PoC code below. Am I missing something, or will `track-changes--signal-if-disjoint' alter `track-changes--state' when it calls `track-changes-fetch' -> `track-changes-reset'? Won't that interfere with the information passed to non-disjoint trackers? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 2 Apr 2024 15:17:56 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Apr 02 11:17:56 2024 Received: from localhost ([127.0.0.1]:54983 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrftc-0004mi-7u for submit <at> debbugs.gnu.org; Tue, 02 Apr 2024 11:17:56 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:27096) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rrfta-0004lr-IK for 70077 <at> debbugs.gnu.org; Tue, 02 Apr 2024 11:17:55 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id D0D3C10005D; Tue, 2 Apr 2024 11:17:44 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1712071063; bh=eeNWoHWR/wUiP2mhIy2njzwEpHTZJYqdVZ1Aqxh3DhM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=WLVOCjS47f//H5W3OgEUuyNMycF9xhOwgoxoqjVbuygzfIUopH+GopUUTods8cC2L arW30CBKKzkED8+qaCkzEUQHdfbxEhqeTWeD68fwCKbbiE6J7MpvZN8QDZRAS5ml2K TaO4fecFA7iOoBrVDalNpRlHC6H2AitWQ1bSGP7EKD7BNms228NGY+MIdLqDV6qVem vD3vc71tz/u8P8Z5PVstwBEMtEF9jsmUCakSuyWTXXuTAQWFO5ZmCYh1s+Uwdsrlv3 dktTtzDD0YAhQtGKJhD+B2W//ujVpY8wQjzpgqpqPgPDr4HFPjxj9P1ERFm1o8FsD4 HSL1B1adk6Oiw== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id A7833100048; Tue, 2 Apr 2024 11:17:43 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 4972512082A; Tue, 2 Apr 2024 11:17:43 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <87wmpfsv2y.fsf@localhost> (Ihor Radchenko's message of "Tue, 02 Apr 2024 14:22:29 +0000") Message-ID: <jwv4jcjq16n.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> <87wmpfsv2y.fsf@localhost> Date: Tue, 02 Apr 2024 11:17:41 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.043 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable >> I'm not sure how to combine the benefits of combining small changes into >> larger ones with the benefits of keeping distant changes separate. > I am not sure if combining small changes into larger ones is at all a > good idea. In my experience, if the amount of work to be done "per change" is not completely trivial, combining small changes is indispensable (the worst part being that the cases where it's needed are sufficiently infrequent that naive users of `*-change-functions` won't know about that, so we end up with unacceptable slowdowns in corner cases). It also keeps the API simpler since the clients only need to care about at most one (BEG END BEFORE) item at any given time. > The changes are stored in strings, which get allocated and > re-allocated repeatedly. Indeed. Tho for N small changes, my code should perform only O(log N) re-allocations. > Repeated string allocations, especially when strings keep growing > towards the buffer size, is likely going to increase consing and make > GCs more frequent. Similar allocations presumably take place anyway while running the code (e.g. for the `buffer-undo-list`), so I'm hoping the effect will be "lost in the noise". But admittedly for code which does not need the `before` string at all (such as `diff-mode.el`), it's indeed a waste of effort. I haven't been able to come up with an API which is still simple but without such costs. > Aside... > How nice would it be if buffer state and buffer text were persistent. > Like in https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimpl= ementation =F0=9F=99=82 >> We could expose a list of simultaneous (and thus disjoint) changes, >> which avoids the last problem. But it's a fair bit more work for us, it >> makes the API more complex for the clients, and it's rarely what the >> clients really want anyway. > FYI, Org parser maintains such a list. Could you point me to the relevant code (I failed to find it)? > We previously discussed a similar API in > https://yhetil.org/emacs-bugs/87o7iq1emo.fsf@localhost/ IIUC this discusses a *sequence* of edits. In the point to which you replied I was discussing keeping a list of *simultaneous* edits. This said, the downside in both cases is that the specific data that we need from such a list tends to depend on the user. E.g. you suggest (BEG END_BEFORE END_AFTER COUNTER) but that is not sufficient reconstruct the corresponding buffer state, so things like Eglot/CRDT can't use it. Ideally for CRDT I think you'd want a sequence of (BEG END-BEFORE STRING-AFTER) but for Eglot this is not sufficient because Eglot needs to convert BEG and END_BEFORE into LSP positions (i.e. "line+col") and for that it needs to reproduce the past buffer state. So instead, what Eglot needs (and does indeed build using `*-change-functions`) is a sequence of (LSP-BEG LSP-END-BEFORE STRING-AFTER) [ Tho it seems it also needs a "LENGTH" of the deleted chunk, not sure exactly why, but I guess it's a piece of redundant info the servers can use to sanity-check the data? ] >> But it did occur to me that we could solve the "disjoint changes" >> problem in the following way: signal the client (from >> `before-change-functions`) when a change is about to be made "far" from >> the currently pending changes. > This makes sense. The code changes were actually quite simple, so I have now included it. See my current PoC code below. Stefan --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=track-changes.el Content-Transfer-Encoding: quoted-printable ;;; track-changes.el --- API to react to buffer modifications -*- lexical-= binding: t; -*- ;; Copyright (C) 2024 Free Software Foundation, Inc. ;; Author: Stefan Monnier <monnier@HIDDEN> ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This library is a layer of abstraction above `before-change-functions' ;; and `after-change-functions' which takes care of accumulating changes ;; until a time when its client finds it convenient to react to them. ;; It provides the following operations: ;; ;; (track-changes-register SIGNAL) ;; (track-changes-fetch ID FUNC) ;; (track-changes-unregister ID) ;; (track-changes-reset ID) ;; (track-changes-register-disjoint ID) ;; ;; A typical use case might look like: ;; ;; (defvar my-foo--change-tracker nil) ;; (define-minor-mode my-foo-mode ;; "Fooing like there's no tomorrow." ;; (if (null my-foo-mode) ;; (when my-foo--change-tracker ;; (track-changes-unregister my-foo--change-tracker) ;; (setq my-foo--change-tracker nil)) ;; (unless my-foo--change-tracker ;; (setq my-foo--change-tracker ;; (track-changes-register ;; (lambda (id) ;; (track-changes-fetch ;; id (lambda (beg end before) ;; ..DO THE THING..)))))))) ;;; Code: ;; FIXME: Try and do some sanity-checks (e.g. looking at `buffer-size'), ;; to detect if/when we somehow missed some changes. ;; FIXME: The API doesn't offer an easy way to signal a "full resync" ;; kind of change, as might be needed if we lost changes. (eval-when-compile (require 'cl-lib)) (cl-defstruct (track-changes--tracker (:noinline t) (:constructor nil) (:constructor track-changes--tracker ( signal state))) ( signal nil :read-only t) state) (cl-defstruct (track-changes--state (:noinline t) (:constructor nil) (:constructor track-changes--state ())) (beg (point-max)) (end (point-min)) (before nil) (next nil)) (defvar-local track-changes--trackers ()) (defvar-local track-changes--clean-trackers () "List of trackers that are clean. Those are the trackers that get signaled when a change is made.") (defvar-local track-changes--disjoint-trackers () "List of trackers that want to react to disjoint changes. These trackers' are signaled every time track-changes notices that some upcoming changes touch another \"distant\" part of the buffer.") (defvar-local track-changes--state nil) ;; `track-changes--before-*' keep track of the content of the ;; buffer when `track-changes--state' was cleaned. (defvar-local track-changes--before-beg nil) (defvar-local track-changes--before-end nil) (defvar-local track-changes--before-string nil) (defvar-local track-changes--buffer-size nil) (defun track-changes-register ( signal) "Register a new tracker and return a new tracker ID. SIGNAL is a function that will be called with one argument (the tracker ID)= when the current buffer is modified, so that we can react to the change. Once called, SIGNAL is not called again until `track-changes-fetch' is called with the corresponding tracker ID." ;; FIXME: Add an optional arg to choose between `funcall' and `funcall-la= ter'? ;; FIXME: Add an optional arg to say we don't need the `before' info? (track-changes--clean-state) (add-hook 'before-change-functions #'track-changes--before nil t) (add-hook 'after-change-functions #'track-changes--after nil t) (let ((tracker (track-changes--tracker signal track-changes--state))) (push tracker track-changes--trackers) (push tracker track-changes--clean-trackers) tracker)) (defun track-changes-register-disjoint (id) "Enable disjoint change tracking (DCT) for tracker ID. This is needed when combining disjoint changes into one bigger change is unacceptable, typically for performance reasons. When DCT is enabled, we call ID's SIGNAL function every time we are about to combine changes from \"distant\" parts of the buffer. These calls are distinguished from normal calls by calling SIGNAL with a second argument which is the distance between the upcoming change and the previous changes. In that case SIGNAL is called directly from `before-change-functions' and should thus be extra careful: don't modify the buffer, don't call a function that may block, ... In order to prevent the upcoming change from being combined with the previo= us changes, SIGNAL needs to call `track-changes-fetch' before it returns." (cl-assert (memq id track-changes--trackers)) (unless (memq id track-changes--disjoint-trackers) (push id track-changes--disjoint-trackers))) (defun track-changes-reset (id) "Mark all past changes as handled for tracker ID. Does not re-enable ID's signal." (track-changes--clean-state) (setf (track-changes--tracker-state id) track-changes--state)) (defun track-changes-unregister (id) "Remove the tracker denoted by ID. Trackers can consume resources (especially if `track-changes-fetch' is not called), so it is good practice to unregister them when you don't need them any more." (unless (memq id track-changes--trackers) (error "Unregistering a non-registered tracker: %S" id)) (setq track-changes--trackers (delq id track-changes--trackers)) (setq track-changes--clean-trackers (delq id track-changes--clean-tracker= s)) (setq track-changes--disjoint-trackers (delq id track-changes--disjoint-trackers)) (when (null track-changes--trackers) (setq track-changes--state nil) (setq track-changes--buffer-size nil) (setq track-changes--before-string nil) (remove-hook 'before-change-functions #'track-changes--before t) (remove-hook 'after-change-functions #'track-changes--after t))) (defun track-changes--clean-state () (cond ((null track-changes--state) (cl-assert (null track-changes--before-string)) (cl-assert (null track-changes--buffer-size)) ;; No state has been created yet. Do it now. (setq track-changes--buffer-size (buffer-size)) (setq track-changes--state (track-changes--state))) ((null track-changes--before-string) nil) ((> (track-changes--state-beg track-changes--state) (track-changes--state-end track-changes--state)) ;; before-c-f was run but not after-c-f, so there was really no change. nil) (t ;; FIXME: We may be in-between a before-c-f and an after-c-f, so we ;; should save some of the current buffer in case an after-c-f comes ;; before a before-c-f. (cl-assert (<=3D track-changes--before-beg (track-changes--state-beg track-changes--state) (track-changes--state-end track-changes--state) track-changes--before-end)) (cl-assert (null (track-changes--state-before track-changes--state))) (setf (track-changes--state-before track-changes--state) ;; The before-* vars can cover more text than the actually modifi= ed ;; area, so trim it down now to the relevant part. (if (=3D (- track-changes--before-end track-changes--before-beg) (- (track-changes--state-end track-changes--state) (track-changes--state-beg track-changes--state))) ;; Common case. track-changes--before-string (substring track-changes--before-string (- (track-changes--state-beg track-changes--state) track-changes--before-beg) (- (length track-changes--before-string) (- track-changes--before-end (track-changes--state-end track-changes--state)))))) (setq track-changes--before-beg nil track-changes--before-end nil track-changes--before-string nil) (let ((new (track-changes--state))) (setf (track-changes--state-next track-changes--state) new) (setq track-changes--state new))))) (defvar track-changes-disjoint-threshold 100 "Distance below which changes are not considered disjoint.") (defun track-changes--signal-if-disjoint (pos1 pos2) (let ((distance (- pos2 pos1))) (when (> distance (max track-changes-disjoint-threshold ;; If the distance is smaller than the size of the current ;; change, then we may as well consider it as "near". (length track-changes--before-string) (- track-changes--before-end track-changes--before-beg))) (dolist (tracker track-changes--disjoint-trackers) (funcall (track-changes--tracker-signal tracker) tracker distance))= ))) (defun track-changes--before (beg end) (cl-assert (=3D track-changes--buffer-size (buffer-size))) (cl-assert track-changes--state) (cl-assert (<=3D beg end)) (if (null track-changes--before-string) (progn (setf track-changes--before-string (buffer-substring-no-properties beg end)) (setf track-changes--before-beg beg) (setf track-changes--before-end end)) (cl-assert (save-restriction (widen) (<=3D (point-min) track-changes--before-beg track-changes--before-end (point-max)))) (when (< beg track-changes--before-beg) (when track-changes--disjoint-trackers (track-changes--signal-if-disjoint end track-changes--before-beg)) (let* ((old-bbeg track-changes--before-beg) ;; To avoid O(N=C2=B2) behavior when faced with many small cha= nges, ;; we copy more than needed. (new-bbeg (min (max (point-min) (- old-bbeg (length track-changes--before-string))) beg))) (setf track-changes--before-beg new-bbeg) (cl-callf (lambda (old new) (concat new old)) track-changes--before-string (buffer-substring-no-properties new-bbeg old-bbeg)))) (when (< track-changes--before-end end) (when track-changes--disjoint-trackers (track-changes--signal-if-disjoint track-changes--before-end beg)) (let* ((old-bend track-changes--before-end) ;; To avoid O(N=C2=B2) behavior when faced with many small cha= nges, ;; we copy more than needed. (new-bend (max (min (point-max) (+ old-bend (length track-changes--before-string))) end))) (setf track-changes--before-end new-bend) (cl-callf concat track-changes--before-string (buffer-substring-no-properties old-bend new-bend)))))) (defun track-changes--after (beg end len) (cl-assert track-changes--state) (cl-assert track-changes--before-string) (let ((offset (- (- end beg) len))) (cl-incf track-changes--before-end offset) (cl-incf track-changes--buffer-size offset) (cl-assert (=3D track-changes--buffer-size (buffer-size))) (cl-assert (save-restriction (widen) (<=3D (point-min) track-changes--before-beg beg end track-changes--before-end (point-max)))) ;; Note the new changes. (when (< beg (track-changes--state-beg track-changes--state)) (setf (track-changes--state-beg track-changes--state) beg)) (cl-callf (lambda (old-end) (max end (+ old-end offset))) (track-changes--state-end track-changes--state))) (cl-assert (<=3D track-changes--before-beg (track-changes--state-beg track-changes--state) beg end (track-changes--state-end track-changes--state) track-changes--before-end)) (while track-changes--clean-trackers (let ((tracker (pop track-changes--clean-trackers))) ;; FIXME: Use `funcall'? (funcall-later #'track-changes--call-signal (list (current-buffer) tracker))))) (defun track-changes--call-signal (buf tracker) (when (buffer-live-p buf) (with-current-buffer buf ;; Silence ourselves if `track-changes-fetch' was called in the mean = time. (unless (memq tracker track-changes--clean-trackers) (funcall (track-changes--tracker-signal tracker) tracker))))) (defun track-changes-fetch (id func) ;; FIXME: Bad name, "fetch" doesn't make much sense for it any more. "Fetch the pending changes. ID is the tracker ID returned by a previous `track-changes-register'. FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) where BEGIN..END delimit the region that was changed since the last time `track-changes-fetch' was called and BEFORE is a string containing the previous content of that region. If no changes occurred since the last time, FUNC is not called and we return nil, otherwise we return the value returned by FUNC, and re-enable the TRACKER corresponding to ID." (let ((beg nil) (end nil) (before nil) (states ())) ;; Transfer the data from `track-changes--before-string' ;; to the tracker's state object, if needed. (track-changes--clean-state) ;; We want to combine the states from most recent to oldest, ;; so reverse them. (let ((state (track-changes--tracker-state id))) (while state (push state states) (setq state (track-changes--state-next state)))) (when (null (track-changes--state-before (car states))) (cl-assert (eq (car states) track-changes--state)) (setq states (cdr states))) (if (null states) (progn (cl-assert (memq id track-changes--clean-trackers)) nil) (dolist (state states) (let ((prevbbeg (track-changes--state-beg state)) (prevbend (track-changes--state-end state)) (prevbefore (track-changes--state-before state))) (if (not before) (progn ;; This is the most recent change. Just initialize the var= s. (setq beg (track-changes--state-beg state)) (setq end (track-changes--state-end state)) (setq before prevbefore) (unless (and (=3D beg prevbbeg) (=3D end prevbend)) (setq before (substring before (- beg (track-changes--state-beg state)) (- (length before) (- (track-changes--state-end state) end)))))) (let ((endb (+ beg (length before)))) (when (< prevbbeg beg) (setq before (concat (buffer-substring-no-properties prevbbeg beg) before)) (setq beg prevbbeg) (cl-assert (=3D endb (+ beg (length before))))) (when (< endb prevbend) (let ((new-end (+ end (- prevbend endb)))) (setq before (concat before (buffer-substring-no-properties end new-end))) (setq end new-end) (cl-assert (=3D prevbend (+ beg (length before)))) (setq endb (+ beg (length before))))) (cl-assert (<=3D beg prevbbeg prevbend endb)) ;; The `prevbefore' is covered by the new one. (setq before (concat (substring before 0 (- prevbbeg beg)) prevbefore (substring before (- (length before) (- endb prevbend))))))))) (cl-assert (<=3D (point-min) beg end (point-max))) ;; Update the tracker's state before running `func' so we don't risk ;; mistakenly replaying the changes in case `func' exits non-locally. (setf (track-changes--tracker-state id) track-changes--state) (unwind-protect (funcall func beg end before) ;; Re-enable the tracker's signal only after running `func', so ;; as to avoid recursive invocations. (cl-pushnew id track-changes--clean-trackers))))) (defmacro with-track-changes (id vars &rest body) (declare (indent 2) (debug (form sexp body))) `(track-changes-fetch ,id (lambda ,vars ,@body))) =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 (provide 'track-changes) ;;; track-changes.el end here. --=-=-=--
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 2 Apr 2024 14:22:42 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Apr 02 10:22:42 2024 Received: from localhost ([127.0.0.1]:54889 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrf2A-00084J-1r for submit <at> debbugs.gnu.org; Tue, 02 Apr 2024 10:22:42 -0400 Received: from mout02.posteo.de ([185.67.36.66]:51447) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rrf27-00083M-NM for 70077 <at> debbugs.gnu.org; Tue, 02 Apr 2024 10:22:40 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id EF7A9240103 for <70077 <at> debbugs.gnu.org>; Tue, 2 Apr 2024 16:22:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1712067750; bh=KsuLsrFU8Ol8cigb1fjaOx9Agwj5H0AYLY41KtcG9oY=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=jKD7uwYngrTL8OoGjlW7uL5luUl8Kc6/DlV4K7P3RkUV13cFBxInT4Alg86nKJq9H q6ejhxeSJAfhFUrsdcF3gJ1Po/+OIwCoumN+9owqfXuyPMRYWRn8fQ8eIRQXodK93Q ifp9yP8Hpb4tU3SCHTM+c2Ak6pT8OBde1UKu9UnG+0j83YFEUBQXgcotUiOaVKv4eW jwn1i+mJo21RPH+LhUIEp1wIX6irSI2rUGC5RAbeI5rXgnhg5uKp6Zx4bfPbqJPzoo AwviuIzpitms5ftMRFNg8npFGrC88nxeoJ3y03NSNTnLUDq3KoJW2zDBBAscBx0KIY GT1cPP+2tGGHg== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V897m05gFz9rxD; Tue, 2 Apr 2024 16:22:27 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> Date: Tue, 02 Apr 2024 14:22:29 +0000 Message-ID: <87wmpfsv2y.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Stefan Monnier <monnier@HIDDEN> writes: >>> Do I understand correctly that such bundling may result in a situation >>> when BEFORE is very long? In particular, I am thinking of a situation >>> when there are changes near the beginning and the end of buffer in quick >>> succession. >> Yes! > > I'm not sure how to combine the benefits of combining small changes into > larger ones with the benefits of keeping distant changes separate. I am not sure if combining small changes into larger ones is at all a good idea. The changes are stored in strings, which get allocated and re-allocated repeatedly. Repeated string allocations, especially when strings keep growing towards the buffer size, is likely going to increase consing and make GCs more frequent. > I don't want to expose in the API a "sequence of changes", because > that's difficult to use in general: the only thing a client can conveniently > do with it is to keep their own copy of the buffer (e.g. in the LSP > server) and apply the changes in the order given. But if for some > reason you need to do something else (e.g. convert the position from > charpos to line+col) you're in a world of hurt because (except for the > last element in the sequence) you don't have easy access to the state > the buffer was in when the change was made. Aside... How nice would it be if buffer state and buffer text were persistent. Like in https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation > We could expose a list of simultaneous (and thus disjoint) changes, > which avoids the last problem. But it's a fair bit more work for us, it > makes the API more complex for the clients, and it's rarely what the > clients really want anyway. FYI, Org parser maintains such a list. We previously discussed a similar API in https://yhetil.org/emacs-bugs/87o7iq1emo.fsf@localhost/ > But it did occur to me that we could solve the "disjoint changes" > problem in the following way: signal the client (from > `before-change-functions`) when a change is about to be made "far" from > the currently pending changes. > > The API would still expose only (BEG END BEFORE) rather than > lists/sequences of changes, but the clients could then decide to record > disjoint changes as they occur and thus create&manage their own > list/sequence of changes. More specifically, someone could come > a create a new API on top which exposes a list/sequence of changes. > > The main downside is that this signal of "upcoming disjoint change" > would have to be called from `before-change-functions`, so the client > would need to be careful as usual (e.g. don't modify the buffer, don't > do any blocking operation, ...). This makes sense. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 1 Apr 2024 17:50:03 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 01 13:50:03 2024 Received: from localhost ([127.0.0.1]:51499 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrLnG-0002Um-UF for submit <at> debbugs.gnu.org; Mon, 01 Apr 2024 13:50:03 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:15759) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rrLnE-0002U9-QS for 70077 <at> debbugs.gnu.org; Mon, 01 Apr 2024 13:50:01 -0400 Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 7C59180B3D; Mon, 1 Apr 2024 13:49:51 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711993790; bh=VQHhZUhpG2vP8s07j656OeU+juE05tho/VcNcY6O10c=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=dgCgENIJ0EeQRg8CIbm+X3SCyigVLX0zg4QQDsHmklnGrRqjXPD0uAwEHHuXjhgp1 uyCfQ7ISmCCyoDp67RuAPKSKKnAGsp4g1MvVYIhBtGx7CSt75yvCtm96Hs94Wccp3e /Lcd7ZJhCpLrBF/M7XAi5SuJTWEMqYrN1LjzzTRGkgaSoAnbm7pV5pqpfJIzqaj333 VHyLHTsY/r7ImeyPWiu2kXVAS7RY4aWxTm9amedEawqwKHFU5lUYxc2h6ImJ2Z7Jmk Ee7NomeJ8sp3VzXsTLONnOiIx6n3oCDaJZcSx8dctku+ldLNu1cSl1LUHoRBDT4Pgp W8Gx7NFB6JDpw== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 1ECF5806EF; Mon, 1 Apr 2024 13:49:50 -0400 (EDT) Received: from alfajor (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id AAA7512046C; Mon, 1 Apr 2024 13:49:49 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> (Stefan Monnier's message of "Mon, 01 Apr 2024 10:51:53 -0400") Message-ID: <jwv1q7p2dqw.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> Date: Mon, 01 Apr 2024 13:49:48 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.142 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) >>> ... That's why `track-changes.el` bundles those into a single (BEG END >>> BEFORE) change, which makes sure BEG/END are currently-valid buffer >>> positions and thus easy to use. >>> The previous buffer state is not directly available but can be >>> easily reconstructed from (BEG END BEFORE). >> Do I understand correctly that such bundling may result in a situation >> when BEFORE is very long? In particular, I am thinking of a situation >> when there are changes near the beginning and the end of buffer in quick >> succession. > Yes! I'm not sure how to combine the benefits of combining small changes into larger ones with the benefits of keeping distant changes separate. I don't want to expose in the API a "sequence of changes", because that's difficult to use in general: the only thing a client can conveniently do with it is to keep their own copy of the buffer (e.g. in the LSP server) and apply the changes in the order given. But if for some reason you need to do something else (e.g. convert the position from charpos to line+col) you're in a world of hurt because (except for the last element in the sequence) you don't have easy access to the state the buffer was in when the change was made. We could expose a list of simultaneous (and thus disjoint) changes, which avoids the last problem. But it's a fair bit more work for us, it makes the API more complex for the clients, and it's rarely what the clients really want anyway. But it did occur to me that we could solve the "disjoint changes" problem in the following way: signal the client (from `before-change-functions`) when a change is about to be made "far" from the currently pending changes. The API would still expose only (BEG END BEFORE) rather than lists/sequences of changes, but the clients could then decide to record disjoint changes as they occur and thus create&manage their own list/sequence of changes. More specifically, someone could come a create a new API on top which exposes a list/sequence of changes. The main downside is that this signal of "upcoming disjoint change" would have to be called from `before-change-functions`, so the client would need to be careful as usual (e.g. don't modify the buffer, don't do any blocking operation, ...). Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 1 Apr 2024 14:52:18 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 01 10:52:18 2024 Received: from localhost ([127.0.0.1]:51354 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrJ1C-0002tB-Su for submit <at> debbugs.gnu.org; Mon, 01 Apr 2024 10:52:18 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:42354) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rrJ16-0002sp-Gi for 70077 <at> debbugs.gnu.org; Mon, 01 Apr 2024 10:52:13 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 70B3410004C; Mon, 1 Apr 2024 10:51:59 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711983118; bh=pb+RkplP5OqfoZO2aH/H6M/03bzldwj9nKqyfdGAgM0=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=bWRLq+Wauhhzj1ocpEOZ04pd0bfnynvRYQJf7KROmwqydenlfCipR1Nk4KLnj262Y PyYKGE7s35jc7lgbHbLccmfRQ29RZ6Lg3uxS81Iqx3OsRu7EFNkIDT7mL/Dng9yqMP dQ5z2yfETAndsRe1WbwQgVg/rqZ8l2FKxhjNixEdHi42G2lEVkTn7rikZHtzUyKcIn frruEDYSf9PN5f21x4qcwDHfj8ZmWGyxUUS25/lHpLZYkoee6MCAGUjc0J3ZO0N4tS tAyk2Ly+v1FBsfl8DzbyDSgRz6SduaFrbILIQ80czs2FxsjDpIcCThxIT0dUaa8NfF sRGB0caZFHhcQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 68D74100046; Mon, 1 Apr 2024 10:51:58 -0400 (EDT) Received: from alfajor (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 0781F1206CD; Mon, 1 Apr 2024 10:51:57 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <877chh8fjk.fsf@localhost> (Ihor Radchenko's message of "Mon, 01 Apr 2024 11:53:51 +0000") Message-ID: <jwvr0fp2l3c.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <877chh8fjk.fsf@localhost> Date: Mon, 01 Apr 2024 10:51:53 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.045 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) >> ... That's why `track-changes.el` bundles those into a single (BEG END >> BEFORE) change, which makes sure BEG/END are currently-valid buffer >> positions and thus easy to use. >> The previous buffer state is not directly available but can be >> easily reconstructed from (BEG END BEFORE). > > Do I understand correctly that such bundling may result in a situation > when BEFORE is very long? In particular, I am thinking of a situation > when there are changes near the beginning and the end of buffer in quick > succession. Yes! Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 1 Apr 2024 11:53:57 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Apr 01 07:53:57 2024 Received: from localhost ([127.0.0.1]:49752 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rrGEf-0002Wk-9R for submit <at> debbugs.gnu.org; Mon, 01 Apr 2024 07:53:57 -0400 Received: from mout01.posteo.de ([185.67.36.65]:45993) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rrGEZ-0002WQ-Oj for 70077 <at> debbugs.gnu.org; Mon, 01 Apr 2024 07:53:55 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id 9F72F24002D for <70077 <at> debbugs.gnu.org>; Mon, 1 Apr 2024 13:53:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1711972422; bh=rKvUL8YUXnkpjBKeHmMFtY4LD3yIB6YLPmVOOVA9srM=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=XySvGXVMNDdDEwulu+Zedu3bYHTmtDm8oOYW7IihOayGJ19yoRfEDRJpVSl3ljFnV Zbu8IcqYxBk12eG/DudC0ryEFc8uj5AoqtUkYGgboQ+r/wW87jkC5j8P0MeDvGQgyL y8/IOvK9CKZZaB1zPoZW2Zd4pQW7aFb+bJLob2bEE4cwS5KuOw4Z99xiRTmCHwl4z6 kTwG8poR/pQ3k0x2Et+i702uIX15QvkHcAe0DmcDo5HTy94Pupwpmkictj5GOe0PRP +l4V3Q1ELsm+F8ueNKOORs36UEDmWF0QNwu7kJ0lxkaPB31/wAqs05NWECe+j+WTsB 6xAuGEivoeErQ== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V7TtW5c5lz9rxF; Mon, 1 Apr 2024 13:53:39 +0200 (CEST) From: Ihor Radchenko <yantar92@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> Date: Mon, 01 Apr 2024 11:53:51 +0000 Message-ID: <877chh8fjk.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Stefan Monnier <monnier@HIDDEN> writes: > ... That's why `track-changes.el` bundles those into a single (BEG END > BEFORE) change, which makes sure BEG/END are currently-valid buffer > positions and thus easy to use. > The previous buffer state is not directly available but can be > easily reconstructed from (BEG END BEFORE). Do I understand correctly that such bundling may result in a situation when BEFORE is very long? In particular, I am thinking of a situation when there are changes near the beginning and the end of buffer in quick succession. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 31 Mar 2024 02:58:09 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 22:58:09 2024 Received: from localhost ([127.0.0.1]:46396 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqlOa-0007g0-SV for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 22:58:09 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:22171) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqlOW-0007fU-Ae for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 22:58:06 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id A00B410004C; Sat, 30 Mar 2024 22:57:56 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711853875; bh=fL5wdRPkydDRn4CcogctMmcdl293Bsos6U6rGGR1pPM=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=g5cr+6A5GDNyg292K6iJ4+hJ6tZIJUBYwm1xvQhbSZBFJP8Y6j34MmsNRG8wKLERx giZdBptXa1B872oQqWMbJdbBjmfZeOU/8vJZt60q/2K+WyPUnP/igk39ivBpaY0xWZ SIvRRP8zU4x+KohRgndkvXRmbZFhOZplTmdd0kHvjrU580hfmJ4DZl9OsuspGKnrDm Jsq5vWS+v+Ken+0efWCYNeXHev3PntXg4LT09rCWwXZU4NxNDXYzeItqrVMfMsP6qP AZFZD84/WE4IUdwg1OG0/gZJwLbi2mAJEKQjLGY/FjDee4qzkaVyE4c+DL+EQzsjJy 8JqfwHEz1xHsw== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id A904D100046; Sat, 30 Mar 2024 22:57:55 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 4F01D120828; Sat, 30 Mar 2024 22:57:55 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <867chjd5yp.fsf@HIDDEN> (Eli Zaretskii's message of "Sat, 30 Mar 2024 19:45:02 +0300") Message-ID: <jwvedbrt8hi.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> <867chjd5yp.fsf@HIDDEN> Date: Sat, 30 Mar 2024 22:57:54 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.115 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) >> `track-changes-fetch` will call its function argument only once. >> If several changes happened since last time, `track-changes.el` will >> summarize them into a single (BEG END BEFORE). > > Then I don't think you will be able to guarantee that in all cases. > You are basically trying to solve a problem that many packages which > used the modification hooks tried to solve, but where they relied on > some specifics of the problem they wanted to solve, you are trying to > solve it in general, and I just don't believe it's possible (but will > be happy to learn I'm mistaken). Indeed, there are cases where bugs in our C code will get in the way, and I'll have to return something like (BEG END :error). But other than that, the current code already handles "arbitrary" merging. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 16:45:27 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 12:45:27 2024 Received: from localhost ([127.0.0.1]:46100 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqbpe-0001Zu-Q1 for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 12:45:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:50908) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqbpc-0001Z6-2I for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 12:45:24 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqbpQ-0001e9-3C; Sat, 30 Mar 2024 12:45:13 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=0v1qgFXfC5slJ+ceh6JS1olKcKXd56/fKB9Jj+ODPLw=; b=LXg5lAaLdlPI hbvghrpfoePnMTkoO9PDkcry9CAXQ5bJt63OFtkU1oMHCEh4Paz/N3gCdwyHeHJFrWx2MQrC9ctNK 71tnB2R0Y39X9va6mdE4yrdTbcw8VzjwdsuJnPIA2fZTKZiNdA7aWhb8vrtss+CMKTxL1681/CfYt L8gumBfzZcvpBvu/W7I8Q2SEPTrvn86X8eJsBHyFMghl2S+/GddfPzbs3DiPKMz/Srr6830cf0xVi xemacgUKNChxPymVsBLPLRU7tOt4A+BW19zr5b3X97aFBXmjMeLn7rggKIelijnwzTVLHAcr3tzcG q466FSQYxXjE0Yj1zhX3Ow==; Date: Sat, 30 Mar 2024 19:45:02 +0300 Message-Id: <867chjd5yp.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> (message from Stefan Monnier on Sat, 30 Mar 2024 10:58:40 -0400) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > From: Stefan Monnier <monnier@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, mail@HIDDEN, yantar92@HIDDEN, > acm@HIDDEN, joaotavora@HIDDEN, alan.zimm@HIDDEN, > frederic.bour@HIDDEN, phillip.lord@HIDDEN, > stephen_leake@HIDDEN, casouri@HIDDEN, qhong@HIDDEN > Date: Sat, 30 Mar 2024 10:58:40 -0400 > > > Otherwise, the above looks like doing all the job in > > after-change-functions, and it is not clear to me how is that better, > > since if track-changes-fetch will fetch a series of changes, > > `track-changes-fetch` will call its function argument only once. > If several changes happened since last time, `track-changes.el` will > summarize them into a single (BEG END BEFORE). Then I don't think you will be able to guarantee that in all cases. You are basically trying to solve a problem that many packages which used the modification hooks tried to solve, but where they relied on some specifics of the problem they wanted to solve, you are trying to solve it in general, and I just don't believe it's possible (but will be happy to learn I'm mistaken).
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 14:58:54 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 10:58:53 2024 Received: from localhost ([127.0.0.1]:46015 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqaAX-0004xL-Aj for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 10:58:53 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:59256) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqaAV-0004x6-H7 for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 10:58:52 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id AAFD5441506; Sat, 30 Mar 2024 10:58:43 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711810721; bh=P1DGJ8aMWNYPARIfB+KpIiHJqtgrioIXFBM5CiRbo4w=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=em2LDzB5CYfPFTvTSBFwv+5F3DKRHp1Ib1gT0r8Cy5p5IypbqQkYBBypRzk7RfSyR i/GW9w1oZAmH6HUTAAsedFb90yVuNb1gJZT8D3bSPLHfztXlKwWkd6y07Oq6ju9d/z Ysk1cHZgtiNOS1PPeWvExUA+Bupiyrgqs+xrSxusoHQ5zLVPliWnV+WJTXxI3nFV8r qShktHowq3k5XzOU2vp+v5/U+yCmK4yL33XTb5UWGthvC64VYc7F9xmkTy/uPC6pNc PfCK9LhmCCFnUyJ8cd2NgjrlOofiGO54NJHygfQBJ3zUs8NcO9se5Qv6+3bop0NOe/ ZadhanINy376g== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 7FD21441421; Sat, 30 Mar 2024 10:58:41 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 296221201E7; Sat, 30 Mar 2024 10:58:41 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86cyrcdy80.fsf@HIDDEN> (Eli Zaretskii's message of "Sat, 30 Mar 2024 09:34:39 +0300") Message-ID: <jwv7chjvmho.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> <86cyrcdy80.fsf@HIDDEN> Date: Sat, 30 Mar 2024 10:58:40 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.157 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > If the last point, i.e. the problems caused by limitations on what can > be safely done from modification hooks, is basically the main > advantage, then I think I understand the rationale. That's one of the purposes but not the only one. > Otherwise, the above looks like doing all the job in > after-change-functions, and it is not clear to me how is that better, > since if track-changes-fetch will fetch a series of changes, `track-changes-fetch` will call its function argument only once. If several changes happened since last time, `track-changes.el` will summarize them into a single (BEG END BEFORE). > deciding how to handle them could be much harder than handling them > one by one when each one happens. For example, the BEGIN..END values > no longer reflect the current buffer contents, Indeed, one of the purposes of the proposed API is to allow delaying the handling to a later time, and if you keep individual changes, then it becomes very difficult to make sense of those changes because they refer to buffer states that aren't available any more. That's why `track-changes.el` bundles those into a single (BEG END BEFORE) change, which makes sure BEG/END are currently-valid buffer positions and thus easy to use. The previous buffer state is not directly available but can be easily reconstructed from (BEG END BEFORE). > Also, all of your examples seem to have the signal function just call > track-changes-fetch and do almost nothing else, so I wonder why we > need a separate function for that, It gives more freedom to the clients. For example it would allow `eglot.el` to get rid of the `eglot--recent-changes` list of changes: instead of calling `track-changes-fetch` directly from the signal and collect the changes in `eglot--recent-changes`, it would delay the call to `track-changes-fetch` to the idle-timer where we run `eglot--signal-textDocument/didChange` so it would never need to send more than a "single change" to the server. Similarly, it would allow changing the specific moment the signal is called without necessarily moving the moment the changes are performed. This may be necessary in `eglot.el`: there are various places where we check the boolean value of `eglot--recent-changes` to know if we're in sync with the server. In the current code this value because non-nil as soon as a modification is made, whereas with my patch it becomes non-nil only after the end of the command that made the first change. I don't know yet if the difference is important, but if it is, then we'd want to ask `track-changes.el` to send the signal more promptly (there is a FIXME to add such a feature), although we would still want to delay the `track-changes-fetch` so it's called only at the end of the command. > and more specifically what would be a use case where the registered > signal function does NOT call track-changes-fetch, but does something > else, and track-changes-fetch is then called outside of the > signal function. As mentioned, a use-case I imagine are cases where we want to delay the processing of changes to an idle timer. In the current `eglot.el` that idle timer processes a sequence of changes, but for some use cases it may too difficult (for the reasons discussed above: it can be difficult to make sense of earlier changes once they're disconnected from the corresponding buffer state), so they'd instead prefer to call `track-changes-fetch` from the idle timer (and thus let `track-changes.el` combine all those changes). > Finally, the doc string of track-changes-register does not describe > the exact place in the buffer-change sequence where the signal > function will be called, which makes it harder to reason about it. > Will it be called where we now call signal_after_change or somewhere > else? Good point. Currently it's called via `funcall-later` which isn't in `master` but can be thought of as `run-with-timer` with a 0 delay. [ In reality it relies on `pending_funcalls` instead. ] But indeed, I have a FIXME to let the caller request to signal as soon as possible, i.e. directly from the `after-change-functions`. I think it's better to use something like `funcall-later` by default than to signal directly from `after-change-functions` because most coders don't realize that `after-change-functions` can be called thousands of times for a single command (and in normal testing, they'll probably never bump into such a case either). So waiting for the end of the current command (via `funcall-later`) provides a behavior closer to what most naive coders expect, I believe, and will save them from the corner cased they weren't aware of. > And how do you guarantee that the signal function will not be > called again until track-changes-fetch is called? By removing the tracker from `track-changes--clean-trackers` (and re-adding it once `track-changes-fetch` is finished, which is the main reason I make `track-changes-fetch` call a function argument rather than making it return (BEG END CHANGES)). In a later email you wrote: >> (concat (buffer-substring (point-min) beg) >> before >> (buffer-substring end (point-max))) > > But if you get several changes, the above will need to be done in > reverse order, back-to-front, no? No, because you (the client) never get several changes. >> I don't mean to suggest to do that, since it's costly for large >> buffers > Exactly. But if the buffer _is_ large, then what to do? It all depends on the specific cases. E.g. in the case of `eglot.el` we don't need the full content of the buffer before the change. We only really need to know how many newlines were in `before` as well as some kind of length of the last line of `before`. I compute that by inserting `before` into a temp buffer. Note that this is proportional to the size of `before` and not to the total size of the buffer. If we want to do better, I think we'd then need a more complex API where the client can specify more precisely (presumably via some kind of function) what information we need to record about the "before state" (and how to update that information as new changes are performed). I don't have a good idea for what such an API could look like. >> Also, it would fix only the problem of pairing, and not the other ones. > So the main/only problem this mechanism solves is the lack of pairing > between before/after calls to modification hooks? No, the text you cite is saying the opposite: that we don't want to solve only the pairing. I hope/want it to solve all the problems I mentioned: - before and after calls are not necessarily paired. - the beg/end values don't always match. - there can be thousands of calls from within a single command. - these hooks are run at a fairly low-level so there are things they really shouldn't do, such as modify the buffer or wait. - the after call doesn't get enough info to rebuild the before-change state, so some callers need to use both before-c-f and after-c-f (and then deal with the first two points above). I don't claim it solves them all in a perfect way, tho. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 14:09:52 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 10:09:52 2024 Received: from localhost ([127.0.0.1]:45921 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqZP3-0002U0-Qu for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 10:09:52 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:46519) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqZOy-0002Tg-Bv for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 10:09:48 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id D65084413F2; Sat, 30 Mar 2024 10:09:35 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711807774; bh=I+8yjTTQyk5OdK+fv8vRlM3c6J99C5fgX5uowxede/k=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=CFCDpo+eUwsx5QKS41cGWB1xQg1L98mG5R9OJ+2ZY5A0QFCrUZDssdUfES08/BxFX vh6L8giqk6TFGklZid0HHKNM2hrkFQfwthcPB+NUTXN/jSvQZEfcPijs/Q+krZFIeP HZdacnPuOiivyoXGpXLKmJtEHN+TwOJd13Te28edAFArc7w2yWQmbio3eMabNpDCvW FHA7BtCL1yOUsS5OdOT5qqJBr9tJ1IEYhS+i64D2oayen6Cixqs3WVezjnVV7H8T1V wG646GlTRjsIzoAJmJxZcsHErkxyr62GDTQ8DBJ5XCRDfFX1Jwv8dCRrrpbYKljvST zRjLdpAT41Q7w== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 53C0B4413C2; Sat, 30 Mar 2024 10:09:34 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id EBE5712077F; Sat, 30 Mar 2024 10:09:33 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <87sf082gku.fsf@localhost> (Ihor Radchenko's message of "Sat, 30 Mar 2024 09:51:13 +0000") Message-ID: <jwvh6gnvncn.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <87sf082gku.fsf@localhost> Date: Sat, 30 Mar 2024 10:09:33 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.173 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org, Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?windows-1252?B?RnLpZOlyaWM=?= Bour <frederic.bour@HIDDEN>, =?windows-1252?B?Sm/jbyBU4XZvcmE=?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN>, Phillip Lord <phillip.lord@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) >> The driving design was: >> >> - Try to provide enough info such that it is possible and easy to >> maintain a copy of the buffer simply by applying the reported changes. >> E.g. for uses such as `eglot.el` or `crdt.el`. >> - Make the API less synchronous: take care of combining small changes >> into larger ones, and let the clients decide when they react to changes. > > Before we discuss the API, may you allow me to raise one critical > concern: bug#65451. Thanks, I wasn't aware of it. Note that bug#65451 would affect `track-changes.el` but not its clients nor its API. Well, I guess it couldn't really insulate its clients from such bugs, but it would be `track-changes.el`s responsibility to detect such problems and pass down to its clients something saying "oops we have a problem, you have to do a full-resync". > If my reading of the patch is correct, your code is relying upon the > buffer changes arriving in the same order the changes are being made. Indeed. > I am skeptical that you can achieve the desired patch goals purely > relying upon before/after-change-functions, without reaching down to C > internals. There's a FIXME for that: ;; FIXME: Try and do some sanity-checks (e.g. looking at `buffer-size'), ;; to detect if/when we somehow missed some changes. All the current non-trivial users of *-change-functions have such sanity checks. They're designed to handle those nasty cases where we have a bug in the C code. I don't claim my new API can magically paper over those bugs. The intention to deal with those bugs is: - When they're detected, call the clients with a special value for `before` which says that we lost track of some change, so the client knows that it may be necessary to do a full resync. Luckily for many/most clients a full-resync is only a performance problem (e.g. having to reparse the whole file), tho for some (like lentic) I suspect it can result in an actual loss of information. - Try and fix them, of course. Alan has done a great job in the past fixing many of them (tho apparently still not all). [ And also a great job of convincing me that we *can* and thus *should* fix them. ] IOW, no magic bullet: the clients would still have to somehow handle such a "full-resync"s. The main advantage would be that the job of sanity-checking would be taken care of by `track-changes.el` and the clients would have to check only `before` for a special value to indicate a problem. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 13:39:36 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 09:39:36 2024 Received: from localhost ([127.0.0.1]:44232 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqYvn-0000Qc-Mc for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:39:36 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:7589) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqYvl-0000QP-IS for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:39:34 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 55FE210004C; Sat, 30 Mar 2024 09:39:25 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711805964; bh=d545QNFiFMZl8t0CHYjAOALXi3WjXLqTBerIP/ljxf4=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=V2wgVtIFBRNPk4yzG2094hC7tjhxbHDUzFLyB12RpizS9Dvkygn7MsOle6GkDGazi Qlzd3l+Lnx5vtqBZjgYq9KYTCpg9//JiDA/zIAgTCA+NPnxDHlEDK4GQk3g7wNJhmQ r8wgvw3MGh/bm1s0TcAK8BU6apHev4Wbl04BKcqeNTPWbHd04oEe8BhXNmZDfOzaAb a2+DJFFunetR7KwxjB44qqS+sG9EKUfTreGue/xtR1E6lnXXylKCYdriqL2aNxGxds I9bLLpuIIlo0BwvO+cSchwZzHAypV9C1kBFemgWwYG4uUjVRW+0PFeT6CjdYOvNi3l 9ukf3ngNd3rkQ== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 67889100046; Sat, 30 Mar 2024 09:39:24 -0400 (EDT) Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 194A812016C; Sat, 30 Mar 2024 09:39:24 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: phillip.lord@HIDDEN Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <6081fabd1e9e701b1b26848fbe0e403d@HIDDEN> (phillip lord's message of "Sat, 30 Mar 2024 08:06:03 -0400") Message-ID: <jwvsf07vo6j.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> <6081fabd1e9e701b1b26848fbe0e403d@HIDDEN> Date: Sat, 30 Mar 2024 09:39:22 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL 0.000 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org, Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?windows-1252?B?RnLpZOlyaWM=?= Bour <frederic.bour@HIDDEN>, =?windows-1252?B?Sm/jbyBU4XZvcmE=?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > Ah, yes, you are correct, I had missed that one. As you note, it would > be costly, especially because if you wanted to do anything with that > data, you would probably end up dumping it into a temp buffer. That's indeed what I end up doing in the `eglot.el` case. But if the API wants to tackle the performance issue of combining many small changes into a larger one, I don't know how we can do better. Another idea I considered was to keep a whole buffer as "previous contents" (instead of a string that covers the modified area). In some cases it would be more efficient, but the common case would be a lot more costly. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 13:32:13 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 09:32:13 2024 Received: from localhost ([127.0.0.1]:44215 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqYof-0008Ot-8W for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:32:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:48768) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqYoc-0008ON-Kc for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:32:12 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqYoS-0002CF-HS; Sat, 30 Mar 2024 09:32:00 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=rJNQWJE2QusLttobzi5Ikj72ifkE6XXcINgXyUtvyVs=; b=j/RdnMYnfnwj vT1dgV9cKl9UplMckdczuVP8Ry557uQvEBD/8V2d89ml/AaoLmyA2voQ+fYWw00qdpUf+jXZnyV7H m1tkS2n0t3kRLyF9MfWqzlxckjUbnf/SJjM86X2iQ0XyD06HUMtlT4NTxq6rc43DbCE+fAeFuryHi T9IeIGqwAknwF0Nb+yo1KUtm4ny9RNdOqBFPRadEwstVmg1N0l1FQnAXJdas/90iEtF095xjQEgta PGj4xFfJavUILgLKz21S+o1H5KNNF5sJ1DRswtSD9ThipZzCQ+mCCMV4kTmv3IrDwfEmS355hcMLu WbJh0jiS+mu8Ale3AHfTkg==; Date: Sat, 30 Mar 2024 16:31:56 +0300 Message-Id: <86il13dewj.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> In-Reply-To: <87frw77t7g.fsf@localhost> (message from Ihor Radchenko on Sat, 30 Mar 2024 13:19:31 +0000) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <87sf082gku.fsf@localhost> <86v853dguu.fsf@HIDDEN> <87frw77t7g.fsf@localhost> X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, phillip.lord@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, monnier@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > From: Ihor Radchenko <yantar92@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, casouri@HIDDEN, yantar92@HIDDEN, > qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, > mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, > alan.zimm@HIDDEN, monnier@HIDDEN, phillip.lord@HIDDEN > Date: Sat, 30 Mar 2024 13:19:31 +0000 > > Looking at the implementation of `track-changes--before/after', I can > see that it is updating the BEFORE string and these updates implicitly > assume that the changes arrive in order - which is not true in some edge > cases described in the bug report. So I guess you have detected a bug in the implementation before it was even finalized and installed.
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 13:19:36 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 09:19:36 2024 Received: from localhost ([127.0.0.1]:44197 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqYcS-0007gt-0Z for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:19:36 -0400 Received: from mout02.posteo.de ([185.67.36.66]:40497) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rqYcO-0007g3-JX for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 09:19:34 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id BECD4240109 for <70077 <at> debbugs.gnu.org>; Sat, 30 Mar 2024 14:19:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1711804764; bh=18D/xibYEnioWpFFPVKAGiYFyDdZCmS+JhopXNpS6VE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=dq7/JOhghVP5rfmVktZAiGB44wlFGtpMI93elaCyS/Ik5ZA7Uuw8J1jvjtBm8yPSs khz+aVeX+4PnYs3jAs9W7I7j11jx8RpYUrRUhMEUFwvF3FqyQwCsNfYu6m74D1x+Ia iWkbrIt37w3Aaq3slsrX4K0lKZvaHZuo48qKI2I9tJJyuubG0XkR+gsvXVgn5Q5Z43 4gfHqTdwJ/oKRHV9zROh7TCvlLmu482AK/m+830OWC1ngAn1uz/ENJLcAkVGGkE45a Kqw5DhQZvJTHOk+Jy4M+TEROrgITZP+Yq6fE65eFjDrPnqxtyF7UBeg49P75Xte84t QsK5/LTQ5TK+w== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V6HtL0Ysbz6trs; Sat, 30 Mar 2024 14:19:21 +0100 (CET) From: Ihor Radchenko <yantar92@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86v853dguu.fsf@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <87sf082gku.fsf@localhost> <86v853dguu.fsf@HIDDEN> Date: Sat, 30 Mar 2024 13:19:31 +0000 Message-ID: <87frw77t7g.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, phillip.lord@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, monnier@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Eli Zaretskii <eliz@HIDDEN> writes: > In any case, who and where said the changes will be fetched by > track-changes-fetch must be in the order they were made? why is the > order at all significant? I have concerns that the following API promise can be fulfilled: (defun track-changes-fetch (id func) "Fetch the pending changes. ID is the tracker ID returned by a previous `track-changes-register'. FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) where BEGIN..END delimit the region that was changed since the last time `track-changes-fetch' was called and BEFORE is a string containing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the previous content of that region. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Looking at the implementation of `track-changes--before/after', I can see that it is updating the BEFORE string and these updates implicitly assume that the changes arrive in order - which is not true in some edge cases described in the bug report. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 12:50:07 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 08:50:06 2024 Received: from localhost ([127.0.0.1]:44167 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqY9t-0006Dj-1j for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 08:50:06 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:35182) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqY9q-0006Cr-KN for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 08:50:04 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqY9e-0002Wk-Tk; Sat, 30 Mar 2024 08:49:50 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=jRUo7066nSNub89wxZGo6KS6XwyIoZIZVE6u3xzTo+c=; b=cGRhskEQCLCz dM1YU5AatkwF5J/kAf9WjSVae9MN4D6NgyBihPi2vz4eetiLmwoaS1G/8EGXFYjUj8r9PTXoZZJST YHXhFGFXspSzAtROZR41oQQ5sE0n3TXJjozI7ye5IXgwTmk+Nh4AtPt4S/VXaLkx80NFJ9/OnCEiu wBfYMaJPWGGANds2xwSuPWpL6bsPdVYYpxYY3meoNHRaPhUg9GlPP5gCmKrBMBB+RWw09e0busBwK rxrJkR4EuI/ZlZ+Lg4CxS1pdlPkqhLZ2wcWAM8T7u9o88bo+3sIeNud5pmSKOyqkS3ViADJJypF64 FyC1VZrVsb25pkd947F2/w==; Date: Sat, 30 Mar 2024 15:49:45 +0300 Message-Id: <86v853dguu.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Ihor Radchenko <yantar92@HIDDEN> In-Reply-To: <87sf082gku.fsf@localhost> (message from Ihor Radchenko on Sat, 30 Mar 2024 09:51:13 +0000) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <87sf082gku.fsf@localhost> X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, phillip.lord@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, monnier@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > Cc: casouri@HIDDEN, yantar92@HIDDEN, qhong@HIDDEN, > frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, > acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, > monnier@HIDDEN, phillip.lord@HIDDEN > From: Ihor Radchenko <yantar92@HIDDEN> > Date: Sat, 30 Mar 2024 09:51:13 +0000 > > Before we discuss the API, may you allow me to raise one critical > concern: bug#65451. > > If my reading of the patch is correct, your code is relying upon the > buffer changes arriving in the same order the changes are being made. > However, it is not always the case, as demonstrated in the linked bug > report. That bug report is about after-change-functions. Since Stefan didn't yet describe where will the changes be recorded, it doesn't necessarily follow that your worries are justified. They could be, of course, but we should decide that after we hear the details. In any case, who and where said the changes will be fetched by track-changes-fetch must be in the order they were made? why is the order at all significant?
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 12:06:19 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 08:06:19 2024 Received: from localhost ([127.0.0.1]:44127 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqXTX-00042L-5n for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 08:06:19 -0400 Received: from cloud103.planethippo.com ([78.129.190.68]:35612) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <phillip.lord@HIDDEN>) id 1rqXTR-00041d-Gk for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 08:06:18 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=russet.org.uk; s=default; h=Content-Transfer-Encoding:Content-Type: Message-ID:References:In-Reply-To:Subject:Cc:To:From:Date:MIME-Version:Sender :Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=/iaqUNq9u594bqzNvPbwgEfgIFIkxnHjUwAGwN54tTc=; b=KDbVD7g4wxOqCr/WLU2r3ajabg oAEY4sB+rhOsKUietK94vpVJrFDIGQpLrH908XxQsHNzm2CgqsPUgn3cz4Kp2/e8RrfQXGi614g/i HonVgKixLxMRtTHdrqIFwwRiX3CZBM5UORNfeF+9WF27+6Ey7lpqrYijN4+lD2X3KAgS2E8pNhq8Z /3KnLh0+yK/bwPKFCCjgg7FNx8ZYevWTn1bNDpn08wi/3doPr7UrT/dd0ytqbHNeepAK3dHigkjSt 9PYis+ncZNZ+jIdrP4fhWiaTBZr6jmdz+dsl1itngrrJ5hHNrGTxH1lVWSNKkW//zO7zldvv7Du1L zGDJyvpw==; Received: from [::1] (port=45052 helo=cloud103.planethippo.com) by cloud103.planethippo.com with esmtpa (Exim 4.96.2) (envelope-from <phillip.lord@HIDDEN>) id 1rqXTA-000cwf-2M; Sat, 30 Mar 2024 08:06:04 -0400 MIME-Version: 1.0 Date: Sat, 30 Mar 2024 08:06:03 -0400 From: phillip.lord@HIDDEN To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> User-Agent: Roundcube Webmail/1.6.0 Message-ID: <6081fabd1e9e701b1b26848fbe0e403d@HIDDEN> X-Sender: phillip.lord@HIDDEN Content-Type: text/plain; charset=US-ASCII; format=flowed Content-Transfer-Encoding: 7bit X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - cloud103.planethippo.com X-AntiAbuse: Original Domain - debbugs.gnu.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - russet.org.uk X-Get-Message-Sender-Via: cloud103.planethippo.com: authenticated_id: phillip.lord@HIDDEN X-Authenticated-Sender: cloud103.planethippo.com: phillip.lord@HIDDEN X-Source: X-Source-Args: X-Source-Dir: X-Spam-Score: 3.0 (+++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: On 2024-03-29 18:59, Stefan Monnier wrote: >> If I remember correctly, I think this wouldn't be enough for my >> use. You keep two buffers in sync, you have to use >> before-change-function -- it is o [...] Content analysis details: (3.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Debbugs-Envelope-To: 70077 Cc: Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org, Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Bour?= <frederic.bour@HIDDEN>, =?UTF-8?Q?Jo=C3=A3o_T=C3=A1vora?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: 2.0 (++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: On 2024-03-29 18:59, Stefan Monnier wrote: >> If I remember correctly, I think this wouldn't be enough for my >> use. You keep two buffers in sync, you have to use >> before-change-function -- it is o [...] Content analysis details: (2.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager On 2024-03-29 18:59, Stefan Monnier wrote: >> If I remember correctly, I think this wouldn't be enough for my >> use. You keep two buffers in sync, you have to use >> before-change-function -- it is only before any change that the two >> buffers are guaranteed to be in sync and it is this that allows you to >> work out what the `start' and `end' positions mean in the copied >> buffer. Afterward, you cannot work out what the end position because >> you don't know if the change is a change, insertion, deletion or both. > > I believe the API I propose does provide that information: you can > recover the state of the buffer before the change (or more > specifically, > the state of the buffer as of the last time you called > track-changes-fetch) from the BEG/END/BEFORE arguments as follows: > > (concat (buffer-substring (point-min) beg) > before > (buffer-substring end (point-max))) > > I don't mean to suggest to do that, since it's costly for large > buffers, but to illustrate that the information is properly preserved. Ah, yes, you are correct, I had missed that one. As you note, it would be costly, especially because if you wanted to do anything with that data, you would probably end up dumping it into a temp buffer. >> Last time I checked, I did find relatively few primitives that were >> guilty >> of being inconsistent -- in the case of `subst-char-in-region', it >> returned >> the maximal area of effect before the and the minimal area of effect >> after. Would it not be easier to fix these? > > [ IIRC `revert-buffer` has a similar behavior, and in that case the > difference can be large since the "before" covers the whole buffer. > ] > > Also, it would fix only the problem of pairing, and not the other ones. Understood. It would be interesting to know how many primitives cause issues though. Phil
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 09:51:18 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 05:51:18 2024 Received: from localhost ([127.0.0.1]:44016 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqVMr-0002gX-N8 for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 05:51:18 -0400 Received: from mout02.posteo.de ([185.67.36.66]:50907) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rqVMp-0002fn-6S for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 05:51:16 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id 129D6240105 for <70077 <at> debbugs.gnu.org>; Sat, 30 Mar 2024 10:51:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1711792267; bh=kYgyL7i/Xnjy/Fx8yCO1vgSbFmBI3BXPCbHoWgqN3iI=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=OV1Ig1sw0Y66ZKpekqb6U4hCHl61Bb9rcYTw+syJP7wWeQVWAt8ExvDj1mbbWFNsv gnyi8/1U4/af2JcuLhzT1sKDlUkJkppdbeaKgVAupqzgXRb7wCVsTfdS3SP8GRw+rr mTjEhLiqgIpFq213saDGzgDH8SP3+wDBim/ILRSVGDGu5bvLlEAVF5rn9jcb20u+b1 lRO5y3nGebpNdvksvN8SGNR7QxhoRybO+3Kp23JDZcb8DIuuRBs2PhEddM0RGF8HXZ 95npXwt8jLhBNlM9LIN9NQoi2X7ezqVCgx5uY3t6ZFbSIGLS4+TL80LOxCm6EY4OsK iuqJQkUTMUpBg== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V6CG00yNGz9rxG; Sat, 30 Mar 2024 10:51:03 +0100 (CET) From: Ihor Radchenko <yantar92@HIDDEN> To: "Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvle615806.fsf@HIDDEN> References: <jwvle615806.fsf@HIDDEN> Date: Sat, 30 Mar 2024 09:51:13 +0000 Message-ID: <87sf082gku.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: Yuan Fu <casouri@HIDDEN>, 70077 <at> debbugs.gnu.org, Ihor Radchenko <yantar92@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?utf-8?B?RnLDqWTDqXJpYw==?= Bour <frederic.bour@HIDDEN>, =?utf-8?B?Sm/Do28gVMOhdm9yYQ==?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN>, monnier@HIDDEN, Phillip Lord <phillip.lord@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> writes: > The driving design was: > > - Try to provide enough info such that it is possible and easy to > maintain a copy of the buffer simply by applying the reported changes. > E.g. for uses such as `eglot.el` or `crdt.el`. > - Make the API less synchronous: take care of combining small changes > into larger ones, and let the clients decide when they react to changes. Before we discuss the API, may you allow me to raise one critical concern: bug#65451. If my reading of the patch is correct, your code is relying upon the buffer changes arriving in the same order the changes are being made. However, it is not always the case, as demonstrated in the linked bug report. I am skeptical that you can achieve the desired patch goals purely relying upon before/after-change-functions, without reaching down to C internals. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at submit) by debbugs.gnu.org; 30 Mar 2024 09:51:40 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 05:51:39 2024 Received: from localhost ([127.0.0.1]:44020 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqVN3-0002gv-5W for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 05:51:39 -0400 Received: from lists.gnu.org ([2001:470:142::17]:44746) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <yantar92@HIDDEN>) id 1rqVN1-0002gi-0S for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 05:51:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <yantar92@HIDDEN>) id 1rqVMt-0001no-0y for bug-gnu-emacs@HIDDEN; Sat, 30 Mar 2024 05:51:19 -0400 Received: from mout01.posteo.de ([185.67.36.65]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <yantar92@HIDDEN>) id 1rqVMn-0006Ch-UE for bug-gnu-emacs@HIDDEN; Sat, 30 Mar 2024 05:51:17 -0400 Received: from submission (posteo.de [185.67.36.169]) by mout01.posteo.de (Postfix) with ESMTPS id E0A2B24002D for <bug-gnu-emacs@HIDDEN>; Sat, 30 Mar 2024 10:51:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1711792266; bh=kYgyL7i/Xnjy/Fx8yCO1vgSbFmBI3BXPCbHoWgqN3iI=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type: From; b=rDCSGP6/l1mL3bMtWWUnGOFi6EHUmKt5R5aLvVsw6foCWV4Ql2Il3N3YgvLub+kV3 gQDGLVtbHWWfjWq7TFMPF1OS1fFAXbH7TyFLY8q+i6Kc0Psp1xT+pnQgSQetC4Sz1I eCTSHov87m26XscbzDux5fmyjPu6HBwIM6V/W66UTabFRCZxbx95TJgx0haSXxark6 DG7j9TzaRLdJ64pIsRSo/YjFaT5aeqRmwmQ7C+USaQBHjwR611NJtIapQ/KPTvpr05 IURJorJ1/qZSnb/5Xo5vAl481PtQtIfRIWJOmNP5Jsvm6kwiyIu9m8d4QheDTw3VeQ NiD/ZGQMmUrmA== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4V6CG00yNGz9rxG; Sat, 30 Mar 2024 10:51:03 +0100 (CET) From: Ihor Radchenko <yantar92@HIDDEN> To: "Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvle615806.fsf@HIDDEN> References: <jwvle615806.fsf@HIDDEN> Date: Sat, 30 Mar 2024 09:51:13 +0000 Message-ID: <87sf082gku.fsf@localhost> MIME-Version: 1.0 Content-Type: text/plain Received-SPF: pass client-ip=185.67.36.65; envelope-from=yantar92@HIDDEN; helo=mout01.posteo.de X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: 4.0 (++++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> writes: > The driving design was: > > - Try to provide enough info such that it is possible and easy to > maintain a copy of the buffer simply by applying the reported changes. > E.g. for uses such as `eglot. [...] Content analysis details: (4.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients 1.0 SPF_SOFTFAIL SPF: sender does not match SPF record (softfail) -0.0 SPF_HELO_PASS SPF: HELO matches SPF record X-Debbugs-Envelope-To: submit Cc: Yuan Fu <casouri@HIDDEN>, 70077 <at> debbugs.gnu.org, Ihor Radchenko <yantar92@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?utf-8?B?RnLDqWTDqXJpYw==?= Bour <frederic.bour@HIDDEN>, =?utf-8?B?Sm/Do28gVMOhdm9yYQ==?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN>, monnier@HIDDEN, Phillip Lord <phillip.lord@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: 3.0 (+++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> writes: > The driving design was: > > - Try to provide enough info such that it is possible and easy to > maintain a copy of the buffer simply by applying the reported changes. > E.g. for uses such as `eglot. [...] Content analysis details: (3.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients 1.0 SPF_SOFTFAIL SPF: sender does not match SPF record (softfail) -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> writes: > The driving design was: > > - Try to provide enough info such that it is possible and easy to > maintain a copy of the buffer simply by applying the reported changes. > E.g. for uses such as `eglot.el` or `crdt.el`. > - Make the API less synchronous: take care of combining small changes > into larger ones, and let the clients decide when they react to changes. Before we discuss the API, may you allow me to raise one critical concern: bug#65451. If my reading of the patch is correct, your code is relying upon the buffer changes arriving in the same order the changes are being made. However, it is not always the case, as demonstrated in the linked bug report. I am skeptical that you can achieve the desired patch goals purely relying upon before/after-change-functions, without reaching down to C internals. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 06:46:35 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 02:46:35 2024 Received: from localhost ([127.0.0.1]:43804 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqSU6-0001xC-Uk for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 02:46:35 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:45126) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqSU3-0001wy-VE for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 02:46:33 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqSTq-0005zL-VJ; Sat, 30 Mar 2024 02:46:18 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From: Date; bh=eM/X+2XyayQZ85/xhQiFHwO1OZXdHxTi+afP3z1LxM8=; b=sX1m+k5euJ055Q8vlmP+ ACZu1bEILh82zeASRx3+pASQ5awOkiwZ5Mmorv4TPcEmmEPqoCrcqZC/OXLpDuB8MKCnxgF6Mvk/F ECjUezd4laitkubPDC1vEDXG13Vf6jSbF6z3FYKGepVh/8woPAZE3TK8hZVJoPvElM4eCQcjmC0mK RPn+dl1GmM0EB1zZVQQQfaQwssN5hP1lDR3leLUhBEw/pAMvZWnFx6d8Y1PCQrJWUQzaaG6VaV3PL gjdxHCs2WwMnhrKdNk+Xb7ME/H2HvbJJ1m9ZZo9i3/0kNg8y6a2w8IDwMG6p8J+iuAzSX+lRb89iU xpZwrL18+K0H4g==; Date: Sat, 30 Mar 2024 09:46:15 +0300 Message-Id: <86bk6wdxoo.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> (bug-gnu-emacs@HIDDEN) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> MIME-version: 1.0 Content-type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > Cc: Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org, > Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, > Frédéric Bour <frederic.bour@HIDDEN>, > João Távora <joaotavora@HIDDEN>, > Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, > Stephen Leake <stephen_leake@HIDDEN>, > Alan Zimmerman <alan.zimm@HIDDEN> > Date: Fri, 29 Mar 2024 18:59:38 -0400 > From: Stefan Monnier via "Bug reports for GNU Emacs, > the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> > > > If I remember correctly, I think this wouldn't be enough for my > > use. You keep two buffers in sync, you have to use > > before-change-function -- it is only before any change that the two > > buffers are guaranteed to be in sync and it is this that allows you to > > work out what the `start' and `end' positions mean in the copied > > buffer. Afterward, you cannot work out what the end position because > > you don't know if the change is a change, insertion, deletion or both. > > I believe the API I propose does provide that information: you can > recover the state of the buffer before the change (or more specifically, > the state of the buffer as of the last time you called > track-changes-fetch) from the BEG/END/BEFORE arguments as follows: > > (concat (buffer-substring (point-min) beg) > before > (buffer-substring end (point-max))) But if you get several changes, the above will need to be done in reverse order, back-to-front, no? And before you do that, you cannot really handle the changes, right? > I don't mean to suggest to do that, since it's costly for large > buffers Exactly. But if the buffer _is_ large, then what to do? > Also, it would fix only the problem of pairing, and not the other ones. So the main/only problem this mechanism solves is the lack of pairing between before/after calls to modification hooks?
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 06:35:00 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 02:35:00 2024 Received: from localhost ([127.0.0.1]:43793 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqSIt-0001NT-MI for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 02:35:00 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:56080) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqSIr-0001Mv-1e for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 02:34:58 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqSId-0000B0-56; Sat, 30 Mar 2024 02:34:43 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=TCzeSQQItuKpPzmPouypKkrrX394edArCH9odaiuJkA=; b=cyDNHCrOXn6d WkLONLPweTEMKrvuFvB4exZjGlR+jNNNkhIBjAuOpw3wV+fx3L9f2C8q+kN6kh4TCLQbJTiPlVdFB +HhAfPh3Ch5Ri0HX4Yd8xjKDaaw7fz5mYr2uhG6V4TLV7vtGm0AdgjxOoiZF9DxSdf09dRXzgTG35 axP0saa27hOY0/oNKhSHCQwRrK8EkY8goc9eTtUENQLR+1ZmkUh8ApTD7XBgdnfFX51Id1USNcpzT mzO+odezijEpYaeChsid1UY0gL1RS6XEwA9aRgIG5GKa3gA6BM8gFVkvtXKAvmNPqyvvho2oZ42Se vE/3MCDIOQNXpUpmaip2YA==; Date: Sat, 30 Mar 2024 09:34:39 +0300 Message-Id: <86cyrcdy80.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> (message from Stefan Monnier on Fri, 29 Mar 2024 14:53:41 -0400) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > From: Stefan Monnier <monnier@HIDDEN> > Cc: 70077 <at> debbugs.gnu.org, mail@HIDDEN, yantar92@HIDDEN, > acm@HIDDEN, joaotavora@HIDDEN, alan.zimm@HIDDEN, > frederic.bour@HIDDEN, phillip.lord@HIDDEN, > stephen_leake@HIDDEN, casouri@HIDDEN, qhong@HIDDEN > Date: Fri, 29 Mar 2024 14:53:41 -0400 > > > I cannot imagine how applications would use these APIs. I'm probably > > missing something, org the above documentation does. Can you show > > some real-life examples? > > Haven't written real code for it yes, no. > The best I can offer is the sample code in the file: > > (defvar my-foo--change-tracker nil) > (define-minor-mode my-foo-mode > "Fooing like there's no tomorrow." > (if (null my-foo-mode) > (when my-foo--change-tracker > (track-changes-unregister my-foo--change-tracker) > (setq my-foo--change-tracker nil)) > (unless my-foo--change-tracker > (setq my-foo--change-tracker > (track-changes-register > (lambda () > (track-changes-fetch > my-foo--change-tracker > (lambda (beg end before) > ..DO THE THING..)))))))) > > Where "DO THE THING" is run similarly to what would happen in an > `after-change-functions`, except: > > - BEFORE is a string holding the content of what was in BEG..END > instead of being limited to its length. > - It's run at most once per command, so there's no performance worries. > - It's run "outside" of the modifications themselves, > so `inhibit-modification-hooks` is nil and the code can wait, modify > the buffer, or do any kind of crazy things. Thanks. I understand the last point, but that still doesn't explain enough for me to see the light. (I've also looked at the two real-life uses you posted, and it didn't help, probably because the important ideas drowned in the sea of modifications to code I'm not familiar with well enough to understand what's important and what isn't.) If the last point, i.e. the problems caused by limitations on what can be safely done from modification hooks, is basically the main advantage, then I think I understand the rationale. Otherwise, the above looks like doing all the job in after-change-functions, and it is not clear to me how is that better, since if track-changes-fetch will fetch a series of changes, deciding how to handle them could be much harder than handling them one by one when each one happens. For example, the BEGIN..END values no longer reflect the current buffer contents, and each fetched change refers to a different content of the buffer (so the same values of BEG..END don't necessarily mean the same places in the buffer). I think the need for the eglot--virtual-pos-to-lsp-position function in one of your examples is the tip of that iceberg. Also, all of your examples seem to have the signal function just call track-changes-fetch and do almost nothing else, so I wonder why we need a separate function for that, and more specifically what would be a use case where the registered signal function does NOT call track-changes-fetch, but does something else, and track-changes-fetch is then called outside of the signal function. Finally, the doc string of track-changes-register does not describe the exact place in the buffer-change sequence where the signal function will be called, which makes it harder to reason about it. Will it be called where we now call signal_after_change or somewhere else? And how do you guarantee that the signal function will not be called again until track-changes-fetch is called?
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 05:09:56 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 30 01:09:56 2024 Received: from localhost ([127.0.0.1]:43719 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqQyZ-0002LR-Cd for submit <at> debbugs.gnu.org; Sat, 30 Mar 2024 01:09:56 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:59441) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqQyW-0002LD-K8 for 70077 <at> debbugs.gnu.org; Sat, 30 Mar 2024 01:09:53 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id A19931000DD; Sat, 30 Mar 2024 01:09:44 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711775383; bh=oPJpqrXJloZ+Vj4qPyoHlTphSd1+E1YAC3BhOuiPyEE=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=JPZUl3tTNIaiVEUoULQkFrWwld2rfnexERFYiYM2yFcZwEqworokVaioxP0ulJwu2 dMD/9kLTrlDVYqPX7G3cglRGMKDGXdQyxNvBRge5R7MFKyAoVZNuTJfEhnb9ANEgWe GDaQtqfb5iUJGXaK7XNhfHQFTr3T7QdcQURwmmhwKtJVnc+aXxFt6Ampd0tgJLQehA LwCnynhj49PqnhSRJH7wru9Dxpmgs8PPXKG6IRHrB/PQ03KbEuNTmb4KxHjMfdPU2B BXmDcoc3t0YDEl/ZyJzqb7VdPfXeqNSg/kQLRYcsnpJz5S0nrPWjWUCMgaAj3Ud6xU jedR4aQWpusIA== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 460A210004A; Sat, 30 Mar 2024 01:09:43 -0400 (EDT) Received: from pastel (104-222-113-60.cpe.teksavvy.com [104.222.113.60]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id E3186120642; Sat, 30 Mar 2024 01:09:42 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwv4jcowguh.fsf-monnier+emacs@HIDDEN> (Stefan Monnier's message of "Fri, 29 Mar 2024 23:17:09 -0400") Message-ID: <jwvy1a0uxd7.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> <jwv4jcowguh.fsf-monnier+emacs@HIDDEN> Date: Sat, 30 Mar 2024 01:09:41 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.162 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) --=-=-= Content-Type: text/plain > Here's my first attempt at a real-life use. And here's a second attempt, which is a tentative patch for `eglot.el`. This one does make use of the `before` argument, so it exercises more the API. The `eglot--virtual-pos-to-lsp-position` is not completely satisfactory, since to compute the LSP position of the end of the chunk before it was modified, I end up creating a temp buffer to insert the part of the text that changed (to count its line+column, which is much easier in a buffer than in a string). That kinda sucks performancewise, but we do it at most once per command rather than once per buffer-modification, so it should be lost in the noise. The upside is that we're insulated from the quirks of the after/before-change-functions evidenced by the copious comments referring to various bug reports. Stefan --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=eglot.patch diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 7d2f1a55165..d2268cea940 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -110,6 +110,7 @@ (require 'text-property-search nil t) (require 'diff-mode) (require 'diff) +(require 'track-changes) ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite @@ -1732,6 +1733,9 @@ eglot-utf-16-linepos "Calculate number of UTF-16 code units from position given by LBP. LBP defaults to `eglot--bol'." (/ (- (length (encode-coding-region (or lbp (eglot--bol)) + ;; FIXME: How could `point' ever be + ;; larger than `point-max' (sounds like + ;; a bug in Emacs). ;; Fix github#860 (min (point) (point-max)) 'utf-16 t)) 2) @@ -1749,6 +1753,24 @@ eglot--pos-to-lsp-position :character (progn (when pos (goto-char pos)) (funcall eglot-current-linepos-function))))) +(defun eglot--virtual-pos-to-lsp-position (pos string) + "Return the LSP position at the end of STRING if it were inserted at POS." + (eglot--widening + (goto-char pos) + (forward-char 0) + ;; LSP line is zero-origin; Emacs is one-origin. + (let ((posline (1- (line-number-at-pos nil t))) + (linebeg (buffer-substring (point) pos)) + (colfun eglot-current-linepos-function)) + ;; Use a temp buffer because: + ;; - I don't know of a fast way to count newlines in a string. + ;; - We currently don't have `eglot-current-linepos-function' for strings. + (with-temp-buffer + (insert linebeg string) + (goto-char (point-max)) + (list :line (+ posline (1- (line-number-at-pos nil t))) + :character (funcall colfun)))))) + (defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos "Function to move to a position within a line reported by the LSP server. @@ -1946,6 +1968,8 @@ eglot-managed-mode-hook "A hook run by Eglot after it started/stopped managing a buffer. Use `eglot-managed-p' to determine if current buffer is managed.") +(defvar-local eglot--track-changes nil) + (define-minor-mode eglot--managed-mode "Mode for source buffers managed by some Eglot project." :init-value nil :lighter nil :keymap eglot-mode-map @@ -1959,8 +1983,9 @@ eglot--managed-mode ("utf-8" (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-linepos) (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-8-linepos))) - (add-hook 'after-change-functions #'eglot--after-change nil t) - (add-hook 'before-change-functions #'eglot--before-change nil t) + (unless eglot--track-changes + (setq eglot--track-changes + (track-changes-register #'eglot--track-changes-signal))) (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) ;; Prepend "didClose" to the hook after the "nonoff", so it will run first (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil t) @@ -1994,8 +2019,8 @@ eglot--managed-mode (eldoc-mode 1)) (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server)))) (t - (remove-hook 'after-change-functions #'eglot--after-change t) - (remove-hook 'before-change-functions #'eglot--before-change t) + (when eglot--track-changes + (track-changes-unregister eglot--track-changes)) (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) (remove-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose t) (remove-hook 'before-revert-hook #'eglot--signal-textDocument/didClose t) @@ -2564,54 +2589,20 @@ jsonrpc-connection-ready-p (defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.") -(defun eglot--before-change (beg end) - "Hook onto `before-change-functions' with BEG and END." - (when (listp eglot--recent-changes) - ;; Records BEG and END, crucially convert them into LSP - ;; (line/char) positions before that information is lost (because - ;; the after-change thingy doesn't know if newlines were - ;; deleted/added). Also record markers of BEG and END - ;; (github#259) - (push `(,(eglot--pos-to-lsp-position beg) - ,(eglot--pos-to-lsp-position end) - (,beg . ,(copy-marker beg nil)) - (,end . ,(copy-marker end t))) - eglot--recent-changes))) - (defvar eglot--document-changed-hook '(eglot--signal-textDocument/didChange) "Internal hook for doing things when the document changes.") -(defun eglot--after-change (beg end pre-change-length) - "Hook onto `after-change-functions'. -Records BEG, END and PRE-CHANGE-LENGTH locally." +(defun eglot--track-changes-signal (id) (cl-incf eglot--versioned-identifier) - (pcase (car-safe eglot--recent-changes) - (`(,lsp-beg ,lsp-end - (,b-beg . ,b-beg-marker) - (,b-end . ,b-end-marker)) - ;; github#259 and github#367: with `capitalize-word' & friends, - ;; `before-change-functions' records the whole word's `b-beg' and - ;; `b-end'. Similarly, when `fill-paragraph' coalesces two - ;; lines, `b-beg' and `b-end' mark end of first line and end of - ;; second line, resp. In both situations, `beg' and `end' - ;; received here seemingly contradict that: they will differ by 1 - ;; and encompass the capitalized character or, in the coalescing - ;; case, the replacement of the newline with a space. We keep - ;; both markers and positions to detect and correct this. In - ;; this specific case, we ignore `beg', `len' and - ;; `pre-change-len' and send richer information about the region - ;; from the markers. I've also experimented with doing this - ;; unconditionally but it seems to break when newlines are added. - (if (and (= b-end b-end-marker) (= b-beg b-beg-marker) - (or (/= beg b-beg) (/= end b-end))) - (setcar eglot--recent-changes - `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker) - ,(buffer-substring-no-properties b-beg-marker - b-end-marker))) - (setcar eglot--recent-changes - `(,lsp-beg ,lsp-end ,pre-change-length - ,(buffer-substring-no-properties beg end))))) - (_ (setf eglot--recent-changes :emacs-messup))) + (track-changes-fetch + id (lambda (beg end before) + (if (stringp before) + (push `(,(eglot--pos-to-lsp-position beg) + ,(eglot--virtual-pos-to-lsp-position beg before) + ,(length before) + ,(buffer-substring-no-properties beg end)) + eglot--recent-changes) + (setf eglot--recent-changes :emacs-messup)))) (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) (let ((buf (current-buffer))) (setq eglot--change-idle-timer @@ -2741,12 +2732,6 @@ eglot--signal-textDocument/didChange (buffer-substring-no-properties (point-min) (point-max))))) (cl-loop for (beg end len text) in (reverse eglot--recent-changes) - ;; github#259: `capitalize-word' and commands based - ;; on `casify_region' will cause multiple duplicate - ;; empty entries in `eglot--before-change' calls - ;; without an `eglot--after-change' reciprocal. - ;; Weed them out here. - when (numberp len) vconcat `[,(list :range `(:start ,beg :end ,end) :rangeLength len :text text)])))) (setq eglot--recent-changes nil) --=-=-=--
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 30 Mar 2024 03:17:23 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 23:17:23 2024 Received: from localhost ([127.0.0.1]:43692 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqPDe-00053G-Nm for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 23:17:23 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:49544) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqPDd-000534-8V for 70077 <at> debbugs.gnu.org; Fri, 29 Mar 2024 23:17:21 -0400 Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 9AE71803C1; Fri, 29 Mar 2024 23:17:13 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711768632; bh=l5FVBO2ixyFlfCyU6ciEHZJs/xQHsI14as4BFV3BnC4=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=HoDRsMA7Q8W8334kRhUq9qeeCAB7Pl2V+daqGvnllsYMBuq08kiw6c2ydYBIHrEtI InXI2LNsnjC4slEGAV5Hyc4Hci1PKyI+E3Hji/5EiX5FKpO0Aejpaqu/HqdCqPcaPo PjezL+IaioA+BrJLZOeIJj0023d+G99ZDV9ekCf2cxN1utEGF6oAT9xZB3cDzXqNYG XSBfBuKkqnNWRtoW7nNyviicX3vOGpq2lIEixDh1e5b6Puj/81R/0CYsJecyRK4kLY qHcByzzhbB8atuCvueZ3o03NaMe4JR2D/6fbffU7EDhyFL8AlaTscV79bAyPqWi4ON klWzmum4u17Lw== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 3C04B8092A; Fri, 29 Mar 2024 23:17:12 -0400 (EDT) Received: from pastel (104-222-113-60.cpe.teksavvy.com [104.222.113.60]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id A3251120857; Fri, 29 Mar 2024 23:17:11 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86frw8ewk9.fsf@HIDDEN> (Eli Zaretskii's message of "Fri, 29 Mar 2024 21:12:54 +0300") Message-ID: <jwv4jcowguh.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> Date: Fri, 29 Mar 2024 23:17:09 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.002 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) --=-=-= Content-Type: text/plain > I cannot imagine how applications would use these APIs. I'm probably > missing something, org the above documentation does. Can you show > some real-life examples? Here's my first attempt at a real-life use. Note: this example doesn't make use of the full API (it doesn't need the `before` argument). Stefan --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=diff-track-changes.patch diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index d9146ea8cc5..f8c31fb6748 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -53,9 +53,10 @@ ;; - Handle `diff -b' output in context->unified. ;;; Code: +(require 'easy-mmode) +(require 'track-changes) (eval-when-compile (require 'cl-lib)) (eval-when-compile (require 'subr-x)) -(require 'easy-mmode) (autoload 'vc-find-revision "vc") (autoload 'vc-find-revision-no-save "vc") @@ -1441,31 +1441,16 @@ (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max))) nil) -;; It turns out that making changes in the buffer from within an -;; *-change-function is asking for trouble, whereas making them -;; from a post-command-hook doesn't pose much problems -(defvar diff-unhandled-changes nil) -(defun diff-after-change-function (beg end _len) - "Remember to fixup the hunk header. -See `after-change-functions' for the meaning of BEG, END and LEN." - ;; Ignoring changes when inhibit-read-only is set is strictly speaking - ;; incorrect, but it turns out that inhibit-read-only is normally not set - ;; inside editing commands, while it tends to be set when the buffer gets - ;; updated by an async process or by a conversion function, both of which - ;; would rather not be uselessly slowed down by this hook. - (when (and (not undo-in-progress) (not inhibit-read-only)) - (if diff-unhandled-changes - (setq diff-unhandled-changes - (cons (min beg (car diff-unhandled-changes)) - (max end (cdr diff-unhandled-changes)))) - (setq diff-unhandled-changes (cons beg end))))) +(defvar-local diff--track-changes nil) -(defun diff-post-command-hook () - "Fixup hunk headers if necessary." - (when (consp diff-unhandled-changes) - (ignore-errors +(defun diff--track-changes-signal (tracker) + (cl-assert (eq tracker diff--track-changes)) + (track-changes-fetch tracker #'diff--track-changes-function)) + +(defun diff--track-changes-function (beg end _before) + (with-demoted-errors "%S" (save-excursion - (goto-char (car diff-unhandled-changes)) + (goto-char beg) ;; Maybe we've cut the end of the hunk before point. (if (and (bolp) (not (bobp))) (backward-char 1)) ;; We used to fixup modifs on all the changes, but it turns out that @@ -1480,17 +1465,16 @@ (re-search-forward diff-context-mid-hunk-header-re nil t))))) (when (and ;; Don't try to fixup changes in the hunk header. - (>= (car diff-unhandled-changes) start) + (>= beg start) ;; Don't try to fixup changes in the mid-hunk header either. (or (not mid) - (< (cdr diff-unhandled-changes) (match-beginning 0)) - (> (car diff-unhandled-changes) (match-end 0))) + (< end (match-beginning 0)) + (> beg (match-end 0))) (save-excursion (diff-end-of-hunk nil 'donttrustheader) ;; Don't try to fixup changes past the end of the hunk. - (>= (point) (cdr diff-unhandled-changes)))) - (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))) - (setq diff-unhandled-changes nil)))) + (>= (point) end))) + (diff-fixup-modifs (point) end)))))) (defun diff-next-error (arg reset) ;; Select a window that displays the current buffer so that point @@ -1572,9 +1557,8 @@ diff-mode ;; setup change hooks (if (not diff-update-on-the-fly) (add-hook 'write-contents-functions #'diff-write-contents-hooks nil t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t)) + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal))) ;; add-log support (setq-local add-log-current-defun-function #'diff-current-defun) @@ -1593,12 +1577,13 @@ diff-minor-mode \\{diff-minor-mode-map}" :group 'diff-mode :lighter " Diff" ;; FIXME: setup font-lock - ;; setup change hooks + (when diff--track-changes (track-changes-unregister diff--track-changes)) + (remove-hook 'write-contents-functions #'diff-write-contents-hooks t) (if (not diff-update-on-the-fly) (add-hook 'write-contents-functions #'diff-write-contents-hooks nil t) - (make-local-variable 'diff-unhandled-changes) - (add-hook 'after-change-functions #'diff-after-change-function nil t) - (add-hook 'post-command-hook #'diff-post-command-hook nil t))) + (unless diff--track-changes + (setq diff--track-changes + (track-changes-register #'diff--track-changes-signal))))) ;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; --=-=-=--
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 29 Mar 2024 22:59:53 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 18:59:53 2024 Received: from localhost ([127.0.0.1]:43614 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqLCT-0000A3-Hf for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 18:59:53 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:64188) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqLCQ-00009k-12 for 70077 <at> debbugs.gnu.org; Fri, 29 Mar 2024 18:59:51 -0400 Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id CEA964429F7; Fri, 29 Mar 2024 18:59:41 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711753180; bh=49XqOxnI8GhRIaUhGknCF7y19BWQaxFhEo758rOPwa4=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=mtixeX0K01O+3DrDkARYXWLDaaKptg7xRGC6bpGnxvaA+hsuEHmkHf93eW0ZApMBd UQTKUUVM0Hd06u+xIItUBU8ENXFSjVoE+B8Y97PAPoxoVXvZvgk6PutDKRyynElmlo fz+NrkCcfK1RHSwjEZ+aBuAyjwTmp2MVBUwaPiTSOTnkOqm/EzxMmjRWoJXeFbXpjE CXHpnSfK//xjIMU2MBJukB94wACVPcfGHDbvxXemUVBxiPDXAT9YQmzlHurIWnysUp NgMd1WcJBKfKL9B/OVZ/M35qMtEp3obCgl+uk06pxeutvRds18iqXZ+yYZqJ7ezEq3 YIRfXrymneGgw== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 47B544429DC; Fri, 29 Mar 2024 18:59:40 -0400 (EDT) Received: from pastel (104-222-113-60.cpe.teksavvy.com [104.222.113.60]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id B63F5120550; Fri, 29 Mar 2024 18:59:39 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: phillip.lord@HIDDEN Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> (phillip lord's message of "Fri, 29 Mar 2024 18:20:32 -0400") Message-ID: <jwva5mgwt05.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> Date: Fri, 29 Mar 2024 18:59:38 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: Ihor Radchenko <yantar92@HIDDEN>, 70077 <at> debbugs.gnu.org, Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?windows-1252?B?RnLpZOlyaWM=?= Bour <frederic.bour@HIDDEN>, =?windows-1252?B?Sm/jbyBU4XZvcmE=?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > If I remember correctly, I think this wouldn't be enough for my > use. You keep two buffers in sync, you have to use > before-change-function -- it is only before any change that the two > buffers are guaranteed to be in sync and it is this that allows you to > work out what the `start' and `end' positions mean in the copied > buffer. Afterward, you cannot work out what the end position because > you don't know if the change is a change, insertion, deletion or both. I believe the API I propose does provide that information: you can recover the state of the buffer before the change (or more specifically, the state of the buffer as of the last time you called track-changes-fetch) from the BEG/END/BEFORE arguments as follows: (concat (buffer-substring (point-min) beg) before (buffer-substring end (point-max))) I don't mean to suggest to do that, since it's costly for large buffers, but to illustrate that the information is properly preserved. > Last time I checked, I did find relatively few primitives that were guilty > of being inconsistent -- in the case of `subst-char-in-region', it returned > the maximal area of effect before the and the minimal area of effect > after. Would it not be easier to fix these? [ IIRC `revert-buffer` has a similar behavior, and in that case the difference can be large since the "before" covers the whole buffer. ] Also, it would fix only the problem of pairing, and not the other ones. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 29 Mar 2024 22:20:44 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 18:20:44 2024 Received: from localhost ([127.0.0.1]:43576 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqKaZ-0003bY-Hg for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 18:20:43 -0400 Received: from cloud103.planethippo.com ([78.129.190.68]:38172) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <phillip.lord@HIDDEN>) id 1rqKaX-0003aw-NM for 70077 <at> debbugs.gnu.org; Fri, 29 Mar 2024 18:20:42 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=russet.org.uk; s=default; h=Content-Transfer-Encoding:Content-Type: Message-ID:References:In-Reply-To:Subject:Cc:To:From:Date:MIME-Version:Sender :Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=j4VfnN77pwpLxdYLNCHa3x9FDkPfvg91FTM3VgVjRP4=; b=2G25W5MMAd+P38mfZ2A8kSm0so 1yfxcQW1puIKjosDnIUSGa6XcDXWq6GB1ZRbdBdWNwjRU4tDClT0UW10d+zQVS8C7KzYrqakInpeO 9UeMDD7dqmBwyvI2RlvrBPx9LpdJBu2ee6ngNrZDwsIUl2DnMJRUWIbhVFOoZstEpZ5rfdP3heUOo aje6o2mMc2K+d/Iyr7mWN1Bvavo6IofGC3T/35cxzHrIfwCMPGOPFjXFZbkdhiSTYmthMmjfGTQel 1ieMknzPK50FV549Qp2hWUGZyv0KmHWdjxHDLXom6oEiuPXoxUhUnXVEF30yVxWwc/LqjbHshzlDE 3TCBqc1Q==; Received: from [::1] (port=51982 helo=cloud103.planethippo.com) by cloud103.planethippo.com with esmtpa (Exim 4.96.2) (envelope-from <phillip.lord@HIDDEN>) id 1rqKaR-005nA2-0I; Fri, 29 Mar 2024 18:20:33 -0400 MIME-Version: 1.0 Date: Fri, 29 Mar 2024 18:20:32 -0400 From: phillip.lord@HIDDEN To: Stefan Monnier <monnier@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <jwvle615806.fsf@HIDDEN> References: <jwvle615806.fsf@HIDDEN> User-Agent: Roundcube Webmail/1.6.0 Message-ID: <e94e6fc3b497db294a87fa2b4c388a11@HIDDEN> X-Sender: phillip.lord@HIDDEN Content-Type: text/plain; charset=US-ASCII; format=flowed Content-Transfer-Encoding: 7bit X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - cloud103.planethippo.com X-AntiAbuse: Original Domain - debbugs.gnu.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - russet.org.uk X-Get-Message-Sender-Via: cloud103.planethippo.com: authenticated_id: phillip.lord@HIDDEN X-Authenticated-Sender: cloud103.planethippo.com: phillip.lord@HIDDEN X-Source: X-Source-Args: X-Source-Dir: X-Spam-Score: 3.0 (+++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: On 2024-03-29 12:15, Stefan Monnier wrote: > Tags: patch > > Our `*-change-functions` hook are fairly tricky to use right. > Some of the issues are: > > - before and after calls are not necessarily pa [...] Content analysis details: (3.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Debbugs-Envelope-To: 70077 Cc: Yuan Fu <casouri@HIDDEN>, 70077 <at> debbugs.gnu.org, Ihor Radchenko <yantar92@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Bour?= <frederic.bour@HIDDEN>, =?UTF-8?Q?Jo=C3=A3o_T=C3=A1vora?= <joaotavora@HIDDEN>, Nicolas Goaziou <mail@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: 2.0 (++) X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: On 2024-03-29 12:15, Stefan Monnier wrote: > Tags: patch > > Our `*-change-functions` hook are fairly tricky to use right. > Some of the issues are: > > - before and after calls are not necessarily pa [...] Content analysis details: (2.0 points, 10.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 3.0 MANY_TO_CC Sent to 10+ recipients -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -1.0 MAILING_LIST_MULTI Multiple indicators imply a widely-seen list manager On 2024-03-29 12:15, Stefan Monnier wrote: > Tags: patch > > Our `*-change-functions` hook are fairly tricky to use right. > Some of the issues are: > > - before and after calls are not necessarily paired. > - the beg/end values don't always match. > - there can be thousands of calls from within a single command. > - these hooks are run at a fairly low-level so there are things they > really shouldn't do, such as modify the buffer or wait. > - the after call doesn't get enough info to rebuild the before-change > state, > so some callers need to use both before-c-f and after-c-f (and then > deal with the first two points above). > > The worst part is that those problems occur rarely, so many coders > don't > see it at first and have to learn them the hard way, sometimes forcing > them to rethink their original design. > > So I think we should provide something simpler. > I attached a proof-of-concept API which aims to do that, with the > following entry points: > > (defun track-changes-register ( signal) > "Register a new tracker and return a new tracker ID. > SIGNAL is a function that will be called with no argument when > the current buffer is modified, so that we can react to the change. > Once called, SIGNAL is not called again until `track-changes-fetch' > is called with the corresponding tracker ID." > > (defun track-changes-unregister (id) > "Remove the tracker denoted by ID. > Trackers can consume resources (especially if `track-changes-fetch' > is > not called), so it is good practice to unregister them when you > don't > need them any more." > > (defun track-changes-fetch (id func) > "Fetch the pending changes. > ID is the tracker ID returned by a previous > `track-changes-register'. > FUNC is a function. It is called with 3 arguments (BEGIN END > BEFORE) > where BEGIN..END delimit the region that was changed since the last > time `track-changes-fetch' was called and BEFORE is a string > containing > the previous content of that region. > > If no changes occurred since the last time, FUNC is not called and > we return nil, otherwise we return the value returned by FUNC, > and re-enable the TRACKER corresponding to ID." > > It's not meant as a replacement of the existing hooks since it doesn't > try to accommodate some uses such as those that use before-c-f to > implement a finer-grained form of read-only text. > > The driving design was: > > - Try to provide enough info such that it is possible and easy to > maintain a copy of the buffer simply by applying the reported > changes. > E.g. for uses such as `eglot.el` or `crdt.el`. > - Make the API less synchronous: take care of combining small changes > into larger ones, and let the clients decide when they react to > changes. > > If you're in the Cc, it's because I believe you have valuable > experience > with those hooks, so I'd be happy to hear your thought about whether > you think this would indeed (have) be(en) better than what we have. Your description of the problem is entirely consistent with my experience. The last time I checked it was `subst-char-in-region' which was causing most of the difficulties, normally as a result of `fill-paragraph'. If I remember correctly, I think this wouldn't be enough for my use. You keep two buffers in sync, you have to use before-change-function -- it is only before any change that the two buffers are guaranteed to be in sync and it is this that allows you to work out what the `start' and `end' positions mean in the copied buffer. Afterward, you cannot work out what the end position because you don't know if the change is a change, insertion, deletion or both. Last time I checked, I did find relatively few primitives that were guilty of being inconsistent -- in the case of `subst-char-in-region', it returned the maximal area of effect before the and the minimal area of effect after. Would it not be easier to fix these? Phil
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 29 Mar 2024 18:53:52 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 14:53:52 2024 Received: from localhost ([127.0.0.1]:43428 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqHMO-0001M4-9A for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 14:53:52 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:39499) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqHMM-0001LS-G8 for 70077 <at> debbugs.gnu.org; Fri, 29 Mar 2024 14:53:50 -0400 Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 46E3910005D; Fri, 29 Mar 2024 14:53:43 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711738421; bh=zOuPkA9+KnkBmDhWM6pkYAbuAuNqNnLQt+Y6IxhMd+Q=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=PenlcIecRM4Ov26Y46ANcWAjIvOGnUaqlOW6MeWF9LD7PFeEywpkfSSzxeyTPAGB/ jCvOvXHIMBT8wJX3li4kYhBlapZbZvMUr0IoUY5jbGkuLIitUdj7FUEDeIxhvuJuXO n0YsgeRUziSdEnjfVl0aR0PMGQRR5+v3K6JlUnE0pn1yzmUV5z1qdQIpJOlU6bwW59 E4jW7NDsLlW81Fdjd8mNNHtPwydFvXmKwU6FPGP/4fwRQXivCwt7iOf9BOHw4Pyw9O clFNSf4LfT6srImj27nHLg+ns0tWRFk8z5/RH0XL4VzDdmWMahZc2qr0b2g0ZiHviz BbR7/BN0/LpTg== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id D48A0100046; Fri, 29 Mar 2024 14:53:41 -0400 (EDT) Received: from pastel (104-222-113-60.cpe.teksavvy.com [104.222.113.60]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 78A36120185; Fri, 29 Mar 2024 14:53:41 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: Eli Zaretskii <eliz@HIDDEN> Subject: Re: bug#70077: An easier way to track buffer changes In-Reply-To: <86frw8ewk9.fsf@HIDDEN> (Eli Zaretskii's message of "Fri, 29 Mar 2024 21:12:54 +0300") Message-ID: <jwvle60x4d6.fsf-monnier+emacs@HIDDEN> References: <jwvle615806.fsf@HIDDEN> <86frw8ewk9.fsf@HIDDEN> Date: Fri, 29 Mar 2024 14:53:41 -0400 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 Content-Type: text/plain X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.243 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: yantar92@HIDDEN, 70077 <at> debbugs.gnu.org, casouri@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > I cannot imagine how applications would use these APIs. I'm probably > missing something, org the above documentation does. Can you show > some real-life examples? Haven't written real code for it yes, no. The best I can offer is the sample code in the file: (defvar my-foo--change-tracker nil) (define-minor-mode my-foo-mode "Fooing like there's no tomorrow." (if (null my-foo-mode) (when my-foo--change-tracker (track-changes-unregister my-foo--change-tracker) (setq my-foo--change-tracker nil)) (unless my-foo--change-tracker (setq my-foo--change-tracker (track-changes-register (lambda () (track-changes-fetch my-foo--change-tracker (lambda (beg end before) ..DO THE THING..)))))))) Where "DO THE THING" is run similarly to what would happen in an `after-change-functions`, except: - BEFORE is a string holding the content of what was in BEG..END instead of being limited to its length. - It's run at most once per command, so there's no performance worries. - It's run "outside" of the modifications themselves, so `inhibit-modification-hooks` is nil and the code can wait, modify the buffer, or do any kind of crazy things. The code can also be changed to: [...] (track-changes-register (lambda () (run-with-idle-timer 2 nil (lambda () (track-changes-fetch my-foo--change-tracker (lambda (beg end before) ..DO THE THING..)))))))) without any extra work. Stefan
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at 70077) by debbugs.gnu.org; 29 Mar 2024 18:13:14 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 14:13:14 2024 Received: from localhost ([127.0.0.1]:43408 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqGj3-0007Z7-AS for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 14:13:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51352) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1rqGj0-0007Yt-V6 for 70077 <at> debbugs.gnu.org; Fri, 29 Mar 2024 14:13:11 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <eliz@HIDDEN>) id 1rqGio-0003TJ-HS; Fri, 29 Mar 2024 14:12:59 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From: Date; bh=X8zNmtRQP0SLSAhAM7kbSkQ28wGf9dwkPmEku0/N82Y=; b=qbQ7X67dWxhtmuykI5tr 1Ssi4B2AfE+T6CW4QD0O2+UShg/9sV5xfwMcM2yhijgqc1NFBw3eRB9s2mUsBfn7Wo/AXykVzYQtA QwNK7zu+RFqyoEMK2tEVurK+uB1hPs8p35zZxZYiLaGBKl3fXnMYhDpXIOaOip00/sTnnCP6qA6qQ Gkw31Z+dAXT6zNLG+K+kZjqTc2b2IImKwXoh0EHk8aJuWc74GuPcPWgGUgHdPuitpqRYxW5NM+iT7 qAgui7RZlmDktB2pIGkP/RpTTrGM9j0Xrx/E2S5w75tzbeaJVuIhnEF9JdAh3H7yWd6Mj5TdcC1xb BPu19HDJS+TUOA==; Date: Fri, 29 Mar 2024 21:12:54 +0300 Message-Id: <86frw8ewk9.fsf@HIDDEN> From: Eli Zaretskii <eliz@HIDDEN> To: Stefan Monnier <monnier@HIDDEN> In-Reply-To: <jwvle615806.fsf@HIDDEN> (bug-gnu-emacs@HIDDEN) Subject: Re: bug#70077: An easier way to track buffer changes References: <jwvle615806.fsf@HIDDEN> MIME-version: 1.0 Content-type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Spam-Score: 0.7 (/) X-Debbugs-Envelope-To: 70077 Cc: casouri@HIDDEN, 70077 <at> debbugs.gnu.org, yantar92@HIDDEN, qhong@HIDDEN, frederic.bour@HIDDEN, joaotavora@HIDDEN, mail@HIDDEN, acm@HIDDEN, stephen_leake@HIDDEN, alan.zimm@HIDDEN, monnier@HIDDEN, phillip.lord@HIDDEN X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -0.3 (/) > Cc: Nicolas Goaziou <mail@HIDDEN>, > Ihor Radchenko <yantar92@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, > João Távora <joaotavora@HIDDEN>, > Alan Zimmerman <alan.zimm@HIDDEN>, > Frédéric Bour <frederic.bour@HIDDEN>, > Phillip Lord <phillip.lord@HIDDEN>, > Stephen Leake <stephen_leake@HIDDEN>, Yuan Fu <casouri@HIDDEN>, > Qiantan Hong <qhong@HIDDEN>, monnier@HIDDEN > Date: Fri, 29 Mar 2024 12:15:53 -0400 > From: Stefan Monnier via "Bug reports for GNU Emacs, > the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN> > > (defun track-changes-register ( signal) > "Register a new tracker and return a new tracker ID. > SIGNAL is a function that will be called with no argument when > the current buffer is modified, so that we can react to the change. > Once called, SIGNAL is not called again until `track-changes-fetch' > is called with the corresponding tracker ID." > > (defun track-changes-unregister (id) > "Remove the tracker denoted by ID. > Trackers can consume resources (especially if `track-changes-fetch' is > not called), so it is good practice to unregister them when you don't > need them any more." > > (defun track-changes-fetch (id func) > "Fetch the pending changes. > ID is the tracker ID returned by a previous `track-changes-register'. > FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) > where BEGIN..END delimit the region that was changed since the last > time `track-changes-fetch' was called and BEFORE is a string containing > the previous content of that region. > > If no changes occurred since the last time, FUNC is not called and > we return nil, otherwise we return the value returned by FUNC, > and re-enable the TRACKER corresponding to ID." I cannot imagine how applications would use these APIs. I'm probably missing something, org the above documentation does. Can you show some real-life examples?
bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.Received: (at submit) by debbugs.gnu.org; 29 Mar 2024 16:16:35 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Mar 29 12:16:35 2024 Received: from localhost ([127.0.0.1]:43253 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1rqEu9-0001jf-Tb for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 12:16:35 -0400 Received: from lists.gnu.org ([2001:470:142::17]:42846) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <monnier@HIDDEN>) id 1rqEu6-0001jP-IY for submit <at> debbugs.gnu.org; Fri, 29 Mar 2024 12:16:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <monnier@HIDDEN>) id 1rqEty-00013T-QT for bug-gnu-emacs@HIDDEN; Fri, 29 Mar 2024 12:16:22 -0400 Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <monnier@HIDDEN>) id 1rqEtv-0008Hr-V8 for bug-gnu-emacs@HIDDEN; Fri, 29 Mar 2024 12:16:22 -0400 Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 7209080AD9 for <bug-gnu-emacs@HIDDEN>; Fri, 29 Mar 2024 12:16:17 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1711728975; bh=HWHFnxY6gTAT9y6U3RemvbTpKbH0HnPlbAz3V/Y8dTo=; h=From:To:Subject:Date:From; b=nkz2zDG9v105OpPhH95wm9azILeG9I6q0U9ZGQgj3e1YduybOn/HOaJiaGSR+xQLw KZl34Cxt+fBsW0WwUMnzYSO2U+IoEwhQglRGZigpM+yAxOtNycuhwt3FfyyzEBGkIX Uqz1i03ow37zetuz8rI5f9QQwmSZlU4FwYLXyW2AV5uLAfX8FKh+VVnrVhAo+EU0WD CmsltX7dYZacV5p2MmgdtJ94UvYuMtkGdnitpFd82wwvPt9aRGfhNXYiBo7KszT4xT mF7sK5jsycsgURUz2m1l9DAbTbO+VbXU8xjescn6skKRs+4O/vm9iL1yKhJEYdj9UN tv4/xm3Fcv70A== Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 1C30A8009D for <bug-gnu-emacs@HIDDEN>; Fri, 29 Mar 2024 12:16:15 -0400 (EDT) Received: from alfajor (unknown [23.233.149.155]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 06D8D12077C for <bug-gnu-emacs@HIDDEN>; Fri, 29 Mar 2024 12:16:15 -0400 (EDT) From: Stefan Monnier <monnier@HIDDEN> To: bug-gnu-emacs@HIDDEN Subject: An easier way to track buffer changes X-Debbugs-Cc: Nicolas Goaziou <mail@HIDDEN>, Ihor Radchenko <yantar92@HIDDEN>, Alan Mackenzie <acm@HIDDEN>, =?iso-8859-1?Q?Jo=E3o_T=E1vora?= <joaotavora@HIDDEN>, Alan Zimmerman <alan.zimm@HIDDEN>, =?iso-8859-1?Q?Fr=E9d=E9ric?= Bour <frederic.bour@HIDDEN>, Phillip Lord <phillip.lord@HIDDEN>, Stephen Leake <stephen_leake@HIDDEN>, Yuan Fu <casouri@HIDDEN>, Qiantan Hong <qhong@HIDDEN>, monnier@HIDDEN Date: Fri, 29 Mar 2024 12:15:53 -0400 Message-ID: <jwvle615806.fsf@HIDDEN> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-SPAM-INFO: Spam detection results: 0 ALL_TRUSTED -1 Passed through trusted hosts only via SMTP AWL -0.045 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain X-SPAM-LEVEL: Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@HIDDEN; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain Tags: patch Our `*-change-functions` hook are fairly tricky to use right. Some of the issues are: - before and after calls are not necessarily paired. - the beg/end values don't always match. - there can be thousands of calls from within a single command. - these hooks are run at a fairly low-level so there are things they really shouldn't do, such as modify the buffer or wait. - the after call doesn't get enough info to rebuild the before-change state, so some callers need to use both before-c-f and after-c-f (and then deal with the first two points above). The worst part is that those problems occur rarely, so many coders don't see it at first and have to learn them the hard way, sometimes forcing them to rethink their original design. So I think we should provide something simpler. I attached a proof-of-concept API which aims to do that, with the following entry points: (defun track-changes-register ( signal) "Register a new tracker and return a new tracker ID. SIGNAL is a function that will be called with no argument when the current buffer is modified, so that we can react to the change. Once called, SIGNAL is not called again until `track-changes-fetch' is called with the corresponding tracker ID." (defun track-changes-unregister (id) "Remove the tracker denoted by ID. Trackers can consume resources (especially if `track-changes-fetch' is not called), so it is good practice to unregister them when you don't need them any more." (defun track-changes-fetch (id func) "Fetch the pending changes. ID is the tracker ID returned by a previous `track-changes-register'. FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) where BEGIN..END delimit the region that was changed since the last time `track-changes-fetch' was called and BEFORE is a string containing the previous content of that region. If no changes occurred since the last time, FUNC is not called and we return nil, otherwise we return the value returned by FUNC, and re-enable the TRACKER corresponding to ID." It's not meant as a replacement of the existing hooks since it doesn't try to accommodate some uses such as those that use before-c-f to implement a finer-grained form of read-only text. The driving design was: - Try to provide enough info such that it is possible and easy to maintain a copy of the buffer simply by applying the reported changes. E.g. for uses such as `eglot.el` or `crdt.el`. - Make the API less synchronous: take care of combining small changes into larger ones, and let the clients decide when they react to changes. If you're in the Cc, it's because I believe you have valuable experience with those hooks, so I'd be happy to hear your thought about whether you think this would indeed (have) be(en) better than what we have. Stefan --=-=-= Content-Type: text/x-emacs-lisp; charset=iso-8859-1 Content-Disposition: attachment; filename=track-changes.el Content-Transfer-Encoding: quoted-printable ;;; track-changes.el --- API to react to buffer modifications -*- lexical-= binding: t; -*- ;; Copyright (C) 2024 Free Software Foundation, Inc. ;; Author: Stefan Monnier <monnier@HIDDEN> ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; This library is a layer of abstraction above `before-change-functions' ;; and `after-change-functions' which takes care of accumulating changes ;; until a time when its client finds it convenient to react to them. ;; It provides the following operations: ;; ;; (track-changes-register SIGNAL) ;; (track-changes-fetch ID FUNC) ;; (track-changes-unregister ID) ;; ;; A typical use case might look like: ;; ;; (defvar my-foo--change-tracker nil) ;; (define-minor-mode my-foo-mode ;; "Fooing like there's no tomorrow." ;; (if (null my-foo-mode) ;; (when my-foo--change-tracker ;; (track-changes-unregister my-foo--change-tracker) ;; (setq my-foo--change-tracker nil)) ;; (unless my-foo--change-tracker ;; (setq my-foo--change-tracker ;; (track-changes-register ;; (lambda () ;; (track-changes-fetch ;; my-foo--change-tracker ;; (lambda (beg end before) ;; ..DO THE THING..)))))))) ;;; Code: ;; FIXME: Try and do some sanity-checks (e.g. looking at `buffer-size'), ;; to detect if/when we somehow missed some changes. ;; FIXME: The API doesn't offer an easy way to signal a "full resync" ;; kind of change, as might be needed if we lost changes. (require 'cl-lib) (cl-defstruct (track-changes--tracker (:noinline t) (:constructor nil) (:constructor track-changes--tracker ( signal state))) ( signal nil :read-only t) state) (cl-defstruct (track-changes--state (:noinline t) (:constructor nil) (:constructor track-changes--state ())) (beg (point-max)) (end (point-min)) (bbeg (point-max)) ;BEG of the BEFORE string, (bend (point-min)) ;END of the BEFORE string. (before nil) (next nil)) (defvar-local track-changes--trackers ()) (defvar-local track-changes--clean-trackers ()) (defvar-local track-changes--state nil) (defun track-changes-register ( signal) "Register a new tracker and return a new tracker ID. SIGNAL is a function that will be called with no argument when the current buffer is modified, so that we can react to the change. Once called, SIGNAL is not called again until `track-changes-fetch' is called with the corresponding tracker ID." ;; FIXME: Add an optional arg to choose between `funcall' and `funcall-la= ter'? (track-changes--clean-state) (add-hook 'before-change-functions #'track-changes--before nil t) (add-hook 'after-change-functions #'track-changes--after nil t) (let ((tracker (track-changes--tracker signal track-changes--state))) (push tracker track-changes--trackers) (push tracker track-changes--clean-trackers) tracker)) (defun track-changes-unregister (id) "Remove the tracker denoted by ID. Trackers can consume resources (especially if `track-changes-fetch' is not called), so it is good practice to unregister them when you don't need them any more." (unless (memq id track-changes--trackers) (error "Unregistering a non-registered tracker: %S" id)) (setq track-changes--trackers (delq id track-changes--trackers)) (setq track-changes--clean-trackers (delq id track-changes--clean-tracker= s)) (when (null track-changes--trackers) (setq track-changes--state nil) (remove-hook 'before-change-functions #'track-changes--before t) (remove-hook 'after-change-functions #'track-changes--after t))) (defun track-changes--clean-p () (null (track-changes--state-before track-changes--state))) (defun track-changes--clean-state () (cond ((null track-changes--state) ;; No state has been created yet. Do it now. (setq track-changes--state (track-changes--state))) ((track-changes--clean-p) nil) (t ;; FIXME: We may be in-between a before-c-f and an after-c-f, so we ;; should save some of the current buffer in case an after-c-f comes ;; before a before-c-f. (let ((new (track-changes--state))) (setf (track-changes--state-next track-changes--state) new) (setq track-changes--state new))))) (defun track-changes--before (beg end) (cl-assert track-changes--state) (cl-assert (<=3D beg end)) (if (track-changes--clean-p) (progn (setf (track-changes--state-before track-changes--state) (buffer-substring-no-properties beg end)) (setf (track-changes--state-bbeg track-changes--state) beg) (setf (track-changes--state-bend track-changes--state) end)) (cl-assert (save-restriction (widen) (<=3D (point-min) (track-changes--state-bbeg track-changes--state) (track-changes--state-bend track-changes--state) (point-max)))) (when (< beg (track-changes--state-bbeg track-changes--state)) (let* ((old-bbeg (track-changes--state-bbeg track-changes--state)) ;; To avoid O(N=B2) behavior when faced with many small change= s, ;; we copy more than needed. (new-bbeg (min (max (point-min) (- old-bbeg (length (track-changes--state-before track-changes--state)))) beg))) (setf (track-changes--state-bbeg track-changes--state) beg) (cl-callf (lambda (old new) (concat new old)) (track-changes--state-before track-changes--state) (buffer-substring-no-properties new-bbeg old-bbeg)))) (when (< (track-changes--state-bend track-changes--state) end) (let* ((old-bend (track-changes--state-bend track-changes--state)) ;; To avoid O(N=B2) behavior when faced with many small change= s, ;; we copy more than needed. (new-bend (max (min (point-max) (+ old-bend (length (track-changes--state-before track-changes--state)))) end))) (setf (track-changes--state-bend track-changes--state) end) (cl-callf concat (track-changes--state-before track-changes--state) (buffer-substring-no-properties old-bend new-bend)))))) (defun track-changes--after (beg end len) (cl-assert track-changes--state) (cl-assert (track-changes--state-before track-changes--state)) (let ((offset (- (- end beg) len))) (cl-incf (track-changes--state-bend track-changes--state) offset) (cl-assert (save-restriction (widen) (<=3D (point-min) (track-changes--state-bbeg track-changes--state) beg end (track-changes--state-bend track-changes--state) (point-max)))) ;; Note the new changes. (when (< beg (track-changes--state-beg track-changes--state)) (setf (track-changes--state-beg track-changes--state) beg)) (cl-callf (lambda (old-end) (max end (+ old-end offset))) (track-changes--state-end track-changes--state))) (cl-assert (<=3D (track-changes--state-bbeg track-changes--state) (track-changes--state-beg track-changes--state) beg end (track-changes--state-end track-changes--state) (track-changes--state-bend track-changes--state))) (while track-changes--clean-trackers (let ((tracker (pop track-changes--clean-trackers))) ;; FIXME: Use `funcall'? (funcall-later (track-changes--tracker-signal tracker) ())))) (defun track-changes-fetch (id func) "Fetch the pending changes. ID is the tracker ID returned by a previous `track-changes-register'. FUNC is a function. It is called with 3 arguments (BEGIN END BEFORE) where BEGIN..END delimit the region that was changed since the last time `track-changes-fetch' was called and BEFORE is a string containing the previous content of that region. If no changes occurred since the last time, FUNC is not called and we return nil, otherwise we return the value returned by FUNC, and re-enable the TRACKER corresponding to ID." (let ((beg nil) (end nil) (before nil) (states ())) ;; We want to combine the states from most recent to oldest, ;; so reverse them. (let ((state (track-changes--tracker-state id))) (while state (push state states) (setq state (track-changes--state-next state)))) (when (null (track-changes--state-before (car states))) (cl-assert (eq (car states) track-changes--state)) (setq states (cdr states))) (if (null states) (progn (cl-assert (memq id track-changes--clean-trackers)) nil) (dolist (state states) (let ((prevbbeg (track-changes--state-bbeg state)) (prevbend (track-changes--state-bend state)) (prevbefore (track-changes--state-before state))) (if (not before) (progn ;; This is the most recent change. Just initialize the var= s. (setq beg (track-changes--state-beg state)) (setq end (track-changes--state-end state)) (setq before prevbefore) (unless (and (=3D beg prevbbeg) (=3D end prevbend)) (setq before (substring before (- beg (track-changes--state-bbeg state)) (- (length before) (- (track-changes--state-bend state) end)))))) ;; FIXME: When merging "states", we disregard the `beg/end' ;; in favor of `bbeg/bend' which also works but is conservative. (let ((endb (+ beg (length before)))) (when (< prevbbeg beg) (setq before (concat (buffer-substring-no-properties prevbbeg beg) before)) (setq beg prevbbeg) (cl-assert (=3D endb (+ beg (length before))))) (when (< endb prevbend) (let ((new-end (+ end (- prevbend endb)))) (setq before (concat before (buffer-substring-no-properties end new-end))) (setq end new-end) (cl-assert (=3D prevbend (+ beg (length before)))) (setq endb (+ beg (length before))))) (cl-assert (<=3D beg prevbbeg prevbend endb)) ;; The `prevbefore' is covered by the new one. (setq before (concat (substring before 0 (- prevbbeg beg)) prevbefore (substring before (- (length before) (- endb prevbend))))))))) (cl-assert (<=3D (point-min) beg end (point-max))) ;; Clean the state of the tracker before calling `func', in case ;; `func' performs buffer modifications. (track-changes--clean-state) ;; Update the tracker's state before running `func' so we don't risk ;; mistakenly replaying the changes in case `func' exits non-locally. (setf (track-changes--tracker-state id) track-changes--state) (unwind-protect (funcall func beg end before) ;; Re-enable the tracker's signal only after running `func', so ;; as to avoid recursive invocations. (cl-pushnew id track-changes--clean-trackers))))) (defmacro with-track-changes (id vars &rest body) (declare (indent 2) (debug (form sexp body))) `(track-changes-fetch ,id (lambda ,vars ,@body))) =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 (provide 'track-changes) ;;; track-changes.el end here. --=-=-=--
Stefan Monnier <monnier@HIDDEN>
:mail@HIDDEN, yantar92@HIDDEN, acm@HIDDEN, joaotavora@HIDDEN, alan.zimm@HIDDEN, frederic.bour@HIDDEN, phillip.lord@HIDDEN, stephen_leake@HIDDEN, casouri@HIDDEN, qhong@HIDDEN, monnier@HIDDEN, bug-gnu-emacs@HIDDEN
.
Full text available.mail@HIDDEN, yantar92@HIDDEN, acm@HIDDEN, joaotavora@HIDDEN, alan.zimm@HIDDEN, frederic.bour@HIDDEN, phillip.lord@HIDDEN, stephen_leake@HIDDEN, casouri@HIDDEN, qhong@HIDDEN, monnier@HIDDEN, bug-gnu-emacs@HIDDEN
:bug#70077
; Package emacs
.
Full text available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997 nCipher Corporation Ltd,
1994-97 Ian Jackson.