Listener functionality

187 views
Skip to first unread message

Bram Moolenaar

unread,
May 12, 2019, 8:02:37 AM5/12/19
to vim...@googlegroups.com

Yesterday I sent out patch 8.1.1321 which adds listener_add().
This makes it possible to find out about text changes and possibly
update text properties or anything else.

The current implementation is quite low-level. That makes it possible
to see exactly what text changed. Also for something like a global
substitute. I wonder if there is use for a much simpler callback, which
would only report the range of lines with any change. That should be
fine to decide where to update highlighting. But for a global subsitute
that changes two lines far apart, the whole area between them would be
reported.

Something like this:

func Listener(start, end)
" Update highlighting from line a:start to line a:end
endfunc
call listener_addsimple('Listener')

Another thing is that the line numbers in the changes are valid for when
the change is made. If later changes insert/delete lines above it, then
those line numbers will need to be adjusted. It would be possible to
add a function that does this, if there is interest.

--
./configure
Checking whether build environment is sane ...
build environment is grinning and holding a spatula. Guess not.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Paul Jolly

unread,
May 13, 2019, 4:14:27 AM5/13/19
to vim_dev
> Yesterday I sent out patch 8.1.1321 which adds listener_add().
> This makes it possible to find out about text changes and possibly
> update text properties or anything else.

Hi Bram - this is fantastic, thanks. I look forward to giving this a try with https://github.com/myitcv/govim and gopls.

> Another thing is that the line numbers in the changes are valid for when
the change is made.

Understood. One relatively easy way to handle this is to handle the changes in reverse, i.e. by iterating through the list backwards. But I think this requires that the changes themselves be non-overlapping.

From glancing at the documentation in https://github.com/vim/vim/commit/a334772967de25764ed7b11d768e8b977818d0c6 it appears that's the case, but I did just want to check.

Please can you confirm?

Thanks

Paul Jolly

unread,
May 13, 2019, 4:19:17 AM5/13/19
to vim_dev
> Understood. One relatively easy way to handle this is to handle the changes in reverse, i.e. by iterating through the list backwards. But I think this requires that the changes themselves be non-overlapping.

I actually misunderstood what you said here, so please ignore the question :)

Now that I've actually given this a bit of a test I'm clear now.

Apologies for the noise.

Bram Moolenaar

unread,
May 13, 2019, 2:44:27 PM5/13/19
to vim...@googlegroups.com, Paul Jolly
Not sure. In a script it's possible a lot of changes happen in
sequence, until the next redraw. So, give it a try.

I was also wondering if we need a "flush" command for the changes, in
case a redraw is undesired but the listeners should be invoked.

Another possibility is to automatically flush before adding a change
that is above a previous one AND it changes the line count. That's when
keeping track of line numbers becomes difficult.

--
If all you have is a hammer, everything looks like a nail.
When your hammer is C++, everything begins to look like a thumb.
-- Steve Hoflich, comp.lang.c++

Bram Moolenaar

unread,
May 13, 2019, 3:33:42 PM5/13/19
to vim...@googlegroups.com, Bram Moolenaar, Paul Jolly

I wrote:

> Paul Jolly wrote:
>
> > > Yesterday I sent out patch 8.1.1321 which adds listener_add().
> > > This makes it possible to find out about text changes and possibly
> > > update text properties or anything else.
> >
> > Hi Bram - this is fantastic, thanks. I look forward to giving this a try with https://github.com/myitcv/govim and gopls.
> >
> > > Another thing is that the line numbers in the changes are valid for when
> > the change is made.
> >
> > Understood. One relatively easy way to handle this is to handle the changes in reverse, i.e. by iterating through the list backwards. But I think this requires that the changes themselves be non-overlapping.
> >
> > From glancing at the documentation in https://github.com/vim/vim/commit/a334772967de25764ed7b11d768e8b977818d0c6 it appears that's the case, but I did just want to check.
> >
> > Please can you confirm?
>
> Not sure. In a script it's possible a lot of changes happen in
> sequence, until the next redraw. So, give it a try.
>
> I was also wondering if we need a "flush" command for the changes, in
> case a redraw is undesired but the listeners should be invoked.
>
> Another possibility is to automatically flush before adding a change
> that is above a previous one AND it changes the line count. That's when
> keeping track of line numbers becomes difficult.

Note that if we go this way, then the text must be locked, since the
callback would then happen somewhere during making changes. That
probably isn't a problem, if the plugin is doing some highlighting or
async server requests. If needed, the plugin could use a timer to be
called back later to do further work.

--
Looking at Perl through Lisp glasses, Perl looks atrocious.

Bram Moolenaar

unread,
May 14, 2019, 3:03:10 PM5/14/19
to Bram Moolenaar, vim...@googlegroups.com, Paul Jolly

I wrote:

> > Paul Jolly wrote:
> >
> > > > Yesterday I sent out patch 8.1.1321 which adds listener_add().
> > > > This makes it possible to find out about text changes and possibly
> > > > update text properties or anything else.
> > >
> > > Hi Bram - this is fantastic, thanks. I look forward to giving this a try with https://github.com/myitcv/govim and gopls.
> > >
> > > > Another thing is that the line numbers in the changes are valid for when
> > > the change is made.
> > >
> > > Understood. One relatively easy way to handle this is to handle the changes in reverse, i.e. by iterating through the list backwards. But I think this requires that the changes themselves be non-overlapping.
> > >
> > > From glancing at the documentation in https://github.com/vim/vim/commit/a334772967de25764ed7b11d768e8b977818d0c6 it appears that's the case, but I did just want to check.
> > >
> > > Please can you confirm?
> >
> > Not sure. In a script it's possible a lot of changes happen in
> > sequence, until the next redraw. So, give it a try.
> >
> > I was also wondering if we need a "flush" command for the changes, in
> > case a redraw is undesired but the listeners should be invoked.
> >
> > Another possibility is to automatically flush before adding a change
> > that is above a previous one AND it changes the line count. That's when
> > keeping track of line numbers becomes difficult.
>
> Note that if we go this way, then the text must be locked, since the
> callback would then happen somewhere during making changes. That
> probably isn't a problem, if the plugin is doing some highlighting or
> async server requests. If needed, the plugin could use a timer to be
> called back later to do further work.

Thinking about this a bit more, I think we can simplify the work that a
plugin needs to do by doing this:

- Before line numbers in the list of changes become invalid, flush the
changes. If changes happen from top to bottom, such as with "%s",
then it will still be one invocation of the callback.

- If there are multiple changes starting at the same line and column, we
can merge them into reporting one change.

- Instead of only passing the list, also pass a summary of the changes,
so that the plugin can use the range of lines, without having to go
through the whole list. Useful for something like a syntax checker.

- Add listener_flush() to trigger listeners without a redraw.
Especially useful when programatically changing the text.

- On a redraw, invoke listeners for all buffers (that's actually a
bugfix).

I'll make a patch that does this, please check it out.

--
DENNIS: Look, strange women lying on their backs in ponds handing out
swords ... that's no basis for a system of government. Supreme
executive power derives from a mandate from the masses, not from some
farcical aquatic ceremony.
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

Paul Jolly

unread,
May 14, 2019, 3:10:09 PM5/14/19
to Bram Moolenaar, vim...@googlegroups.com
> I'll make a patch that does this, please check it out.

Thanks, Bram. I'll only properly get a chance to look this weekend.

As you said this function is quite low level. Thinking about govim,
what I'm going to want is the lowest cost means of translating a
callback to the listener into an LSP delta. That means zero
round-trips from govim back to Vim.

Now it's entirely possible I can do this LSP delta calculation in
VimScript and have a VimScript based listener proxy to a govim
registered function.

So, not raising any concerns per se, just thinking out loud (before
having written a single line of code) to make sure my thinking isn't
too far off track. Would appreciate if you could flag any problems
with what I've sketched out here though.

Thanks

Paul Jolly

unread,
May 16, 2019, 8:20:15 AM5/16/19
to Bram Moolenaar, vim...@googlegroups.com
> I'll make a patch that does this, please check it out.

Hi Bram - I've only spotted one potential issue working with v8.1.1333.

Using the following vimrc

function EchoChanges(bufnr, start, end, added, changes)
redir >> /tmp/listener.log | echom a:changes | redir END
endfunction
call listener_add("EchoChanges")

via -u and then issuing the following keys:

iasdf<Esc>yyp

gives the following log sequence:

[{'lnum': 1, 'col': 1, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 2, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 3, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 4, 'added': 0, 'end': 2}]
[{'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]

It's the last line that I think is wrong; it's an add and according to the docs:

When lines are inserted the values are:
lnum line below which the new line is added
end equal to "lnum"
added number of lines inserted
col 1

I think lnum should be 1, no?

Apologies if I'm missing something obvious here.

Thanks

Bram Moolenaar

unread,
May 16, 2019, 11:01:14 AM5/16/19
to vim...@googlegroups.com, Paul Jolly
The help is wrong, it should say "line above which".
Of course we could change the implementation, but it would be an exception.

--
Mrs Abbott: I'm a paediatrician.
Basil: Feet?
Mrs Abbott: Children.
Sybil: Oh, Basil!
Basil: Well, children have feet, don't they? That's how they move
around, my dear. You must take a look next time, it's most
interesting. (Fawlty Towers)

Paul Jolly

unread,
May 16, 2019, 11:31:27 AM5/16/19
to Bram Moolenaar, vim...@googlegroups.com
> The help is wrong, it should say "line above which".
> Of course we could change the implementation, but it would be an exception.

Ah, if it's "line above which" then I think there is a problem with
the following sequence:

iasdf<CR>

which gives:

[{'lnum': 1, 'col': 1, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 2, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 3, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 4, 'added': 0, 'end': 2}]
[{'lnum': 1, 'col': 5, 'added': 1, 'end': 2}]

Again, apologies if I'm missing something obvious here.

Thanks,


Paul

Bram Moolenaar

unread,
May 16, 2019, 12:07:57 PM5/16/19
to vim...@googlegroups.com, Paul Jolly
This is not inserting a line but splitting a line. There could be text
after the insertion point, which moves to the next line when pressing
Enter. In your case there might be nothing, but that doesn't cause a
different change item. You can see "lnum" and "end" are not the same
here, meaning that this line might be changed.

--
Any resemblance between the above views and those of my employer, my terminal,
or the view out my window are purely coincidental. Any resemblance between
the above and my own views is non-deterministic. The question of the
existence of views in the absence of anyone to hold them is left as an
exercise for the reader. The question of the existence of the reader is left
as an exercise for the second god coefficient. (A discussion of
non-orthogonal, non-integral polytheism is beyond the scope of this article.)
(Ralph Jennings)

Paul Jolly

unread,
May 16, 2019, 2:42:10 PM5/16/19
to Bram Moolenaar, vim...@googlegroups.com
> > Again, apologies if I'm missing something obvious here.
>
> This is not inserting a line but splitting a line. There could be text
> after the insertion point, which moves to the next line when pressing
> Enter. In your case there might be nothing, but that doesn't cause a
> different change item. You can see "lnum" and "end" are not the same
> here, meaning that this line might be changed.

Bram - that makes total sense, thanks very much for your patience.

One further issue I'm seeing relates to how undos are processed.

Now with the following:

function EchoChanges(bufnr, start, end, added, changes)
redir >> /tmp/listener.log | echom getbufline(a:bufnr, 0, "$")
a:changes | redir END
endfunction
call listener_add("EchoChanges")

loaded via -u, with an initial file of:

Line 1
Line 2 #
Line 3
Line 4 #
Line 5
Line 6 #

and then executing the following commands:

:g/#/d
u

I see the following log line for the global command (which makes sense):

['Line 1', 'Line 3', 'Line 5'] [{'lnum': 2, 'col': 1, 'added': -1,
'end': 3}, {'lnum': 3, 'col': 1, 'added': -1, 'end': 4}, {'lnum': 4,
'col': 1, 'added': -1, 'end': 5}]

And then the following sequence as a result of the undo:

['Line 1', 'Line 3', 'Line 4 #', 'Line 5', 'Line 6 #'] [{'lnum': 4,
'col': 1, 'added': 1, 'end': 4}]
['Line 1', 'Line 2 #', 'Line 3', 'Line 4 #', 'Line 5', 'Line 6 #']
[{'lnum': 3, 'col': 1, 'added': 1, 'end': 3}]
['Line 1', 'Line 2 #', 'Line 3', 'Line 4 #', 'Line 5', 'Line 6 #']
[{'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]

Now I'm guessing the undo is being processed step-by-step (so to
speak) in reverse.

So the first and second lines look wrong, because they appear to show
the buffer already has the "next" undo applied.

Again, apologies if (as is likely) I'm missing something obvious.

Thanks

Bram Moolenaar

unread,
May 16, 2019, 4:12:25 PM5/16/19
to vim...@googlegroups.com, Paul Jolly
You are right, the changes are flushed before adding one that changes
the line numbers, but the text has already changed. I'll see how that
can be fixed...

--
|

Ceci n'est pas une pipe.

Paul Jolly

unread,
May 16, 2019, 5:12:39 PM5/16/19
to Bram Moolenaar, vim...@googlegroups.com
> You are right, the changes are flushed before adding one that changes
> the line numbers, but the text has already changed. I'll see how that
> can be fixed...

Thanks very much for the quick fix; I've confirmed it's working.

Thanks again for your patience; I think I now have a sufficient handle
on this to try and implement deltas within govim.

Bram Moolenaar

unread,
May 17, 2019, 6:32:12 AM5/17/19
to vim...@googlegroups.com, Paul Jolly
Good luck. Let me know when something doesn't work properly, we can
still adjust the functionality for a week or so. Well, once you use it
we should probably not change it anymore.

--
BEDEVERE: Why do you think she is a witch?
SECOND VILLAGER: She turned me into a newt.
BEDEVERE: A newt?
SECOND VILLAGER: (After looking at himself for some time) I got better.
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

Paul Jolly

unread,
May 22, 2019, 7:43:17 AM5/22/19
to Bram Moolenaar, vim...@googlegroups.com
> Good luck. Let me know when something doesn't work properly, we can
> still adjust the functionality for a week or so. Well, once you use it
> we should probably not change it anymore.

Hi Bram,

Good news: I have delta based updates from govim -> gopls largely working.

But there appears to be one issue as far as changes made by a
particular plugin is concerned.

I use https://github.com/jiangmiao/auto-pairs (please do say if there
is a better equivalent!)

I find this plugin useful. For example given

const x =

If I then type the backtick character, autopairs will leave me with:

const x = ``

i.e. it will insert the closing backtick, leaving the cursor in between the two.

If I then hit return I am left with:

const x = `

`

i.e. autopairs introduces a blank line.

But I'm struggling to reason about the callback that I receive.

Here I repeat the above experiment, outputting the lines of the buffer
following by the list of changes:

['c'] [{'lnum': 1, 'col': 1, 'added': 0, 'end': 2}]
['co'] [{'lnum': 1, 'col': 2, 'added': 0, 'end': 2}]
['con'] [{'lnum': 1, 'col': 3, 'added': 0, 'end': 2}]
['cons'] [{'lnum': 1, 'col': 4, 'added': 0, 'end': 2}]
['const'] [{'lnum': 1, 'col': 5, 'added': 0, 'end': 2}]
['const '] [{'lnum': 1, 'col': 6, 'added': 0, 'end': 2}]
['const x'] [{'lnum': 1, 'col': 7, 'added': 0, 'end': 2}]
['const x '] [{'lnum': 1, 'col': 8, 'added': 0, 'end': 2}]
['const x ='] [{'lnum': 1, 'col': 9, 'added': 0, 'end': 2}]
['const x = '] [{'lnum': 1, 'col': 10, 'added': 0, 'end': 2}]
['const x = ``'] [{'lnum': 1, 'col': 11, 'added': 0, 'end': 2},
{'lnum': 1, 'col': 12, 'added': 0, 'end': 2}]
['const x = `', '', '`'] [{'lnum': 1, 'col': 12, 'added': 1, 'end':
2}, {'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]

Based on these changes, I don't see there has been a change/insert on line 3.

Hopefully I've managed to explain that clearly enough?

Thanks,


Paul

Bram Moolenaar

unread,
May 22, 2019, 10:19:37 AM5/22/19
to vim...@googlegroups.com, Paul Jolly
So the first list are the lines in the buffer? Then it looks correct:

> ['const x = ``'] [{'lnum': 1, 'col': 11, 'added': 0, 'end': 2}, {'lnum': 1, 'col': 12, 'added': 0, 'end': 2}]

Here you type the first backtick, and the plugin adds the second
backtick.

> ['const x = `', '', '`'] [{'lnum': 1, 'col': 12, 'added': 1, 'end': 2}, {'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]

Here you type Enter twice. The first time the existing line is split,
so you get lnum = 1 and end = 2. The second time a line is inserted, so
you get lnum = 2 and end = 2. You get two entries with "added" equal to
one, thus two lines have been added, that is also correct.

Perhaps it's confusing that when a line is inserted, the "lnum" is the
line above which it is inserted. Imagine doing "O" on a line. Doing
"o" on the previous line has the same effect, thus you get the same line
numbers.

--
Everybody wants to go to heaven, but nobody wants to die.

Paul Jolly

unread,
May 22, 2019, 11:07:23 AM5/22/19
to Bram Moolenaar, vim...@googlegroups.com
Thanks for your patience with this!

> So the first list are the lines in the buffer?

Yes, correct.

> > ['const x = ``'] [{'lnum': 1, 'col': 11, 'added': 0, 'end': 2}, {'lnum': 1, 'col': 12, 'added': 0, 'end': 2}]
>
> Here you type the first backtick, and the plugin adds the second
> backtick.

Yup, this looked right to me.

> > ['const x = `', '', '`'] [{'lnum': 1, 'col': 12, 'added': 1, 'end': 2}, {'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]
>
> Here you type Enter twice. The first time the existing line is split,
> so you get lnum = 1 and end = 2. The second time a line is inserted, so
> you get lnum = 2 and end = 2. You get two entries with "added" equal to
> one, thus two lines have been added, that is also correct.

The problem (or likely my misunderstanding) is that I get both of
these in the same callback, i.e. they both see the buffer in the state
shown, hence neither "sees" the change in line 3.

To provide this in context (perhaps what I should have done from the start).

What I'm doing in response to these callbacks is gathering the
affected lines, adding them to each of the changes, and passing on the
lots to govim (to save roundtrips).

for l:change in a:changes
if l:change.added < 0
let l:change.type = "deleted"
else
if l:change.lnum != l:change.end
let l:change.type = "changed"
let l:change.lines = getbufline(a:bufnr, l:change.lnum,
l:change.end-1+l:change.added)
elseif l:change.added > 0
let l:change.type = "inserted"
let l:change.lines = getbufline(a:bufnr, l:change.lnum,
l:change.lnum+l:change.added-1)
endif
endif
endfor

Or for the second entry in the change list, do I need to move lnum
"forward" by the number of lines added before?

> Perhaps it's confusing that when a line is inserted, the "lnum" is the
> line above which it is inserted. Imagine doing "O" on a line. Doing
> "o" on the previous line has the same effect, thus you get the same line
> numbers.

I _think_ I have this one sorted ;-) But depending on your answer
above that might also prove to be a wrong assumption!

Thanks again,


Paul

Paul Jolly

unread,
May 22, 2019, 11:50:45 AM5/22/19
to Bram Moolenaar, vim...@googlegroups.com
Here you type Enter twice. 

Not sure if it's significant, but I only hit enter once. The plugin makes the second change here.

Bram Moolenaar

unread,
May 22, 2019, 12:26:02 PM5/22/19
to vim...@googlegroups.com, Paul Jolly
It matters in the sense that there is no screen redraw in between.
If you would have typed really fast then the same thing would have
happened, since Vim often doesn't redraw when there is typeahead.

--
hundred-and-one symptoms of being an internet addict:
7. You finally do take that vacation, but only after buying a USB modem
and a laptop.

Paul Jolly

unread,
May 22, 2019, 1:56:18 PM5/22/19
to Bram Moolenaar, vim...@googlegroups.com
Thanks for your other response. Not sure whether my message below came through.

It seems I'm going to have to do a bit more work on the list of
changes that comes through.

In the example, I think I actually need to collapse the two changes
into a single one with added = 2.

Then do a pass over the resulting list getting the lines based on the
current buffer.

Which I think brings us back to the non-overlapping changes question I
think. Because the second change overlaps with the first, which is
what causes my logic to fail.

Is that about right?

Bram Moolenaar

unread,
May 22, 2019, 2:50:40 PM5/22/19
to vim...@googlegroups.com, Paul Jolly

Paul Jolly wrote:

> Thanks for your patience with this!
>
> > So the first list are the lines in the buffer?
>
> Yes, correct.
>
> > > ['const x = ``'] [{'lnum': 1, 'col': 11, 'added': 0, 'end': 2}, {'lnum': 1, 'col': 12, 'added': 0, 'end': 2}]
> >
> > Here you type the first backtick, and the plugin adds the second
> > backtick.
>
> Yup, this looked right to me.
>
> > > ['const x = `', '', '`'] [{'lnum': 1, 'col': 12, 'added': 1, 'end': 2}, {'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]
> >
> > Here you type Enter twice. The first time the existing line is split,
> > so you get lnum = 1 and end = 2. The second time a line is inserted, so
> > you get lnum = 2 and end = 2. You get two entries with "added" equal to
> > one, thus two lines have been added, that is also correct.
>
> The problem (or likely my misunderstanding) is that I get both of
> these in the same callback, i.e. they both see the buffer in the state
> shown, hence neither "sees" the change in line 3.

Well, it depends on what you want to do with them. The line numbers are
correct and the text lines are correct for those line numbers.
If you have a number of changes in the same line, you only get the final
state though, not all the text states in between. And I assume that's
fine.

> To provide this in context (perhaps what I should have done from the start).
>
> What I'm doing in response to these callbacks is gathering the
> affected lines, adding them to each of the changes, and passing on the
> lots to govim (to save roundtrips).
>
> for l:change in a:changes
> if l:change.added < 0
> let l:change.type = "deleted"
> else
> if l:change.lnum != l:change.end
> let l:change.type = "changed"
> let l:change.lines = getbufline(a:bufnr, l:change.lnum,
> l:change.end-1+l:change.added)
> elseif l:change.added > 0
> let l:change.type = "inserted"
> let l:change.lines = getbufline(a:bufnr, l:change.lnum,
> l:change.lnum+l:change.added-1)
> endif
> endif
> endfor
>
> Or for the second entry in the change list, do I need to move lnum
> "forward" by the number of lines added before?

Up to you. You can merge the changes into one, keeping the lowest
changed line number and the total deleted/added lines, or you can deal
with each change separately.

Your loop has a clear problem, that if you get two changes, one deleting
a line and one adding a line, then the l:change.type will get the last
change type, which depends on the ordering, so that won't work. The
easiest would be to deal with each change separately. Thus just before
the "endfor".

> > Perhaps it's confusing that when a line is inserted, the "lnum" is the
> > line above which it is inserted. Imagine doing "O" on a line. Doing
> > "o" on the previous line has the same effect, thus you get the same line
> > numbers.
>
> I _think_ I have this one sorted ;-) But depending on your answer
> above that might also prove to be a wrong assumption!

It is unrelated, it's just that you might expect a different line number
when inserting a line.

--
hundred-and-one symptoms of being an internet addict:
8. You spend half of the plane trip with your laptop on your lap...and your
child in the overhead compartment.

Paul Jolly

unread,
May 22, 2019, 3:58:01 PM5/22/19
to Bram Moolenaar, vim...@googlegroups.com
Thanks very much.

> Up to you. You can merge the changes into one, keeping the lowest
> changed line number and the total deleted/added lines, or you can deal
> with each change separately.

I'm not sure I see how the change can be handled separately (without
carrying forward position changes) in my case because it's only the
combined result of the two changes that tells me I also need to
include line 3.

The first change:

{'lnum': 1, 'col': 12, 'added': 1, 'end': 2}

tells me I need to change line 1 in my plugin's version of the buffer,
and then I should insert line 2 from the current buffer state after
line 1 in my plugin's version of the buffer.

The second change:

{'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]

tells me I also need to insert line 2 from the current buffer before
line 2 in my plugin's version of the buffer.

Both read from the buffer in the current state, which means I end up
with two blank lines instead of a blank line followed by a line with a
single backtick

> Your loop has a clear problem, that if you get two changes, one deleting
> a line and one adding a line, then the l:change.type will get the last
> change type, which depends on the ordering, so that won't work. The
> easiest would be to deal with each change separately. Thus just before
> the "endfor".

Thanks, there are I'm sure a few issues that need to be resolved given
my new understanding of the way things work :)

I think I've got an idea of how I should be handling this now.

Thanks again for your patience and taking the time to walk me through it.


Paul

Bram Moolenaar

unread,
May 22, 2019, 4:29:47 PM5/22/19
to vim...@googlegroups.com, Paul Jolly

Paul Jolly wrote:

> Thanks for your other response. Not sure whether my message below came through.
>
> It seems I'm going to have to do a bit more work on the list of
> changes that comes through.
>
> In the example, I think I actually need to collapse the two changes
> into a single one with added = 2.
>
> Then do a pass over the resulting list getting the lines based on the
> current buffer.
>
> Which I think brings us back to the non-overlapping changes question I
> think. Because the second change overlaps with the first, which is
> what causes my logic to fail.
>
> Is that about right?

The changes were made in the order they are in the list. They can
overlap, but only in a way that a later change does not change the line
number of an earlier change. Thus getting the text from the current
state of the buffer will result in the right line, but a later change
may have changed the content of that line.

It would be possible to report every single change separately, but that
is inefficient, especially when making several changes to the same line,
e.g. when manipulating an XML file, these tend to have very long lines.
The current implementation assumes that the final state of the text
lines is what matters.

--
hundred-and-one symptoms of being an internet addict:
10. And even your night dreams are in HTML.

Bram Moolenaar

unread,
May 22, 2019, 5:13:55 PM5/22/19
to vim...@googlegroups.com, Paul Jolly

Paul Jolly wrote:

> > Up to you. You can merge the changes into one, keeping the lowest
> > changed line number and the total deleted/added lines, or you can deal
> > with each change separately.
>
> I'm not sure I see how the change can be handled separately (without
> carrying forward position changes) in my case because it's only the
> combined result of the two changes that tells me I also need to
> include line 3.
>
> The first change:
>
> {'lnum': 1, 'col': 12, 'added': 1, 'end': 2}
>
> tells me I need to change line 1 in my plugin's version of the buffer,
> and then I should insert line 2 from the current buffer state after
> line 1 in my plugin's version of the buffer.

Hmm, that is not right, since after this change the second line would be
the terminating quote.

> The second change:
>
> {'lnum': 2, 'col': 1, 'added': 1, 'end': 2}]
>
> tells me I also need to insert line 2 from the current buffer before
> line 2 in my plugin's version of the buffer.
>
> Both read from the buffer in the current state, which means I end up
> with two blank lines instead of a blank line followed by a line with a
> single backtick

I see. So one would argue that the second change makes the "end" lnum of
the first change invalid, thus the changes would need to be flushed
first.

> > Your loop has a clear problem, that if you get two changes, one deleting
> > a line and one adding a line, then the l:change.type will get the last
> > change type, which depends on the ordering, so that won't work. The
> > easiest would be to deal with each change separately. Thus just before
> > the "endfor".
>
> Thanks, there are I'm sure a few issues that need to be resolved given
> my new understanding of the way things work :)
>
> I think I've got an idea of how I should be handling this now.
>
> Thanks again for your patience and taking the time to walk me through it.

Thanks for reviewing with an actual implementation.

--
hundred-and-one symptoms of being an internet addict:
14. You start introducing yourself as "Jim at I-I-Net dot net dot au"

Paul Jolly

unread,
May 23, 2019, 4:53:02 AM5/23/19
to Bram Moolenaar, vim...@googlegroups.com
> Hmm, that is not right, since after this change the second line would be
> the terminating quote.

Indeed, but we don't ever see the buffer in that state.

> I see. So one would argue that the second change makes the "end" lnum of
> the first change invalid, thus the changes would need to be flushed
> first.

Yes, that's a much more articulate and succinct way of describing the issue.

So do you think that the last callback should in fact be two callbacks?


Paul

Paul Jolly

unread,
May 26, 2019, 2:16:34 AM5/26/19
to Bram Moolenaar, vim...@googlegroups.com
Hi Bram,

> So do you think that the last callback should in fact be two callbacks?

Thanks for your fix in v8.1.1389 and all the work before that for
listener_add - I've landed initial support for delta-based updates in
govim:

https://github.com/myitcv/govim/commit/ad260329614442948d653c270343cbf0e32d5f01

Will report back if I find any further issues in the wild.


Paul
Reply all
Reply to author
Forward
0 new messages