Vim "trigger abbreviation" command i_CTRL-] sometimes inserts literal <C-]> - vim

In insert mode, the command CTRL-] expands an abbreviation without entering a space. So if you set :abbrev foo bar, typing foo<C-]> will change foo to bar.
But what happens if you type bar<C-]> when you have not set any abbreviations for bar?
In one version of vim (7.3.754), the CTRL-] is ignored. In other versions that I have access to (7.3.0 and 7.2.411), a literal character ^] is inserted, so you get bar^].
Is this a bug that was fixed? Or is the different treatment because of different compiling options? And is there a way to avoid inserting extraneous ^]s when I must work in these older vims?

As discussed in the comments, the behavior changed in 7.3.489, which fixed a related bug that prevented <C-]> from expanding an abbreviation if the <C-]> was inserted via a mapping.
Funny enough, the reason I came across this was because I was defining a mapping
inoremap <CR> <C-]><CR><C-U>g
The last three characters simply create a new undo point after each <CR> pressed in insert mode. Under normal circumstances, pressing enter will also expand an abbreviation if applicable, but not if <CR> has been remapped. The <C-]> was to forcibly expand abbreviations. It turns out this is the exact mapping that led Yichao Zhou to write the above bugfix.
It isn't a perfect workaround, but by using if has("patch489") when defining the map, you can at least degrade gracefully to older versions of vim. Pressing enter will not expand abbreviations, but at least it won't insert <C-]>.

Related

Insert mode default keys in vim

The following items are useful to me in editing text, and I was wondering if vim had something for this built out of the box (though I didn't see it on the https://vimhelp.org/index.txt.html#index.txt page), or I had to create mappings for it:
Forward-delete a character. This is X in normal mode.
Forward-delete all text to the right of the cursor on the line. This is the inverse of ctrl-u.
Are either of these mappings available? And if not, are there 'standard' mappings for this that are common (for example, how it might be done in another unix program).
Note that this is the keyboard I have -- there is only one delete key (which acts like a normal backspace key) and there is no backspace key:
Note: for forward-delete, I am currently mapping ctrl-d as:
"Ctrl-d to forward-delete when in insert or command mode
noremap! <C-d> <Delete>
However, this interferes with the tab in insert mode (which I don't use) and the help-options in command mode (which I do use!) so I may have to modify this later, or hopefully someone suggests a better solution.
though I didn't see it on the https://vimhelp.org/index.txt.html#index.txt page
If you can't find it in the documentation, then it doesn't exist.
You can use fn+delete for "Forward-delete a character".
"Forward-delete all text to the right of the cursor on the line" is ctrl+k in MacOS, but Vim has its own use for that combo, :help i_ctrl-k so it is up to you to create a mapping for it.
Something like:
inoremap <key> <C-o>ld$

Doing an insert remapping for commenter

I am trying to do a remapping when I'm in insert mode to insert a comment but am having a tough time figuring out what all the keys map to. What I am trying to do is:
:inoremap leadercspace ==> escleadercspacei
Basically, if I'm in insert mode I want to get out of insert mode to insert the comment (leader+c+space) and then go back into insert mode.
What would the correct :inoremap mapping for this be? What I have right now is:
:inoremap <leader>c<space> <Esc><Leader>c<space>i
But this doesn't seem to work (at least the latter half of it -- it does seem to be executing the mapping command). Note: the plugin I'm trying to remap is:
https://github.com/preservim/nerdcommenter
[count]<leader>c<space> |NERDCommenterToggle|
Toggles the comment state of the selected line(s). If the topmost selected line is commented, all selected lines are uncommented and vice versa.
From vim doc (:help nore):
Disallow mapping of {rhs}, to avoid nested and recursive mappings
In other words, the nore part forbids mapping to be applied to the rhs (right hand side).
So in your case, the <Esc><Leader>c<space>i doesn't trigger the VimCommenter mapping for that reason.
To allow recursion, you can take off the nore:
:imap <leader>c<space> <Esc><Leader>c<space>i
My recommendation is that, instead of creating an insert-mode mapping for this purpose, just use the native Ctrl+O mapping to run a single Normal mode command from Insert mode.
Assuming your leader key is set to the default \, you can use:
Ctrl+O, \, c, Space
You'll be left in Insert mode at the end of this sequence.
The advantages of this approach over an insert mode mapping are:
You don't need any extra configuration, since Ctrl+O is a native Vim command.
This works for any Normal mode command, so you don't need to add extra mappings for other commands you might want to be able to access from Insert mode.
Adding a multi-character mapping in Insert mode starting with <Leader> means Vim will always pause and hold if you insert the leader character. In this case, it will also pause when you insert <Leader> and c. I find that avoiding this kind of mappings of otherwise printable characters is usually best.

How to efficiently add parentheses or a string in vim?

In traditional text editors, whenever I needed to open a string or parentheses and type something between it I used to do:
Type () or ""
Press left
Type in what I need
Press right
But in vim (that is if I followed the vim way) the process becomes quite tedious as I have to enter the normal mode to move a whole bunch of times:
Type () or ""
Press <ESC>
Press i
Type what I need
Press <ESC>
Press l
Press a
If it is not a good practice to use the arrow keys at any time, is there a more efficient way of doing this kind of task in vim?
It is actually quite easy to automatically append those closing characters in a mapping, and put your cursor where you want it. The trick is to do that, without also messing up the undo/redo/repeat actions. The problem is that cursor movement commands in insert mode will break the "undo sequence" so that any change you make after moving the cursor is undone separately from changes made before moving the cursor.
Warning: the following information may become dated
There are plenty of plugins available to automatically append these characters (see the partial list at the Vim wiki page for appending closing characters), and prior to Vim 7.4, some of them even had complicated workarounds for keeping the undo sequence intact. Unfortunately, they all relied on a bug in Vim that got fixed in version 7.4 for this.
A patch is available to add a cursor movement that does not break undo, so if you want to compile Vim yourself, you can grab that patch and use mappings like the following (no plugin required!) to do what you want:
inoremap ( ()<C-G>U<Left>
inoremap <expr> ) strpart(getline('.'), col('.')-1, 1) == ")" ? "\<C-G>U\<Right>" : ")"
These mappings will insert "()" when you type an opening (, placing the cursor in between the parentheses. When you type ')' and there is already a closing ')' after the cursor, Vim will skip over the parenthesis instead of inserting a new one. Cursor movement is preceded by <C-G>U which is the feature the aforementioned patch adds, allowing the following cursor movement to not break the undo sequence (as long as the movement is all in a single line).
As of Vim 7.4.663, this patch has still not been officially included.
No. Doing it in Vim is exactly the same as in your "traditional" editor:
Type () or ""
Press left
Type in what you need
Press right
But… why don't you type the opening character, what you want inside the pair and then the closing character?
Type ( or "
Type what you need
Type ) or "
Too simple?
I think using arrow keys to move around is bad practice in normal mode but in your case; moving one space while in insert mode, I would hazard to say using the arrow keys is probably best practice.
That being said if you are dead set on avoiding them you could use <i_ctrl-o>.
:help i_ctrl_o
CTRL-O execute one command, return to Insert mode *i_CTRL-O*
So, while in insert mode, you could type: ()<ctrl-o>h<xxx><ctrl-o>l, where <xxx> is whatever you want in the brackets.
Unfortunately that doesn't work if you cursor is on the last character of the line, which if you are typing it most likely is.
To solve that problem do :set virtualedit+=onemore or add it to your ~/.vimrc file.
Note that this solution is more keystrokes than simply using the arrow keys but you don't need to move your hands away from the home row so it may be faster anyway.

How and when would the ":ab" mapping be triggered?

The ab family of command (iab and cab) could be a huge time saver. However, it is yet unclear when will it be triggered. For example, with the following settings in _vimrc:
iab sj <c-r>=strftime("20%y-%m-%d %H:%M:%S")<cr>
cab cab drop C:\users\llinfeng\dropbox
For c and i being "mode-indicator", a complete time-tag will be inserted while in Insert mode if one presses <space> (or <ESC>, <C-[>) right after typing sj. For the second mapping, the full directory will be inserted in Command-line mode if one hits \ (or <space>) right after typing drop.
So, it seems that, for iab, the following characters will trigger the expansion of an abbreviation defined through iab-mapping:
<space>
<esc> (or escape in general)
Ideally, one may guess the <tab> key shall also trigger such expansion. However, for my case (set expandtab tabstop=4 shiftwidth=4, i.e. one "tab" key == 4 spaces"), no expansion would have been triggered.
Then, for cab, the following may work to trigger the expansion:
\
<space>
??
What is confusing to me is that: what is the thing "laying at the core" that triggers the expansion of an abbreviation defined through ab-mapping? And, does there exist a complete list of keys that could trigger such expansion, for both Insert mode and Command-line mode?
Thank you!
All the best,
-Linfeng
It's all exhaustively documented in the vimdocs:
http://vimdoc.sourceforge.net/htmldoc/map.html#abbreviations
Specifically:
An abbreviation is only recognized when you type a non-keyword character.
This can also be the <Esc> that ends insert mode or the <CR> that ends a
command. The non-keyword character which ends the abbreviation is inserted
after the expanded abbreviation. An exception to this is the character <C-]>,
which is used to expand an abbreviation without inserting any extra
characters.
But it goes on a good few paragraphs detailing precisely what conditions trigger different types of abbreviations. Specifically watch out for the difference between
full-id (foo, g3, -1)
end-id (#i, ..f, $/7)
non-id (def#, 4/7$)
abbreviations

Issue with smartindent in Vim

In vim with smartindent on:
Press Enter after say an if-statement
Type in {
Press Enter twice
Type in }
If you hit ↑ and go to the previous line, indentation is removed from the blank line.
Even the vim documentation says that:
If you do not type anything on the new line except <BS> or CTRL-D and then type <Esc>, CTRL-O or <CR>, the indent is deleted again.
Is there any way to keep this indentation and not have it deleted?
Use Shift+S to start editing on a blank line (from command mode, obviously). This will start your cursor off with the expected level of indentation.
Another doesn't-answer-the-question-as-asked-but-is-a-better-solution-overall:
When typing an opening brace in insert mode, this will insert a matching set of braces
and leave the cursor on a new line in the middle.
:imap { {<CR>}<Esc>O
Similarly, this will auto-insert matching parens and square brackets.
:imap ( ()<Left>
:imap [ []<Left>
(Strip off the leading : when adding to vimrc.)
As I commented on Victor's answer, changing Vim's indentation behavior will leave "empty" lines containing extraneous spaces throughout your files. IMO, this is completely intolerable.
When this happens to me, I sometimes use ddko (or ddO) to delete the line without enough spaces and open a new line with the correct indent. Or, I'll just press A and then Tab enough times to get to the correct indent.
the article here talks about you're very same problem, and what to put in vimrc to fix it.
inoremap <CR> <CR><Space><BS>
nnoremap o o<Space><BS>
nnoremap O O<Space><BS>
I havn't exactly tested this tho.
also the same article links to a shorter alternate solution.
My preferred method is {<CR>}<esc>shift+o as it outpaces {<CR><CR>}<esc>k shift+s by several strokes. I get in a rut with it, though, and end up just using o or O to grab new, properly-indented lines off an empty when I should be using S.
That is, set up your bracing structure and open line-above:
if (true) {
}//cursor here, press shift-o
And you get the indenting you expect.
The open-above trick isn't any fewer keypresses than <up><end><cr>, but with escape remapped and shift being chorded, you can throw it in quite fast.
Also, don't forget your manual indent reset and block-movement. If you're inside a mangled curly brace block, simply use ={ (or =i{ if you're on top of one of the braces). I use that when I have a Good Idea that needs to see text asap, and I don't worry about any formatting frippery until I take a breather.

Resources