Map command with prefix in Vim - vim

Some Vim commands could have expression as prefix, like 100j or 20k, how to map the entire command? I want to call a Vim function before invoking the given command.
EDIT: I want to preserve the previous input number for j/k as repeat times, the calling function might be unrelated. For example, I want to do some bookkeeping before every j or k command, but I don't want to break the number prefix.

Every problem can be solved by adding another layer of indirection
except the problem of having too many layers of indirection
- Misquote of David Wheeler and Kevlin Henney
So how are we going to solve this? Add a layer of indirection!
One way to do this by is using a map expression which calls a function that does the "book keeping" and returns the command back.
let s:bookkeeping = {}
function! s:bookkeeping(count, cmd)
let s:bookkeeping[a:cmd] = get(s:bookkeeping, a:cmd, 0) + a:count
return a:cmd
endfunction
nnoremap <expr> j <SID>bookkeeping(v:count1, 'j')
Personally I think their is little to gain from gathering data on such commands as their is no context however it is probably good to see how to do an map expression. See :h :map-<expr> for more information.

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

How to diff two lines in an open file in vim?

I occasionally see very long lines in my code that I need to check if they are the same. Is there a way in vim to select two lines and diff them to show any differences between the two?
For example, given the two lines in vim:
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *lists);
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *1ists);
I would like vim to tell me that the two lines are in fact different because each spells "lists" differently. Is this possible, and if so, how do I do it?
A quick and dirty solution is to just select both lines and sort them while removing duplicates:
select lines
":sort u"
if only one line remains, both were equal
if both remain, there most be some difference
An undo recovers everything again.
An alternative to #sehe's approach would not require the use of temp files:
funct! DiffTwoTexts(text1, text2)
new
put =a:text1
normal ggdd
diffthis
new
put =a:text2
normal ggdd
diffthis
endfunct
funct! DiffTwoLines(line1, line2)
let text1 = getline(a:line1)
let text2 = getline(a:line2)
call DiffTwoTexts(text1, text2)
endfunct
comma! DiffWithNext call DiffTwoLines('.', line('.') + 1)
This will still be pretty hard to read, since it keeps everything on a single line, so I came up with this modification:
funct! EvalTextPreprocessor(expr, text)
let text = a:text
return eval(a:expr)
endfunct
comma! -nargs=1 DiffWithNextPre call DiffTwoTexts(
\ EvalTextPreprocessor(<q-args>, getline('.')),
\ EvalTextPreprocessor(<q-args>, getline(line('.') + 1)))
This new command takes a vimscript expression as its argument, wherein the variable text refers to whichever line is being preprocessed. So you can call, e.g.
DiffWithNextPre split(text, '[(,)]\zs')
For your sample data, this gives the two buffers
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *lists)
;
and
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *1ists)
;
Only the lines that start with Parameter are highlighted.
You can even build up from there, creating a command
comma! DiffTwoCFunctionSigs DiffWithNextPre split(text, '[(,)]\s*\zs')
Notice that I modified the regexp a bit so that it will keep trailing spaces at the end of lines. You could get it to ignore them entirely by moving the \s* to after the \zs. See :help /\zs if you're unfamiliar with what that vim-specific RE atom does.
A nicety would be to make the command take a range (see :help command-range), which you could use by diffing the first line of the range with the last line. So then you just visual-select from the first line to the second and call the command.
I used linediff.vim.
This plugin provides a simple command, ":Linediff", which is used to diff two separate blocks of text.
That is not a feature, however it is easily scripted, e.g. in your vimrc:
function! DiffLineWithNext()
let f1=tempname()
let f2=tempname()
exec ".write " . f1
exec ".+1write " . f2
exec "tabedit " . f1
exec "vert diffsplit " . f2
endfunction
This will open the current and next lines in vertical split in another tab.
Note that this code is a sample
it doesn't check whether next line exists (there are any following lines)
it doesn't cleanup the tempfiles created
a nice improvement would be to take a range, or use the '' mark to select the other line
You can leave off the 'vert' in order to have a horizontal split
Map it to something fancy so you don't have to :call it manually:
:nnoremap <F10> :call DiffLineWithNext()^M
you could also just create a new empty window buffer and copy line, then make command:
:windo diffthis
this should open a new window showing the differences of those 2 lines

Vim whole word search - but faster

I know that to search for a whole word in vim you need to type:
/\<word\><CR>
Now, what I would like to do is to map this behaviour to ? (as I never search backwards, and if needed I could search forward and then NN). I.e. I'd like to type:
?word<CR>
and have the same result as above (vim searches the whole word). I've been fiddling around with vim commands and mappings for some weeks now, but I'm not sure about how to accomplish this one. Thank you for any help.
Update: (insead of ? I use \ now).
I tend to use * and # (as suggested by Brian Agnew), but if you want a method that involves typing
?word<CR>
you could do something like this:
function! SearchWord(word)
let #/ = '\<' . a:word . '\>'
normal n
endfunction
command! -nargs=1 SearchWord call SearchWord(<f-args>)
nmap ? :SearchWord
Note there is a space after SearchWord on the last line.
Explanation:
The mapping will make ? open up a command prompt and type SearchWord (including the space). The command makes SearchWord myword do the equivalent of call SearchWord('myword') (i.e. it puts the quotes round the argument in order to make it into a string). The function sets the search register #/ equal to your word surrounded by \< and \> and then does a normal-mode n to find the next instance of the contents of the search register.
Of course you lose the benefits of incremental searching if you do this, but hopefully it's useful anyway.
You can search for words under the cursor using * and # (forwards and back). g* and g# will do the same but finding words containing what's under your cursor initially.
Obviously (!) you need to have found the first instance already for this to work, but it's very effective for successive searches.
Al's solution is very nice, but wouldn't it be simpler to just enter the \<\> pattern, then move the cursor back twice? This works for me:
nmap ? /\<\><Left><Left>
This way you still get incremental search (kinda).

Vim search and highlighting control from a script

I'm writing a script in which I want to control searches programmatically, and get them highlighted. The search() function results are not highlighted (I think), so using that function is not of use to me.
What I want to do is use the 'normal /' command to search for a variable, but that doesn't seem to be straightforward. I can script the command:
execute 'normal /' . my_variable . '\<CR>'
(or other variations as suggested in the vim tip here: http://vim.wikia.com/wiki/Using_normal_command_in_a_script_for_searching )
but it doesn't do anything. I can see the correct search term down in the command line after execution of the script line, but focus is in the document, the search register has not been altered, and the cursor has not done any search. (It seems as though the < CR > isn't getting entered, although no error is thrown -- and yes, I have tried using the literal ^M too.)
I can at least control the search register by doing this:
execute 'let #/ ="' . a:term .'"'
and then the obvious thing seems to be to do a:
normal n
But that 'normal n' doesn't do anything if I run it in a script. Setting the search register does work, if I manually press 'n' after the scrip terminates the search happens (and highlighting appears, since hlsearch is on). I don't even care if the cursor is positioned, I just want the register pattern to be highlighted. But various combinations of 'set hlsearch' in the script don't work either.
I know I could use 'match()', but I want to get it working with regular search highlighting, and I wonder what I'm doing wrong. It must be something simple but I'm not seeing it. Thanks for any help.
run:
let #/ = a:searchStr
from inside your function then run
normal n
from outside your function (inside it does nothing) eg.
command -nargs=* Hs call MySearch() | normal n
or you can use:
set hlsearch
instead of normal n if you don't want the cursor to move
(I cannot work out another way of doing this without having something outside the function.)
If your script is using functions, then this quote from :help function-search-undo is relevant:
The last used search pattern and the redo command "."
will not be changed by the function. This also
implies that the effect of :nohlsearch is undone
when the function returns.
Vim usually tries to reset the search pattern (and a few other things) when a function ends, often you can get around this by adding the n (next search) to the end of a mapping, or using :map <expr> and having your function return the key sequence to be executed.
On closer inspection, it seems \<CR> is not picked up inside single quotes. Try using this instead:
execute 'normal /' . my_variable . "\<CR>"

Resources