How to imap a character in Vim based on previous character - vim

I have created the following mapping to simulate behaviour in some IDEs where when you insert { after a function declaration like foo() a closing } and empty row is inserted automatically and cursor is set to the empty row on tabbed position.
:imap { {<CR><CR>} <up><Tab>
This of course does this behavior when I insert { in any context. How do I do it based on the previously inserted character? Must be a vim script function involved?
Note: I do not want to use external vim plugins.

IDEs usually do this expansion after typing {<CR>, which is easy to do in vimscript:
:imap {<CR> {<CR><CR>} <up><Tab>
This will not expand if you keep on typing other things on the same line.
The caveat is that there's a small delay when typing a { with this mapping. See the 'timeout' and 'timeoutlen' options for details.

These code snippets give you the character just before and just after the cursor when in insert mode:
let previous_character = getline(".")[col(".")-2]
let next_character = getline(".")[col(".")-1]
You can use them in an <expr> mapping:
:inoremap <expr> { getline(".")[col(".")-2] == " " ? "{^M}^OO" : "{"
The pointless mapping above checks if the character before the cursor is a space before deciding if it inserts a { or an expanded {}.
If you want a "smart" mapping you won't be able to avoid writing one or more functions. The one I use, for example, is 69 lines long.

You'll find multiple approaches and a list of plugins on the Automatically append closing characters Vim Tips Wiki page. Note that though there are simplistic solutions, they usually have some downsides, and the whole approach is unfortunately broken in Vim 7.4 with regards to undo.

Related

Using placeholders in vim

Given a vim document with multiple occurrences of a specific placeholder, say <%%>, I want to be able to jump to the next placeholder from the beginning of the document: More explicitly, if the document is given by
$\frac{<%%>}{<%%>}$
I want to press a key such that the first placeholder gets removed, i.e. we have
$\frac{}{<%%>}$
where the cursor is at the position of the placeholder and vim is in insert mode.
I'm aware of the vim-latex plugin which implements such a behaviour but only need this one feature. I tried to use the /-search of vim but didnt get the cursor position right.
Thanks in advance for any advice.
lh-brackets provides this feature -- actually vim-latex placeholder system has been inspired by lh-brackets one.
The idea to implement this feature, is:
to look for the pattern of the placeholder -- prefer search() to know whether something has been found: no selection shall be done otherwise
Actually doing it correctly may require a couple of calls to searchpair() to handle the case where the cursor is in the middle of the placeholder, see lh-brackets code as search(..., 'c') is not enough;
select this pattern -- v + movement 3<right> for instance
and finally either go into SELECT-mode (gh <c-g>) or remove the placeholder and go into insert mode (s)
If your placeholder pattern is exactly <%%>, it'll be quite simple to implement.
" I factorize common code without introducing the exact keybinding
" NB: we have to use the ancestor of map-<expr> as the later doesn't
" permit to move the cursor -> we execute the expression register: :h #=
" NB: As said earlier a correct implementation would require to call searchpair()
" twice in case the cursor is within a placeholder, see lh-brackets code
nnoremap <silent> <Plug>(jump-next) #=(search('<%%>') > 0 ? "v3l<c-g>" : '')<cr>
vmap <silent> <Plug>(jump-next) <c-\><c-n><Plug>(jump-next)
imap <silent> <Plug>(jump-next) <c-\><c-n><Plug>(jump-next)
" Tests shall be done in a real plugin before binding to the chosen shortcut: µ, <f3>, <c-j>, <tab>...
nmap <silent> µ <Plug>(jump-next)
vmap <silent> µ <Plug>(jump-next)
imap <silent> µ <Plug>(jump-next)
If sometimes it could become <%somestring%>, then I would definitively recommend using lh-brackets or any snippet engine that already takes care of this -- for instance, mu-template would permit to use your exact snippets/templates by changing locally the default placeholder characters with VimL: let s:marker_open = '<%' +
VimL: let s:marker_close = '%>' (I'm also maintaining mu-template which depends on lh-brackets).
NB: lh-brackets also provides surrounding (non intrusive), and bracket pairs insertion (can be deactivated: add :let g:cb_no_default_brackets = 1 in your .vimrc)
Using a macro might help.
In your example, use /<%%> to search for your placeholder. Then gg will take you at the beginning of the document.
Then start the macro with qa for instance. Go to the next occurrence of your placeholder with n. Then, ca< will remove the placeholder. C-o q will stop recording, while keeping you in insertion mode.
To go to and replace the next placeholder, just do #a (execute the macro stored in register a)
Does this mapping help?
:nmap %% /<%%><cr>ni
It executes a search (/<%%><cr>), repeats the search with n to skip the 1st placeholder and goes to the second. Then it switches (i) to Insert Mode.

Show result instead of X with incomplete mapping in vim

In my file at ~/.vim/ftplugin/tex/insert.vim I have the following mappings
; <Nul> is ctrl-space
imap <Nul> \,
imap <Nul><Nul> ~
imap <Nul><Nul><Nul> \enspace
imap <Nul><Nul><Nul><Nul> \quad
imap <Nul><Nul><Nul><Nul><Nul> \qquad
This allows me to insert spaces of different width by pressing ctrl-space, with each press making the whitespace longer. When I type the first ctrl-space, I get an X and it waits for me to see if I wanted to input more ctrl-spaces or if I'm done.
I would like to know if there's a way to show what the space will be, either input it directly to the buffer and change it if it's pressed again, or put it in the status bar until I'm done.
A way I think this could be possible was to check if the chars before the cursor are \, and if they are replace it with the next space, and if it's not, check the rest of the spaces I can input and finally input a \, if none of the spaces are there. I just don't know how to accomplish such a keymap.
Your current map solution is based on the mapping timeout. Until that happens, Vim is waiting for more input, and nothing has been inserted yet. In order to later revise what got inserted, you need to completely replace the approach.
This could be implemented with :map <expr> <Nul>, checking what's before the cursor, and then returning <BS> characters to wipe that followed by the replacing text.
With the mjbrownie/swapit plugin, you'll get that "toggling through alternatives" functionality (using <C-a> / <C-x>) for free: Just put the following into ~/.vim/ftplugin/tex/swapit.vim:
SwapList spaces \, ~ \enspace \quad \qqad
You can keep your original <Nul> mappings to quickly insert when you know what you want (or for the initial insert), and use swapit only to revise later on.

How to insert at the very left of the line in vim?

Imagine I am editing this code in vim, and wish to comment out the bar(); line:
while (foo()) {
bar();
baz();
}
If I press I# while on that line, I get this:
while (foo()) {
#bar();
baz();
}
However, our coding standards say I should have done this:
while (foo()) {
# bar();
baz();
}
I can work around this by pressing 0i# instead (or even putting map I 0i in my .vimrc for a more permanent fix) but this is not repeatable with . as it just repeats the i rather than the 0i.
Many other editors have options to make Home not be "smart" and just go to column 0 rather than trying to work with the indentation. I've tried searching the docs, but have drawn a blank — is there a way to do this in vim?
Alternatively, is there a way to make the bound command atomic, so that repeating it with . repeats the whole thing rather than the last command of the bound sequence?
Thanks
Use gI instead of I.
From :help gI :
gI
gI Insert text in column 1 [count] times. {not in Vi}
You're looking for the gI command. In the excellent and comprehensive help, you would have found that just below the entry for :help I.
What you really want is a commenting plugin like commentary (which I use), Nerd Commenter, EnhCommentify, tComment, ..., etc. However from what I can tell only EnhCommentify has an option to respect indention or not (g:EnhCommentifyRespectIndent).
If you already have a commenting plugin that you like and it doesn't work the way you like I suggest you open an issue via the plugin's issue tracker and request the option.
If you want to skip the plugin then you use these quick n' dirty mappings inspired by commentary:
nnoremap <expr> gcc getline('.') =~ '^#' ? '0"_xw' : "gI#\<esc>w"
xnoremap <expr> gc ':norm! ' . (getline("'<") =~ '^#' ? '0"_x' : "gI#") . "\<cr>"
Use the home key to go to the beginning of the line and then insert

How to change Vim insert mode mappings behavior?

I have insert-mapped in a script the pattern date<Tab> to insert the current date in the format YYYY-MM-DD.
inoremap <script> <silent> <buffer> date<Tab> <C-R>=strftime("%Y-%m-%d")<CR>
When I start typing the pattern in insert mode, instead of displaying the full pattern before replacing it by the date string only when I press Tab, Vim displays only the last character typed and it is pretty anoying when I don't want to use it. If I'm typing 'date', for instance, this is what Vim displays ('|' is the cursor representation):
|d
|a
|t
data|
Also, I have installed the Snipmate vim plugin which makes use of the Tab key to replace snippets by code templates and when I'm typing a snippet, it doesn't behave like I described before. What Snipmate does is to map only the Tab key and when the key is pressed, it gets the previous word and check if it matches one of its snippets.
That said, I will leave two questions and the answer to one of them solves my problem:
Is there a way to configure Vim no display the full pattern before changing it to its mappings?
Can I have two plugins using the same mapping? Like if I map the Tab key too and whenever the word before the cursor is 'date' my plugins acts and Snipmate acts in the other cases.
First answer is no.
Second is also no, but it can be emulated:
Generic way is the following (requires frawor):
" plugin/tab.vim
execute frawor#Setup('0.0', {'#/mappings': '0.0'})
" Make sure that mappings were set up
runtime! after/plugin/snipMate.vim
" Get information about already existing mapping
" (it was defined by snipmate)
let s:snipmap=s:_r.map.maparg('<Tab>', 'i', 0)
" Create a new mapping with unique lhs
let s:snipmap.lhs='<SNR>'.s:_sid.'_OldSnipMap'
call s:_r.map.map(s:snipmap)
function s:F.insdate()
if getline('.')[:(col('.')-1)][-4:] is# 'date'
return repeat("\<BS>", 4).strftime("%Y-%m-%d")
else
" Here is the magic: I have a choice to either use remappable mapping
" or <C-\><C-o>:call feedkeys()<CR> workaround for nore mapping
return "\<C-\>\<C-o>:call feedkeys(\"\\<SNR>".s:_sid."_OldSnipMap\")\n"
endif
endfunction
call s:_f.mapgroup.add('Tab', {'tab': {'lhs': '<Tab>', 'rhs': s:F.insdate, 'mode': 'i'}})
Note that in your example you don't map <Tab>, you map date<Tab> so it does not interfer with snipmate mapping. Above code uses the same approach as IMAP plugin: when last key of {lhs} is pressed, check whether previous keys are in the buffer. If they are remove them and insert {rhs} instead. Thus you can type date<Tab> no matter how slow and it will work.
Note 2: it is the generic way. You can drop frawor dependency and most of the code by simply looking at <Tab> {rhs}:
function s:Insdate()
if getline('.')[:(col('.')-1)][-4:] is# 'date'
return repeat("\<BS>", 4).strftime("%Y-%m-%d")
else
return "\<C-g>u\<C-r>=TriggerSnippet()\n"
endif
endfunction
inoremap <Tab> <C-r>=<SID>Insdate()<CR>
As I know, there seems no such setting to "turn off" the "searching map" status, which vim will eat all the char you type if it is part of some map in keymap matching.
Vim only can bind one key to a specific action, so there is no way to make a key do two thing as you may wish. On the other hand, you can configure "snipmate" to use other key to do the "expand" action. And that's should be a usually way when you meet the key conflict problem. Alternatively, you can use "abbreviate" to do something :
:abbreviate <expr> :date: strftime("%Y-%m-%d")
But I am sorry for that, the "eating matching" also exists here.

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