Vim Custom Replace Function - vim

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

Related

Trigger a Vim custom g# command on a single line?

In Vim 8.x, is there a way to specify a same-line action when using g# and :set opfunc to define a custom command? In other words, I know how to use map to:
noremap <silent> <a-z> :set opfunc=MyFuncOp<cr>g#
Let's say MyFuncOp does some custom type of yank or delete. It works perfectly to press alt-z then j and do that on two lines. But what if you need it on just one line... just like you would with repeated operators like dd or yy? Mapping g#g# doesn't work. How do you convey a single linewise (i.e., the current line) to a g# custom command?
It may depend on how your MyFuncOp() is implemented but, in general, you should only need to do <A-z>l, <A-z>h, or basically use any motion that stays on the current line: 0, $, etc. Just like with a real operator.
Showing us your MyFuncOp() would help.
No, it's not to be implemented by custom g# only. Basically, you have two options.
Either, (1) create also a custom motion (mapmode-o) that includes the whole current line. So, say, you have Y to perform custom "yank" and al to select current line in operator pending mode. Then typing Yal will do. There are many existing implementations of such "custom motion" out there. And it's also pretty easy to devise another one yourself.
Or (2), define special version of your mapping. That is, both Y to invoke g-at, and YY to perform custom operation on the current line without entering the operator pending mode. Proper use of functions and commands should help to avoid code duplication in this case.

Using vimscript to run test scripts by utilizing normal vim commands

I started using VIM as my editor around six months back and I enjoy it very much. However, there are a few work related scripts that I'd like to implement to make my life easier. If there is anyone who can help me I would be grateful.
This is my question. I have some tests written in python and I wrote a key mapping to run those tests using vim terminal. It works perfectly. However, now I want to use VimScript and some vim functions to make it look better. I'm a beginner in VimScript and therefore, I'm not sure whether this is doable.
My folder structure looks like,
.
├── my_test.py
└── test
└── testRunner.py
1 directory, 2 files
My test code looks something like,
my_test.py:
#!/bin/python
class MyTest1:
def Run():
# Test body
class MyTest2:
def Run():
# Test body
test/testRunner.py:
#!/bin/python
print "Running the test"
My current key-mapping in .vimrc looks like:
nnoremap <leader>t mZ/class<CR>Nwyiw:noh<CR>:terminal<CR>cd test<CR>python testRunner.py <C-W>"0<CR><C-W><C-W>'Z
What this does is,
Find the test name (the test that I'm currently editing)
Copy the name and run that test name in a vim-terminal
What I want it to be something which looks like:
nnoremap <leader>t :call RunThisTest()<CR>
function! RunThisTest()
RememberEditContext()
FindAndCopyTestName()
RunTestInTestDirectory()
ReturnToEditContext()
endfunction
Can someone help me in developing these functions?
Thank you in advance!
One option is to use the :normal! command directly, which allows you to run a sequence of keystrokes directly as you'd have used them in a mapping.
But it turns out we can do better, much better, so let's get to it!
Searching and Matching
You can use the search() function to look for the class you're in. You can pass it flags, such as bcnW, to have it search backwards, possibly match at the cursor position, do not move the cursor and do not wrap around the file. Putting it all together:
let line = search('^class \w', 'bcnW')
This will return a line number if there was a positive match, or 0 if there wasn't one. If there was a match, we can use getline() to get its contents and then matchlist() to capture the name of the class.
let [_, classname; _] = matchlist(getline(line), '^class \(\w\+\)')
As you can see, using Vimscript we were able to get the classname without moving the cursor and without touching the search register. So we didn't need to set any marks and we won't need to worry about recovering the current position and view!
Running a command
Now it's time to run a command on the terminal. We can simplify the process by passing it a command directly. (Note that there's a difference here, in that the terminal will run just that command, it won't leave the shell around after finished. Depending on your use case, you might prefer to do something more akin to what you're doing now.)
We can run the command in a terminal with:
:terminal ++shell cd test && python testRunner.py MyTest1
But, of course, we need to actually pass it the class name we got, not a fixed value here. We can use the :execute command for this purpose. It takes a string and runs it as a Vimscript command. We can use this to assemble the string dynamically.
execute "terminal ++shell cd test && python testRunner.py ".shellescape(classname)
Finally, to go back to the original window, we can use the :wincmd command, more specifically wincmd p.
Putting it together
The resulting function is:
function! RunThisTest() abort
let line = search('^class \w', 'bcnW')
if line == 0
echoerr "Not inside a test class!"
return
endif
let [_, classname; _] = matchlist(getline(line), '^class \(\w\+\)')
execute "terminal ++shell cd test && python testRunner.py ".shellescape(classname)
wincmd p
endfunction
nnoremap <silent> <leader>t :call RunThisTest()<CR>
There's definitely room for improvement, but this should get you started!
Saving and restoring context
We didn't go into saving and restoring context, since this case actually didn't need any of that!
If you were to develop functions that use commands that affect global context, you can use Vimscript to save and restore it.
For example, if you're going to search, you can save the #/ register and restore it after the search:
let saved_search = #/
/class
let #/ = saved_search
If you're going to yank into a register, you can save and restore it too. For example, #" for the default register. You should also save the register type, which records whether the contents were taken in a character-wise, linewise or blockwise context.
let saved_register = getreg('"')
let saved_regtype = getregtype('"')
normal! y3W
let words = getreg('"')
call setreg('"', saved_register, saved_regtype)
You can also save the current view, which includes the position your cursor is in, but also the other parameters of the window, such as what the first displayed line and column are, such that you can fully restore that context. See the winsaveview() and winrestview() functions for details on that.
Managing Terminals
There are functions to control the terminal that go way beyond what :terminal can do.
For instance, the much richer term_start() allows running a command as a list and passing options such as 'cwd' to run the command on a different directory.
So we could simplify our test execution with:
call term_start(['python', 'testRunner.py', classname], {'cwd': 'test'})
There's also term_sendkeys() which you can use to send keystrokes to the terminal. For example, if you prefer to start a shell and call the Python script through the shell:
let termbuf = term_start(&shell, {'cwd': 'test'})
call term_sendkeys(termbuf, "python testRunner.py ".shellescape(classname)."\r")
You can also use term_getline(termbuf, '.') to get the contents of the line where the cursor currently is. For instance, you could use that to detect whether the terminal is on a shell prompt (line ending in $ and whitespace) or still on an execution of a test runner.
Finally, you can even have the command running inside the terminal call Vim commands! Through special escape sequences, it can call exported functions or ask Vim to open files for editing. See :help terminal-api for details.
Learning More
This is all very neat... But how can I learn more?
My first strong recommendation would be to read the excellent "Learn Vimscript the Hard Way", by Steve Losh. It covers the basics of the language, how to interface with the editor (mappings, auto-commands, indentation expressions, filetypes) and basics of how to put together Vim plug-ins. It also covers common pitfalls of Vimscript and best practices for writing reliable code. That's a must if you want to get serious about scripting Vim.
Second suggestion is read the excellent documentation that's available through :help! Few applications are as well documented as Vim is, so knowing your way around the help system can really help a lot.
Third is using StackExchange. In particular, the Vi & Vim SE which is dedicated to the subject. Not only you'll find great answers there and you'll be able to ask great questions, you will also have the opportunity of seeing great questions, wonder how to solve them and possibly take a stab at writing an answer. (Personally, since I started using the Vi & Vim SE, my Vim-foo has greatly improved, to the point I can consider myself almost an expert.) I strongly recommend that.
Finally, practice. It typically takes a few attempts to get something really right. But the fact that the environment is fairly dynamic and flexible allows for experimentation. You can type and experiment with the commands in the editor itself, so it's usually quick to test your code and get it right as you're writing it.

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

Returning to Normal Mode from Insert Mode after "execute normal"

You can perform normal mode commands programmatically in Ex mode, via execute normal, e.g.
:execute "normal" "iNEWTEXT\<Esc>0"
This switches to insert mode (i), writes "NEWTEXT", escapes to normal mode (\< Esc>), then moves to the start of the line (0).
However, using a non-constant string, either a register or variable, the behavior is different. For example, suppose you have the same command above saved on a line in any file (not necessarily a vimscript file):
iNEWTEXT\<Esc>0
You can then copy the text into any register (here, z) via "zy$ and execute the register via #z. This time, though, the output is different:
NEWTEXT\<Esc>0
After entering insert mode, the Escape is no longer treated as a special character, and is instead taken literally. Alternative forms like \e don't work either. Is there a way around this?
EDIT: Using Ingo's answer, I created the the following function. Basically, the use is for having a set of normal/insert commands embedded within the text of the file, and being able to execute them. More commonly, something similar is used for running Ex commands from a line of text, but I couldn't find anything that did this exact thing for normal and insert mode.
So, you'd have text like the following in your file:
jy10j10jpO\<Esc>jEll
When on that line, you could call the function or a remap, and the commands would execute (in this example, copying and pasting 10 lines, and moving 2 columns past the first word). Ingo's alternatives are better for serious usage, namely sourcing commands from another file, having the command in the .vimrc, or a file-type specific option. Macros saved by a session would work just as well, and are more practical than having commands scattered throughout a file. In my case, I was syncing across multiple devices, and didn't want to have another file or clutter my vimrc with this very specific command, but didn't mind cluttering this specific file itself. Think of this like a portable macro.
" Execute current line as Vim normal mode commands.
nnoremap <A-y> :call EvaluateLineAsNormalModeCmd()<CR>
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
"have to :execute twice: once to get the contents of the
"register inserted into a double-quoted string, and then once for
"the :normal to evaluate the string.
execute 'execute "normal" "' . g:getCurrentLine . '"'
endfunction
EDIT2/3: Here are two functions using Christian Brabandt's answer. They work about the same but can put the user in insert mode at the end (whereas, based on my minimal information, 'i' in the other context is considered an incomplete command and not executed, and :startinsert can't be used in that situation). PS: Please don't ask me what all those single and double quotes are doing, as I can't wrap my head around it O_o
function! EvaluateLineAsNormalModeCmd()
normal! 0y$
execute ':call feedkeys("'.#".'", "t")'
endfunction
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
execute ':call feedkeys("'.g:getCurrentLine.'", "t")'
endfunction
If you really need this (the use case is dubious), you have to :execute twice: once to get the contents of the register inserted into a double-quoted string, and then once for the :normal to evaluate the string.
:execute 'execute "normal" "' . #z . '"'
PS: Please give more background; what is your final goal? When a question is only about a small technical step, it's difficult to provide a good answer. If you don't tell us why you want this, it's easy to succumb to the XY problem.
I would rather use the feedkeys() function. E.g. for your sample, this should work:
exe ':call feedkeys("'.#".'", "t")'
(If you yanked your line into the unnamed register, else adjust the register name accordingly). Note, quoting could get ugly.
To understand what is going on, this is what is done:
exe ':call feedkeys(' - First part of the feedkeys() function call
" - Start of Quote for the first argument
. - String concatenation
#" - content of the unnamed register
. - String concatenation
' - Start of second part of the feedkeys function call
" - End of Quote for the first argument
, "t")' - Second argument of feedkeys() function call
You could also do it in 2 steps like this:
exe ':let a="'. #". '"' - Also needs to quote #" correctly.
call feedkeys(a, 't')
which should be easier to understand. The exe call is only to translate the normalized key notation into literal keys.

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