Assign two actions to a VIM shortcut - vim

I have the following keyboard shortcut in VIM
nnoremap C vi"
This enable me to select text between "" by clicking C.
Now I want this same shortcut, to do the same but for ''.
nnoremap C vi'
Putting these two rules doesn't work, as the last replaces the one before it.
Is there a way to make both of them work?

Yes, but you need to build the intelligence into the mapping. This can be done via a :help :map-expr:
nnoremap <expr> C 'vi' . (getline('.') =~ '"' ? '"' : "'")
This simplistic example will check whether the current line contains a double quote, and then select those, else single quotes. For a useful mapping, you probably need to ensure surrounding quotes on both sides (using search()), and if both types match select the "closer" one. With a :function, you can make that as complex as you like...

Related

Vim Surround command

I have trouble remembering all the various keywords in vim. However, things like 'surround' from Tim Pope's surround.vim plugin are very useful.
Is it possible to set up a generic text command such that when I execute something like
:surround (.)
it will replace the current selection with
(<current_selection>)
or if I execute
:surround preamble.somethingelse
It will replace the current selection with:
preamble<current_selection>somethingelse
Alternatively you can use a custom "surrounding" which will prompt you for the text:
let g:surround_{char2nr('-')} = "\1start: \1\r\2end: \2"
Now you can visually select what you want to wrap and then press S- to tigger the - surrounding. It will then prompt you for a "start" and "ending" text.
For more help see:
:h surround-customizing
:h char2nr()
:h curly-brace-names
Almost, yes.
:command! -range -nargs=1 Surround normal gv"zc<args><Esc>F.v"zp
With this, you can create a visual selection, then use
:Surround (.)
:Surround preamble.somethingelse
Note that user-defined commands can never start with a lowercase letter, so :surround that you ask for is not possible. Also, this is a quick hack, so it's rather fragile.
However, as per comments, I would urge you to use more standard Vim methods (and well-vetted plugins like surround.vim) before cooking up custom ways to use it.
Here's another way, using surround plugin: define a custom replacement and perform it (then fix the plugin config to what it was before):
command! -range -nargs=1 Surround call ArbitrarySurround(<q-args>)
function! ArbitrarySurround(repl)
let backup = b:surround_45
let b:surround_45 = substitute(a:repl, "\\.", "\r", "")
norm gvS-
let b:surround_45 = backup
endfunction
This is more robust than the previous one, allowing ad-hoc custom replacement pairs, but does require the surround plugin. It can be used with the same syntax as the above one.
In order to leverage the surround.vim plugin, you have to base your customization on what the plugin offers. For visual mode, it's basically this mapping (that by default is mapped to S):
vnoremap <silent> <Plug>VSurround :<C-U>call <SID>opfunc(visualmode(),visualmode() ==# 'V' ? 1 : 0)<CR>
The plugin does not expose any functions (the <SID>opfunc() it invokes is script-local), so you have to invoke the <Plug>-mapping; that can easily be done with :normal (without a [!]).
Also, the plugin only takes a single character (e.g. ( to surround with parens and whitespace, ) to surround with just parens); you can reference that in your custom command via <args>.
You create a custom command with :help :command; note that the command has to start with an uppercase letter:
command! -nargs=+ Surround execute "normal gv\<Plug>VSurround" . <q-args>
This re-enters visual mode (gv), then invokes the plugin, and finally supplies the passed argument. In order to directly invoke it from visual mode, just add a -range to the definition, so that the command takes (and ignores) the '<,'> range that Vim automatically supplies then.
You can then invoke the new custom command as :Surround ), for example, and it will surround the previous visual selection with (...).
It's not exactly what you asked, but since the problem is memorizing, how about putting this in your .vimrc:
vmap ( o<ESC>i(<ESC>gvo<ESC>a)<ESC>
vmap " o<ESC>i"<ESC>gvo<ESC>a"<ESC>
Make your selection, hit ( and it gets surrounded with ( ).
Make your selection, hit " and it gets surrounded with " ".
You could do the same with {, [ or '. For such a simple use-case there's no real need for a plugin.

How to append text to end of current line using Vim function?

I'd like to append text to the end of a current line in Vim. I'd like to do this within the context of a function.
How can this be done? Do I need to escape/sanitise the text?
You could use the normal command with the execute command:
let text_for_appending = ' # a comment'
execute "normal! A" . text_for_appending
The exclamation mark is included to prevent any key mappings from being expanded. See :help :normal for more details.
With :exe + :normal! you may need to sanatize the text as you feared -- it'll depend on the kind of quotes you use, and on whether you forget to bang :normal and you have insert mode mappings and abbreviations.
With setline('.', getline('.') . text), vim won't try to interpret the text you append. This seems convoluted, but this is the more robust way to proceed -- it can become way more convoluted if you start to escape things with A.

Command for putting backticks around the current word

I have to format a lot of files in markdown manually, and I often have to wrap some isolated words in backticks to get them in a code span, ie. : object.method -> `object.method`
I'm using vim and I was wondering how I could write and map to some key a command which would put backticks around the word under cursor, by just pressing F1 for instance ?
Thanks !
The canonical answer is to use surround plugin by Time Pope that allows to surround easily a selection.(Unless you don't want to install any plugin)
Another (crude) no-plugin solution for the sake of diversity:
nnoremap <key> ciw`<C-r>"`<Esc>
xnoremap <key> c`<C-r>"`<Esc>
but yeah, just install surround.
lh-brackets has several mappings already defined for markdown:
backtick will insert a pair of backticks in insert mode, or surround the current word or the current selection (what you were looking for, and that surround also provides with its own "syntax")
* -> *<cursor>* ; twice for **<cursor>** (<localleader>* for surrounding)
_ -> _<cursor>_ ; twice for __<cursor>__ (_ for surrounding)
~ -> <del><cursor></del> (<localleader>~ for surrounding)
<BS> -> in INSERT mode, deletes an empty pair when the cursor is within it.
Here is a one liner you can try:
:nmap <F4> :s/\(<c-r>=expand("<cword>")<cr>\)/`\1`/<cr>
After this command pressing F4 key will do what you want, i.e. replace the word under cursor with the same word surrounded by backticks.
[UPDATE]
This may not work for something like object.Method. For this here is a new mapping in visual mode. Select the block of text you want to surround with backtick and press F3
:vnoremap <F3> <Esc>`>a`<Esc>`<i`<Esc

Creating a command that replaces the word under the cursor with another one?

I want to create a command that replaces the word under the cursor by another one, say I have the word have under my cursor and it replaces it with the word had and vice versa. How to accomplish that?
This could easily be accomplished by ciw and then entering the word that you would like to replace it with. Assuming that you want to replace words with different values.
Another solution is you could use a plugin like switch.vim. You would have to define the words/regular expressions you would want to replace.
If you actually want to do this with longer words, then this method may help. First, position the cursor on the word "had" and use yiw to yank (copy) it into the #0 register (and also the unnamed register, but we are about to overwrite that). Then move the cursor to "have" and use ciw<C-R>0<Esc> to replace it with the yanked word.
Do not type <C-R> as five characters: I mean hold down the CTRL key and type r. Similarly, <Esc> means the escape key. Do type each as five characters if you want to make a map out of it, for example
:nmap <F2> ciw<C-R>0<Esc>
If you want to replace all occurrences of the word under the cursor, you can add this into your _vimrc:
" search and replace all occurrences of word under cursor
:nnoremap <C-h> :%s/\<<C-r><C-w>\>/
:inoremap <C-h> <ESC>:%s/\<<C-r><C-w>\>/
Usage of this:
1) Press Ctrl+h (under the cursor is the word "have"), and Vim will enter this in the command line:
:%s/\<have\>/
2) Now just complete the replacing statement:
:%s/\<have\>/had/g
3) And press ENTER...
The SwapIt - Extensible keyword swapper allows you to configure sets of words (e.g. have and had) and toggle them via <C-a> / <C-x> mappings.

Search forward for the [count]'th occurrence of the text selected in visual mode

I want to search, like I do with the * command, for a pattern I have selected in visual mode.
I am aware of visual mode yanking, which fills the register 0 by default, and the possibility of just searching by / and then Ctrl-R (retrieving) the contents of register 0 (Ctrl-R, 0) to paste the pattern as a search.
Thing is, I do not want to YANK first, I already have something yanked, I just want to search for what's selected in visual mode now.
How can I do that, please? Can I do that without fiddling with different "yank to register N" tricks?
If you use gvim or console vim built with X support (check if 'guioption' is available) and a is present in your 'guioptions', then you can get current selection from * register. Otherwise, I'm afraid there is no easy way to do that without writing a VimL function, which will extract the selection based on values of < and > marks. That function then can be used with CTRL-R = in the search prompt.
Why don't you just combine all the steps you've outlined into a mapping? The only thing missing is saving and restoring the unnamed register, and a little bit of escaping.
" Atom \V sets following pattern to "very nomagic", i.e. only the backslash has special meaning.
" As a search pattern we insert an expression (= register) that
" calls the 'escape()' function on the unnamed register content '##',
" and escapes the backslash and the character that still has a special
" meaning in the search command (/|?, respectively).
" This works well even with <Tab> (no need to change ^I into \t),
" but not with a linebreak, which must be changed from ^M to \n.
" This is done with the substitute() function.
" gV avoids automatic reselection of the Visual area in select mode.
vnoremap <silent> * :<C-U>let save_unnamedregister=##<CR>gvy/\V<C-R><C-R>=substitute(escape(##,'/\'),"\n",'\\n','ge')<CR><CR>:let ##=save_unnamedregister<Bar>unlet save_unnamedregister<CR>gV
Here's the solution that works for me to make * work with [count] in visual mode:
vnoremap * :call <SID>VisualSearch()<cr>:set hls<cr>
fun! s:VisualSearch() range
let unnamed = #"
let repeat = v:count
exe 'norm gv"zy' | let #/ = #z
for x in range(repeat)
call search(#/, 'ws')
endfor
let #" = unnamed
endfun
You change the "z"s on line five to whatever registers you never use.

Resources