Any automatic bracket-insertion plugins not breaking undo?

115 views
Skip to first unread message

Benjamin Fritz

unread,
Jan 14, 2015, 11:37:15 AM1/14/15
to vim_use
A feature I've been missing ever since just before Vim 7.4 came out, is
the automatic closing of matched characters. E.g. if I insert abc(def I
want to get abc(def|) where '|' denotes my cursor. Then if I type the
closing ) I want my cursor to jump over the existing ) rather than
inserting a second one.

If that's all I wanted, some fairly simple mappings can do the job for
me. But, after inserting "abc(def[ghi])", I want to be able to undo that
insertion, and redo that insertion, and potentially even REPEAT that
insertion, as if I had typed it all out manually in one insert.

Previously, I used delimitMate for this.
contains a list of plugins for this, some of which in Vim 7.3 or earlier
accomplished my goal of keeping the undo sequence intact. But since Vim
7.4 these all broke.

Is there a plugin out there that has found a reliable way to do this?

I know there is an open pull request on delimitMate that supposedly
fixes undo/redo in many cases, but I gather there are still some cases
where it breaks, and I have the impression it's not ready for general
consumption yet.

Ben Fritz

unread,
Jan 15, 2015, 9:53:31 AM1/15/15
to vim...@googlegroups.com
On Wednesday, January 14, 2015 at 10:37:15 AM UTC-6, Ben Fritz wrote:
> A feature I've been missing ever since just before Vim 7.4 came out, is
> the automatic closing of matched characters. E.g. if I insert abc(def I
> want to get abc(def|) where '|' denotes my cursor. Then if I type the
> closing ) I want my cursor to jump over the existing ) rather than
> inserting a second one.
>
>
> If that's all I wanted, some fairly simple mappings can do the job for
> me. But, after inserting "abc(def[ghi])", I want to be able to undo that
> insertion, and redo that insertion, and potentially even REPEAT that
> insertion, as if I had typed it all out manually in one insert.
>

Incidentally, at the moment I'd settle for simply not breaking undo/redo. If I want to repeat something I normally can prepare that in advance by using <C-V> to avoid the mappings.

Christian Brabandt

unread,
Jan 16, 2015, 4:57:37 AM1/16/15
to vim...@googlegroups.com
That would need a change to the source, right? Did we ever talk about
how to prevent this or what would have to be done, to not break undo?
Is there an easy way to reproduce the issue without having to install
some plugins?

Best,
Christian

Ben Fritz

unread,
Jan 16, 2015, 10:55:13 AM1/16/15
to vim...@googlegroups.com
Pretty much any of the methods at http://vim.wikia.com/wiki/Automatically_append_closing_characters

The problem is, there is no way to move the cursor in insert mode, without breaking the undo sequence. Such a capability would allow both of these mappings:

inore ( ()<Left>
inore <expr> ) GetNextChar()==")" ? "\<Right>" : ")"

Alternatively, there is no way in insert mode to insert a character after the cursor, or delete the character after the cursor. Such a capability would allow:

inore <expr> ( ")".PutCharAfter(")")
inore <expr> ) GetNextChar()==")" ? DeleteNextChar().")" : ")"

At one point, there was a workaround that exploited a bug in setline() that allowed the undo/redo to work. You could use setline() to change the line without breaking undo sequence. I don't remember how repeat worked, but I think it involved an <Esc> mapping.

If either the abilities above (moving the cursor without breaking undo, or insert/delete after the cursor) were implemented as Vim insert-mode commands, I would consider dropping the use of a plugin and just writing my own mappings.

As it is, I tried the pull request I mentioned on delimitMate, and it seems to work for simple cases, but either visual-block mode messes it up. I have an unsatisfactory workaround I posted as a comment on that pull request that I'm using for the present.

Ben Fritz

unread,
Jan 16, 2015, 10:59:13 AM1/16/15
to vim...@googlegroups.com
On Friday, January 16, 2015 at 9:55:13 AM UTC-6, Ben Fritz wrote:
>
> Alternatively, there is no way in insert mode to insert a character after the cursor, or delete the character after the cursor. Such a capability would allow:
>
> inore <expr> ( ")".PutCharAfter(")")
> inore <expr> ) GetNextChar()==")" ? DeleteNextChar().")" : ")"
>

Someone here might suggest using <C-O> and an :undojoin operation inside those functions. And maybe that could work. That is basically the method used by the delimitMate pull request I keep mentioning. The problem with this method, is that the <C-O> triggers the special multi-line insert in visual block mode, and the rest of the insertion just affects one line. Perhaps just fixing this would allow this method to succeed?

Christian Brabandt

unread,
Jan 16, 2015, 4:43:01 PM1/16/15
to vim...@googlegroups.com, vim...@vim.org
Hi Ben!

On Fr, 16 Jan 2015, Ben Fritz wrote:

> The problem is, there is no way to move the cursor in insert mode,
> without breaking the undo sequence. Such a capability would allow both
> of these mappings:
>
> inore ( ()<Left> inore <expr> ) GetNextChar()==")" ? "\<Right>" : ")"
>
> Alternatively, there is no way in insert mode to insert a character
> after the cursor, or delete the character after the cursor. Such a
> capability would allow:
>
> inore <expr> ( ")".PutCharAfter(")") inore <expr> ) GetNextChar()==")"
> ? DeleteNextChar().")" : ")"
>
> At one point, there was a workaround that exploited a bug in setline()
> that allowed the undo/redo to work. You could use setline() to change
> the line without breaking undo sequence. I don't remember how repeat
> worked, but I think it involved an <Esc> mapping.
>
> If either the abilities above (moving the cursor without breaking
> undo, or insert/delete after the cursor) were implemented as Vim
> insert-mode commands, I would consider dropping the use of a plugin
> and just writing my own mappings.
>
> As it is, I tried the pull request I mentioned on delimitMate, and it
> seems to work for simple cases, but either visual-block mode messes it
> up. I have an unsatisfactory workaround I posted as a comment on that
> pull request that I'm using for the present.

Hm, we already have special cases <C-[Left|Right]> and <S-[Left|Right]>
to move by word|WORD. So how about making <M-Left> and <M-Right> move
without breaking undo?
https://github.com/chrisbra/vim-mq-patches/blob/master/move_cursor_without_breaking_undo


Best,
Christian
--
Wer tugendhaft lebt und handelt, der legt seinen Adel an den Tag.
-- Giovanni Boccaccio, Das Dekameron
move_cursor_without_breaking_undo.diff

Bram Moolenaar

unread,
Jan 16, 2015, 5:10:16 PM1/16/15
to Christian Brabandt, vim...@googlegroups.com, vim...@vim.org
It's very likely there will be a request for other movements, e.g. a
word back. How about a prefix for movement commands? Something
starting with CTRL-G. It's several keys to type, but I assume this will
mostly be used in mappings.


--
From "know your smileys":
=):-) Uncle Sam

/// 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 ///

Christian Brabandt

unread,
Jan 16, 2015, 5:55:42 PM1/16/15
to vim...@googlegroups.com, vim...@vim.org
Hi Bram!
<C-Right> and <S-Right> does this already.

> How about a prefix for movement commands? Something
> starting with CTRL-G. It's several keys to type, but I assume this will
> mostly be used in mappings.

I thought about that. But my fear is, that this will allow to jump
anywhere in insert mode and I think this would probably break undo (a
problem we have just fixed with <c-r>= in insert mode before 7.4).
So I did intentionally just allow <M-Left> and <M-Right> in the hope,
that this will still allow proper undo and will cover most of the snip
plugin use-cases.

Best,
Christian
--
Es ist ebenso fehlerhaft, nicht überall die Sinnlichkeit, als überall
ihren Sieg voranzusetzen.
-- Jean Paul (eig. Johann Paul Friedrich Richter)

Ben Fritz

unread,
Jan 16, 2015, 10:24:10 PM1/16/15
to vim...@googlegroups.com, vim...@googlegroups.com, vim...@vim.org
On Friday, January 16, 2015 at 4:55:44 PM UTC-6, Christian Brabandt wrote:
> Hi Bram!
>
> On Fr, 16 Jan 2015, Bram Moolenaar wrote:
>
> >
> > Christian Brabandt wrote:
> > > Hm, we already have special cases <C-[Left|Right]> and <S-[Left|Right]>
> > > to move by word|WORD. So how about making <M-Left> and <M-Right> move
> > > without breaking undo?
> > > https://github.com/chrisbra/vim-mq-patches/blob/master/move_cursor_without_breaking_undo
> >
> > It's very likely there will be a request for other movements, e.g. a
> > word back.
> > How about a prefix for movement commands? Something
> > starting with CTRL-G. It's several keys to type, but I assume this will
> > mostly be used in mappings.
>
> I thought about that. But my fear is, that this will allow to jump
> anywhere in insert mode and I think this would probably break undo (a
> problem we have just fixed with <c-r>= in insert mode before 7.4).
> So I did intentionally just allow <M-Left> and <M-Right> in the hope,
> that this will still allow proper undo and will cover most of the snip
> plugin use-cases.
>

I'm definitely going to try the patch, but I think I'd prefer a different prefix also. If it works this is exactly what is needed for auto-close, without all the fuss of a big plugin (unless you want that...)

The meta-keys have traditionally been "safe" for mapping in Vim without stepping on existing features, I think keeping it that way is a good idea.

And I agree these will mostly be used in mappings.

CTRL-G_u breaks undo sequence. What about CTRL-G_U keeping undo sequence unbroken?

It's ok for the short term if just CTRL-G_U_<Left> and CTRL-G_U_<Right> are supported to start with...with potential for more if there is a need for them.

I'd probably restrict to movement within a single line, though.

Christian Brabandt

unread,
Jan 17, 2015, 8:41:43 AM1/17/15
to vim...@googlegroups.com, vim...@googlegroups.com, vim...@vim.org
Hi Ben!
Okay, above URL contains an updated version, that won't break undo for a
single <left>/<break> movement after <Ctrl-G>U have been pressed (but
only, if the cursor stays in the same line).


Best,
Christian
--
Wußten Sie schon...
... daß sich die alten Rittersleut' ihre Morgensterne auch
am Abend auf die Schädel droschen?
move_cursor_without_breaking_undo

Nikolay Pavlov

unread,
Jan 17, 2015, 12:09:24 PM1/17/15
to vim...@googlegroups.com, vim-dev Mailingliste
Just `w` and `b` with left and right will already allow to go anywhere, regardless of &whichwrap option.

But if you are going to use the same code for <M-Left> as for <Left> then adding `[` and `]` &whichwrap is just enough for <M-Left> and <M-Right> to go anywhere as well. If you want to restrict motions to a single line <M-Left>/<M-Right> should ignore &whichwrap. Also given that they will be used mostly in mappings which can use <expr> allowing <M-Left>/<M-Right> is just as good as allowing going anywhere in the line plugins wants to go and it may be easier to create code that does random motion after pressing <C-g>, but intentionally breaks undo if this motion happens to end on the other line.
 

Best,
Christian
--
Es ist ebenso fehlerhaft, nicht überall die Sinnlichkeit, als überall
ihren Sieg voranzusetzen.
                -- Jean Paul (eig. Johann Paul Friedrich Richter)

--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bram Moolenaar

unread,
Jan 17, 2015, 2:20:08 PM1/17/15
to Ben Fritz, vim...@googlegroups.com, vim...@googlegroups.com, vim...@vim.org

Ben Fritz wrote:

> ------=_Part_1625_1020240385.1421465043545
> Content-Type: text/plain; charset=UTF-8
Since undo works by saving the line before changing it, restricting the
"keep undo" modifier to only work when the cursor remains in the same
line should work. It's also fairly easy to understand, instead of
making a list of specific commands for which undo can be kept.

Note that "." to repeat the insert may not always work properly. Need
to check that.

--
From "know your smileys":
(X0||) Double hamburger with lettuce and tomato

Ben Fritz

unread,
Jan 21, 2015, 12:07:00 PM1/21/15
to vim...@googlegroups.com, fritzo...@gmail.com, vim...@googlegroups.com, vim...@vim.org
On Saturday, January 17, 2015 at 1:20:04 PM UTC-6, Bram Moolenaar wrote:
>
> Since undo works by saving the line before changing it, restricting the
> "keep undo" modifier to only work when the cursor remains in the same
> line should work. It's also fairly easy to understand, instead of
> making a list of specific commands for which undo can be kept.
>
> Note that "." to repeat the insert may not always work properly. Need
> to check that.
>

Wow. WOW! Thanks for the patch, Christian!

I tested with some fairly simple mappings, and it works perfectly!

Even repeating these mappings with '.' and inserting in visual-block insert works without issue as far as I can tell!

inoremap ( ()<C-G>U<Left>
inoremap (<SPACE> (<SPACE><SPACE>)<C-G>U<Left><C-G>U<Left>
inoremap <expr> ) strpart(getline('.'), col('.')-1, 1) == ")" ? "\<C-G>U\<Right>" : ")"

The only thing that won't work smoothly is a mapping to insert a new indented line between pairs, but it's only repeat that is broken there, and I can live with that:

inoremap <silent> {<CR> {<CR>}<Esc>zv:undojoin<CR>O

I even got a smart mapping done for paired " characters that also works with Vim comments...but that uses some complicated regex so I'll not confuse the issue here :-)

Ben Fritz

unread,
Mar 25, 2015, 3:02:37 PM3/25/15
to vim...@googlegroups.com, fritzo...@gmail.com, vim...@googlegroups.com, vim...@vim.org
On Wednesday, January 21, 2015 at 11:06:53 AM UTC-6, Ben Fritz wrote:
> On Saturday, January 17, 2015 at 1:20:04 PM UTC-6, Bram Moolenaar wrote:
> >
> > Since undo works by saving the line before changing it, restricting the
> > "keep undo" modifier to only work when the cursor remains in the same
> > line should work. It's also fairly easy to understand, instead of
> > making a list of specific commands for which undo can be kept.
> >
> > Note that "." to repeat the insert may not always work properly. Need
> > to check that.
> >
>
> Wow. WOW! Thanks for the patch, Christian!
>
> I tested with some fairly simple mappings, and it works perfectly!
>

Updated patch to apply cleanly (with line offsets but no fuzz) to the latest source.
move_cursor_without_breaking_undo.patch

Jacob Niehus

unread,
Aug 3, 2015, 6:48:49 PM8/3/15
to vim_dev, fritzo...@gmail.com, vim...@googlegroups.com, vim...@vim.org
I would love to see this in mainline as well. I've been using it quite extensively for four months now with these maps to move the cursor left/right by word/WORD and to the first column, first non-blank, and last column without breaking undo:

" Move cursor in insert mode without splitting undo
inoremap <Left> <C-g>U<Left>
inoremap <Right> <C-g>U<Right>
inoremap <expr> <Home> col('.') == match(getline('.'), '\S') + 1 ?
\ repeat('<C-g>U<Left>', col('.') - 1) :
\ (col('.') < match(getline('.'), '\S') ?
\ repeat('<C-g>U<Right>', match(getline('.'), '\S') + 0) :
\ repeat('<C-g>U<Left>', col('.') - 1 - match(getline('.'), '\S')))
inoremap <expr> <End> repeat('<C-g>U<Right>', col('$') - col('.'))
imap <C-b> <Home>
imap <C-e> <End>

Here's an update to the patch that applies cleanly to Vim 7.4.803.

-Jake
move_cursor_without_breaking_undo_vim-v7-4-803.patch

Bram Moolenaar

unread,
Aug 4, 2015, 8:42:32 AM8/4/15
to Jacob Niehus, vim_dev, fritzo...@gmail.com, vim...@googlegroups.com, vim...@vim.org
This appears to be OK to include. However, a test is missing.
Especially that using this at the end of the line, then a cursor-right
should work as before, because it moves to the next line.


--
You were lucky to have a LAKE! There were a hundred and sixty of
us living in a small shoebox in the middle of the road.

Christian Brabandt

unread,
Aug 4, 2015, 2:46:29 PM8/4/15
to vim_dev, vim...@googlegroups.com, vim...@vim.org

On Di, 04 Aug 2015, Bram Moolenaar wrote:

> Jacob Niehus wrote:
>
> > I would love to see this in mainline as well. I've been using it quite
> > extensively for four months now with these maps to move the cursor
> > left/right by word/WORD and to the first column, first non-blank, and
> > last column without breaking undo:
> >
> > " Move cursor in insert mode without splitting undo
> > inoremap <Left> <C-g>U<Left>
> > inoremap <Right> <C-g>U<Right>
> > inoremap <expr> <Home> col('.') == match(getline('.'), '\S') + 1 ?
> > \ repeat('<C-g>U<Left>', col('.') - 1) :
> > \ (col('.') < match(getline('.'), '\S') ?
> > \ repeat('<C-g>U<Right>', match(getline('.'), '\S') + 0) :
> > \ repeat('<C-g>U<Left>', col('.') - 1 - match(getline('.'), '\S')))
> > inoremap <expr> <End> repeat('<C-g>U<Right>', col('$') - col('.'))
> > imap <C-b> <Home>
> > imap <C-e> <End>
> >
> > Here's an update to the patch that applies cleanly to Vim 7.4.803.
>
> This appears to be OK to include. However, a test is missing.
> Especially that using this at the end of the line, then a cursor-right
> should work as before, because it moves to the next line.

Here is an update, including 3 tests.

Mit freundlichen Grüßen
Christian
--
Viele Menschen versäumen das kleine Glück, während sie auf das große
vergebens warten.
-- Pearl Sydenstricker Buck (Pseudonym: Sedges, John)
move_cursor_without_breaking_redo

Bram Moolenaar

unread,
Aug 4, 2015, 3:27:36 PM8/4/15
to Christian Brabandt, vim_dev, vim...@googlegroups.com, vim...@vim.org

Christian Brabandt wrote:

> On Di, 04 Aug 2015, Bram Moolenaar wrote:
>
> > Jacob Niehus wrote:
> >
> > > I would love to see this in mainline as well. I've been using it quite
> > > extensively for four months now with these maps to move the cursor
> > > left/right by word/WORD and to the first column, first non-blank, and
> > > last column without breaking undo:
> > >
> > > " Move cursor in insert mode without splitting undo
> > > inoremap <Left> <C-g>U<Left>
> > > inoremap <Right> <C-g>U<Right>
> > > inoremap <expr> <Home> col('.') == match(getline('.'), '\S') + 1 ?
> > > \ repeat('<C-g>U<Left>', col('.') - 1) :
> > > \ (col('.') < match(getline('.'), '\S') ?
> > > \ repeat('<C-g>U<Right>', match(getline('.'), '\S') + 0) :
> > > \ repeat('<C-g>U<Left>', col('.') - 1 - match(getline('.'), '\S')))
> > > inoremap <expr> <End> repeat('<C-g>U<Right>', col('$') - col('.'))
> > > imap <C-b> <Home>
> > > imap <C-e> <End>
> > >
> > > Here's an update to the patch that applies cleanly to Vim 7.4.803.
> >
> > This appears to be OK to include. However, a test is missing.
> > Especially that using this at the end of the line, then a cursor-right
> > should work as before, because it moves to the next line.
>
> Here is an update, including 3 tests.

Thanks. So who wrote the original patch then?


--
FATHER: Who are you?
PRINCE: I'm ... your son ...
FATHER: Not you.
LAUNCELOT: I'm ... er ... Sir Launcelot, sir.
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

Christian Brabandt

unread,
Aug 4, 2015, 3:29:02 PM8/4/15
to vim_dev, vim...@googlegroups.com
On Di, 04 Aug 2015, Bram Moolenaar wrote:

> Thanks. So who wrote the original patch then?

I believe that was me.

Best,
Christian
--
Lieber Lügen als kurze Beine.
Reply all
Reply to author
Forward
0 new messages