vim: pass a char or word to your function - vim

I know that when you define a function in vim, you can use the range keyword so that users can say:
:-5,+5call MyFunction()
And then your function gets a:firstline and a:lastline.
What I want is something similar, but more like the traditional vi way of combining a command with a movement, so that 'd ' deletes a space, 'dw' deletes a word, 'd2w' deletes two words, 'd2j' deletes three lines, etc. Assuming my function gets mapped to some input-mode character sequence, is there any way to make it accept similar variable-length inputs, and then modify that text?
Just to be a little more clear, suppose I want to define a function to wrap <b> tags around existing text. We'll say that function is mapped to ;b. I want users to be able to say ';bw' to bold one word, or ';bf.' to bold everything to the end of the sentence, or whatever, with all the flexibility that vim provides to built-in commands.

If I understand what you're asking, then all you do is include the char argument in your mapping. For example:
map d :call MyFunction('d')<cr>
map dw :call MyFunction('dw')<cr>
map d2w :call MyFunction('d2w')<cr>
" Of course these would be terrible mappings because they
" are already mapped to important Vim functions
The way mappings work is that if you "overspecify" a char like 'd' above so that it is usable either by itself or as a prefix for longer command, Vim will wait briefly (for timeoutlen)after you press 'd' to see if you're going to press another character. (This depends on thetimeout option being set to true, which is the default.) If you don't press another character, Vim will execute the 'd' mapping. If you do it will call the more complex mapping. See :h map-commands generally and :h map-typing for more.
Note: After your clarification I think what you want is to create a custom 'operator' function that you can use to operate on buffer areas defined by Vim motions. See :h map-operator for info on how to do this.

Related

How to do mark-like mapping in vim

The m normal command accepts a letter just after it to set a "letter" mark.
I would like to create a similar command that works across tabs... But my problem is with the binding : Is there a simple way to bind for example M<letter> to a function or a command or should I manually repeat all the possibilities ?
As romainl has already said, no.
Covering this for good measure (and in case someone else comes along later), you can only practically map upper-case letters. As is outlined in the documentation, lower-case marks are only valid within a single file. Upper-case ones, that the Vim docs calls "file marks", are valid from anywhere. Unless you have some dark magic function to resolve ambiguous file marks, you probably only need a single for loop mapping the upper-case letters, if you're going with the brute-force option.
That said, there are a couple alternatives here as well.
As far as I know, the only "dynamic" bit of a command is a count (or a range, but unless you want to map characters to a number (and handle ranges and other fun stuff:tm:), I don't recommend this approach:
" The <C-U> is required for all uses. If you want a function,
" you'd need :<C-U>call YourFunction()<cr>
nnoremap M :<C-U>echom v:count<cr>
See also :h v:count, which states:
Note: the <C-U> is required to remove the line range that you get when typing ':' after a count.
You can then run 26M, decode v:count as Z, and then do whatever fancy lookup from there.
The second alternative, and the one proposed by romainl and by far the most used one in cases like this (source: experience and lots of code browsing), is using a for loop to brute-force map letters:
for i in range(char2nr('A'), char2nr('Z'))
exec 'nnoremap M' . nr2char(i) ':echo "This is where the appropriate function call goes"<cr>'
endfor
Which produces all 26 mappings.
And the final approach is abusing getchar(). This means a single mapping, but at the expense of doing additional processing:
func! Func()
let c = getchar()
echo c
" Character processing (the `echo` isn't required) here
endfunc
nnoremap M :call Func()<cr>
You can decide which works for you, but I strongly suggest preferring option 2, as this gives you map timeouts and clear definitions, and more obvious conflict detection.

Vim split function arguments

I found that I often do refactor:
def function(param1, param2, (...), paramK):
to
def function(
param1,
param2,
(...),
paramK,
):
And tried to write mapping for it.
I started with
command! Split normal! qqqf(a<cr><esc>qqf s<cr><esc>#qq#qf)i<cr><esc>
qqq - reset content of macro q.
f(a<cr><esc> - find bracket, make newline and return to normal mode.
qq - start to record macro q.
f s<cr><esc> - change space to newline.
#q - run macro recursively.
q#q - end recording and run.
f)i<cr><esc> - add last newline before closing bracket.
My idea was, that macro will fail when it won't find space, but something is wrong with that. It raised some questions.
1) How can I check if some motion succeed? e.g. How to check if there is a space in current line?
2) Is there better idea to achieve what I want? Maybe some plugin or clear function?
3) What is wrong with my idea? When I run this combination from hand it works, but while calling :Split it doesn't.
Regarding why it doesn't work quite like it does when you type it manually:
When you type normal! <esc>, Vim parses this as "type the letters <, e, s, c, >". You might be able to insert a literal escape key there by typing <c-v><esc>, but that can look a bit weird in the configuration. Instead, a better way is to use the :exe command (:help :exe):
exe "normal! \<esc>"
The \<esc> gets interpolated by the string to be a literal escape key. So, the exe "normal! ..." gets translated to an invocation to normal! with the keys you're looking for. You also need to escape <cr> the same way. I'd also use \<space> instead of , I'm not entirely sure if a normal space is going to work here. After that, hopefully, you should get the same results as when you type it manually.
As for the actual problem you're trying to solve, I do have a plugin for that: splitjoin. By default, it splits your example like this:
def function(param1,
param2,
(...),
paramK):
pass
But there's a setting you can change to adjust it to your liking. Alternatively, from Jordan Running's link, it seems you could also use the argwrap plugin, which might be more reliable for argument-splitting in particular (splitjoin handles a wider variety of cases, but maybe doesn't do as good with arguments? Not sure.)
Answers:
I don't understand the question. You can search for spaces with f or with the / syntax. Why do you want to do this?
Yes. See below.
The vimrc syntax is super different from normal vim syntax. I don't know why and I don't fully understand it.
Code:
nnoremap <C-r> :s/, */,\r /g<cr>:s/)/\r)<cr>:nohl<cr>
remaps ctrl+r to search for a comma followed by 0 or more spaces and replace that with a comma, newline, and tab. then searches for ) and replaces that with newline and ). then undoes the highlighting it just did.
To enter a literal tab instead of 4 spaces, you'll have to type CtrlVtab in place of the 4 spaces you see in the command

Vim Custom Replace Function

Note: I'm currently using Neovim v0.2.2 (But I believe this shouldn't change anything related this post)
I'm currently attempting to create a function within vim that allows for easily replacing text.
I understand I can create a shortcuts and macros and all that, but ideally I just want to give 2 args, and not think about what specifics go where as this can interupt my thought process.
So I decided to just have a simple wrapper disguised as a function (Which I will create a command wrapper for as well, once I figure out what I did wrong here)
function! VisualReplace(query, replacement)
" Example = '<,'>s/query\%V/replacement/g
'<,'>s/a:query\%V/a:replacement/g
endfunction
As you can see, it's a very simple function that just applies the args in it's respective position, Yet, this fails even when called as a function using : call VisualReplace('some_query', 'some_replacement'
Alternatively, if you simply use the Example I have commented out directly, there's no issue, So I was hoping someoen could enlighten me on a potential fix
If need be, I could possibly look into string building & build it incrementally
Error msg:
Pattern not found: a:query\%V
General theory
Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R>= to insert variable contents:
:sleep <C-R>=timetowait<CR>m<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables:
execute 'sleep' timetowait . 'm'
Your function
In order to get the a:query and a:replacement arguments into :substitute, use :execute and either string concatenation or printf():
function! VisualReplace(query, replacement)
execute "'<,'>s/" . a:query . '\%V/' . a:replacement . '/g'
endfunction
Additional critique
Passing a range to a function is so common, there's special syntactic sugar for it: The range attribute to :function, and a:firstline and a:lastline implicit arguments. Read more about it at :help function-range-example. While your use case here seems to be specifically for visual mode, in general it's useful to keep the scope of functions as broad as possible.
#Ingo Karkat answered perfectly. However, I feel like there might be some workflow alternatives which might help. (Assuming you aren't trying to script this behavior)
Visual Star
It looks like you are build a search based on a visual section. You may want to consider using a visual-star plugin to simplify the process. Here is a an example of a visual star mapping:
xnoremap * :<c-u>let #/=#"<cr>gvy:let [#/,#"]=[#",#/]<cr>/\V<c-r>=substitute(escape(#/,'/\'),'\n','\\n','g')<cr><cr>
This mapping will allow you to visually select text and then execute * to make it a search pattern. Similar to how * works in normal mode on the current word.
Search refining
I get the impression that you are trying to refine your search pattern. Vim has a nice way of doing this with q/ or pressing <c-f> while searching with /. See :h q/. This will bring up the command-line window which will allow you to edit the query/command-line with all your normal Vim keys.
Search and Replace with gn motion
Sometimes doing a substitution is just overkill or doesn't quite fit the situation right. You can mimic a search and replace by using the gn motion to operate on a search pattern. By using an operator and the gn motion together you can use the dot command, ., to repeat the action easily.
Example:
/foo
cgnbar<esc>
Now you can use . to repeat the foo -> bar replacement. Use n to skip. You can use other operators as well, e.g. gU to uppercase.
See :h gn and :h operator for more help.
Related Vimcasts episodes:
Refining search patterns with the command-line window
Operating on search matches using gn
Search for the selected text

Vim: recognize character sequences as part of "whole word"

This has been bugging me for a while: I'm writing code that uses verilog-auto which means I'm editing in a verilog file with perl snippets injected into comment sections. One very useful thing that I like to do in Vim is to search for the whole word under the cursor with * and #. However, with perl syntax that contains variable names such as ${w} and $w, these shortcuts don't work.
I don't want to add $, { and } to my "keywords" list as there are many instances where I don't want these to count as part of a whole word. For instance, in verilog concatenation: {sig1,sig2[1:0]}, I wouldn't want {sig1 to be searched for as a whole word.
Is there a way to get "whole word" to recognize sequences via a regex or something? So only ${[a-z]+} or $[a-z]+ gets recognized as "keywords".
Either that or a separate keyboard shortcut that can let me search for the pattern under the cursor.
Here's a really ugly hack, but it works:
nnoremap * viW:s/\%V\$*{*\a*}*/\=setreg('a', submatch(0))/n<cr>/<C-r>a<cr>n
nnoremap # viW:s/\%V\$*{*\a*}*/\=setreg('a', submatch(0))/n<cr>/<C-r>a<cr>N
The only downside is that this will overwrite your last visual selection, so if you use gv a lot, this isn't the best solution. It also overwrites the a register, although you could pick a different one if you want.

Easy motion vim plugin mapping issue

I have the vim EasyMotion plugin installed.
Pressing,
<Leader><Leader>f searches forward from the current line.
<Leader><Leader>F searches backward from the current line.
Is there a way to search the entire visible part of the buffer only using 'f'? I would ideally not want to use two different bindings for searching forwards and backward. One single binding to search the entire visible portion of the buffer would be most ideal.
You can try a mapping like this:
nnoremap <leader><leader>f :execute "/\\%>" . line('w0') . "1\\%<" . line('w$') . "l"<left>
That's a confusing syntax, so I'll unpack it.
line('w0') and line('w$') return the line numbers of the first and last visible lines in the buffer, respectively, so you use them to find the range for the visible part.
The / search command allows a range to be specified, but with an odd syntax. The format is /\%>Xl\%<Yl where X is the line to start from and Y is the line to end at.
It's not possible to just drop the results from line() into a normal / invocation, but we can build a string, using . to join segments together, and once the command is built, pass it in to :exec to make it happen.
Finally, there's the <left>. That's for cursor positioning. When you execute <leader><leader>f, the whole mapping fires as though you were typing it, so you end up with the full :exec command on the line, and it ends with a ", but you want to type inside those quotes. Alternatively, you could remove "<left> from the end of the mapping, but then you'll have to remember to close the quote after typing your search term.
I'm not familiar with EasyMotion, so this may not give you exactly what you were asking for (I realized this after I typed up the answer), but it will let you run a search in the currently visible part of a buffer only, and you can probably adapt it to EasyMotion's purposes without too much difficulty.

Resources