Using vimscript to run test scripts by utilizing normal vim commands - vim

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.

Related

How do I save a macro with multiple commands with let?

I'm currently learning to program in Vim, and I decided to make a macro to compile and run a C code quickly, so I made it like this:
save quickrun.c
!gcc quickrun.c -o quickrun
!quickrun
It worked as expected, no problems there, but then I tried to save the macro in the _vimrc file, and I had no problems with saving other macros with only one command using "let", like
let #a = '!gcc file.c'
But I want to save all the 3 commands in order to compile and run in the macro, how can I do it?
That's a very strange idea to put a sequence of commands into a register permanently. Typically, one uses a register to build commands on-the-fly by yanking portions of code from some script, or by recording a key sequence with q. The problem is that you have only 26 slots with fixed names, and that's quite inconvenient for storing general command sequences.
More frequent solution is to setup user commands or mappings (or both). You can define a command (it must start with a capital letter!) in your vimrc like this:
command! QRun save quickrun.c | !gcc quickrun.c -o quickrun | !quickrun
The bar symbol (|) is a command separator in Vim script, i.e. it's allowed in an interactive mode too. After that you can execute your command just like everything else: :QRun. If you feel "QRun" is too hard to type, you can additionally define a key mapping to spare a couple of keystrokes.
Also, you probably should revise your script to use arguments, and to get rid of "save[as]" in favour of plain "update", and so on.
Macros are like replayable keystrokes: they are as if you typed them. So if you want to use the ex command :!, you need to start with a colon. Then you can use the |:
let #a = ":save ... | !gcc ... \<CR> !quickrun\<CR>"
A better solution is a mapping:
nnoremap keys execute ":save ... <bar> !gcc ... <bar> !quickrun\<CR>"
But for C code you can usually just use :make

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 use ":g/regex/d_" without losing current cursor position

In Vim, if you run
:g/some_word/d_
all lines containing "some_word" are removed and the cursor automatically jumps to the place of last deletion. I want the cursor to stay where it was before the operation. How do I accomplish this?
You can't make the cursor stay but you can make it go back to where it was:
:g/some_word/d_|norm ''
The answer by romainl is a good one, and it should do the trick without any additional fuss.
If this is a common problem, and you'd rather not add the additional norm! '' at the end (or type the key sequence when you're done), you could encapsulate it in a command:
command! -nargs=* G call s:G(<q-args>)
function! s:G(args)
let saved_position = winsaveview()
exe 'g'.a:args
call winrestview(saved_position)
endfunction
The :G command will invoke the s:G function with all of its arguments, which saves the position, runs the normal g with these exact arguments, and then restores it. So you'd do :G/some_word/d_ and it would run the command and restore the cursor in the line/column where it started.
Obviously, this only makes sense if you use it often enough and you don't often work on bare Vims (like on remote servers and such). Otherwise, it might be a better idea to try romainl's suggestion, or get used to typing in ''. Your choice.
Also, if the current position happens to be after some of these lines, the cursor might end up in an unexpected place. For example, if your cursor is on line 7 and the command deletes line 3, then you'll be back at line 7, but all the text will have shifted up one line, so you'll be in the "wrong" place. You could probably play around with this function and compensate for the change, but it'll get pretty complicated pretty fast, so I wouldn't recommend it :)
The anwolib plugin provides a handy :KeepView command; you can apply this to any Ex command, so it's even more generic than the :G command suggested by #AndrewRadev:
:KeepView g/some_word/d_

Sending input to a screen window from vim

I have a vim function set up where I can highlight a line of text and execute in clojure. Here's the function:
function! Clojure_execline()
let cl = (getline(line(".")))
// ...
exec 'clojure -e "' . cl . '"'
endfunction
The problem with this is that it's slow to start and because it spawns a new clojure session every time I run it, I can't call a function I ran previously. Ideally, I'd like for a hidden repl to be running where I could send input from vim and retrieve the output from as well. I learned about gnu screen and thought it could help me, but I don't know how to send input from one screen window to another.
To clarify my problem, take this line of clojure:
(defn add2 [x y] (+ x y))
I'd like to be able to highlight this line in vim and execute in a running repl. I want to be able to call the line below and have it execute in the same repl:
(add2 4 5)
Afterwards, I'd like to be able to get the output of the function.
So, basically, my question is, how do I send input from one screen window to another?
Jake McCrary's suggestion is a good one. There are also a couple other scripts available, probably based on same idea:
VimClojure, which says it does "repl in a vim buffer"
and
slimv, specifically supports Clojure
and
Gorilla, I think VimClojure, above, is based on Gorilla
I don't know whether VimClojure actually does what you want, sending result back from Screen to buffer in Vim. One way to do that, I think, would be to finagle something using Vim's client-server functionality, possible with the --remote-send flag. See:
:h client-server
:h --remote-send
I don't have an exact answer, but it might be worth taking a look at slime.vim and seeing if anything can be learned from it.
blog post about it
script at vim.org
Found what I was looking for. You can execute this from a terminal to send a string directly to the stdin of a screen window:
$ screen -X stuff "ls -l\015" # \015 sends a carrige return.
You might also be interested in Conque http://code.google.com/p/conque/
I use it for Scala

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