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

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

Related

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.

Replace visual selection with command output

I would like to replace parts of one line with the result of the selection being piped into a command.
For example:
echo "hello $(echo "world" | base64)" | vim -
This will open a buffer with hello d29ybGQK in it. Now press wvw to visually select d29ybGQK.
Then I attempted :!base64 -d and I expected the buffer to contain hello world, which did not happen. Indeed, the whole line was piped into the command, and the whole line was replaced.
Is it possible to replace only the visual selection, and have only that selection piped into the command?
I also attempted c<c-r>=system('base64 -d') but that did not send the visual selection to the command's stdin.
Filtering with ! is always line-wise. Your solution with c and the
expression register is an excellent way to solve this. You only forgot
to pass the input to system(), which is its second optional argument.
Since you just changed the text selected, it went into the " register
automatically. All you need to do is to grab it back and pass it to
system with getreg():
c<C-R>=system('base64 -D', getreg('"'))
Note that base64 may echo a newline at the end. If you want to remove
it, either wrap the whole thing in trim(), a new function in Vim 8, or
use [:-2]:
c<C-R>=trim(system('base64 -D', getreg('"')))
c<C-R>=system('base64 -D', getreg('"'))[:-2]
This is a shorthand for [0:-2], meaning grab everything from character
0 to second-last in the resulting string.
Consider creating a visual map if you use it often:
vnoremap <leader>d c<C-R>=system('base64 -D', getreg('"'))[:-2]<CR>
For historical reasons, the Ex commands are inherently line-based; venerable vi also didn't have visual mode yet. That limitation includes filtering through an external command with :range!; it will always filter complete lines.
manual solution
For simple input like in your example, it's probably easiest to temporarily split the line, filter, and then reassemble. For example:
:substitute/ /\r/ | execute '.!base64 -d' | -1join
plugin solution
For a universal solution, you need to use or implement a plugin that grabs the selected text, filters it (probably through system()), and then replaces the selection with the result.
My SubstituteExpression plugin has a {Visual}g= mapping that can filter through Vimscript expressions, Vim functions and commands, and external commands.
express.vim by Tom McDonald offers an almost identical implementation. It also allows on-the-fly creation of operators via :MapExpress and :MapSubpress, something for which I would use my TextTransform plugin, which you need to install as a dependency, anyway. My plugin offers more advanced (cross-mode) repeats, and the :Ex-command expression variant, but has two large dependencies you also need to install.

vim: Run multiple commands based off of one :global command

Apologies if this has been posted already, for I cannot find an answer, even on the vim wiki.
Is there a way I can run multiple commands in vim command-line mode off of a single :g search?
For example,
:%g/foo/ s/bar/\=#a/g | exe "norm /cat\<enter>\"ayiw"
Which (for what I intend it to do) should, on every line matching foo, replace bar with the contents of register a, and then find the next iteration of cat (even if it is many lines ahead), and put the surrounding word into register a.
Instead, this specific syntax completes the subsitution command using the current contents of the initial a register, and then executes the normal mode command on a single line (after the substitution has been completed).
This example is not my specific use-case but shows one instance where this functionality is useful. I realize I could put it all into a single exe, i.e., %g/foo/exe "norm :s/bar/\\=#a/g\<enter>/cat\<enter>\"ayiw", but I would like to do it the first way, as I feel it is more flexible.
I would prefer to do this using vanilla vim, but if a plugin exists for this, that is an okay alternative. Does anybody know if there is syntax to do such a thing?
Okay a "little bit" dirty, but does this work for you?
:let list = split(execute('g/cat/p'), '\n') | g/foo/ s/bar/\=matchstr(remove(list, 0), '\s\d\+\s\zs.*')/g
It first reads all occurences of cat save them in a list.
Then replace the first bar with the first cat... and so on.
The dirty part ist the matchstr command. the g//p also returns a number for the result so the list looks like this:
1 cat
2 cat
3 cat
...
that's why we have to remove a bit from the front. I would love to hear if someone knows a clean solution for that (I am also interested in a clean vimscript solution, does not have to be a oneliner).
You can do this (at least for multiple :s commands applied to a single :g). Example:
" SHORT STORY TITLES to single word of CapitalizedWords within <h3>s
.,$g/^\L\+$/s/[^A-Z0-9 ]\+//ge|s/\u\+/\L&/ge|s/\<\l\+\>/\u&/ge|s/ \+//ge|s/.*/<h3>&<\/h3>/

Syntax of Greplace

I want to change some words in several files using VIM. To do this, I found out about Greplace, which appears to be made to do just that. However, in the documentation there is no sample of the syntax of Greplace (there is a sample of Gsearch, but I need the replace function).
Say I want to change "foo" for "bar" in all ".asp" files, how could that be achieved with Greplace (or any other method). I do not care about confirmation (it's fine if it requires confirmation; it's also fine if it does not).
It's trivial to do without a dedicated plugin.
$ vim *.asp
:bufdo %s/foo/bar/ge | update
:q
:bufdo runs the following commands on all buffers.
%s/foo/bar/ge replaces foo with bar on all lines and all occurrences in each line. The e flag makes sure :s doesn't emit an error if it doesn't find foo, since foo may not exist in all the files.
| is Vim's way of separating multiple commands to run. See :help :bar
update saves the file back to disk, only if any changes were actually made.

Vim: How to handle newlines when storing multiple commands in registers?

I have a file where I store snippets of vim commands. When I need a snippet, I yank it and then execute it with #". The snippets are stored as a script, one line per command, like this:
:s/foo/bar/g
:echo "hello"
:s/1/2/g
Edit: I removed normal mode commands from the example, as they were not part of the problem.
Now this procedure doesn't work anymore: when executing the snippet, it just stops at the first line as if waiting for a newline.
Is there an option somewhere affecting how # is executed? I'm pretty sure it was working some time ago...
Substituting the newline with a ^M character works but makes the file more difficult to handle.
Additional information:
Here's another symptom: when I yank a snippet, if I execute it with #" it stops at the first line as I just explained. But if I execute it with :# it works. But the help file doesn't seem to imply any difference in how the two commands treat the register's content...
I don't think the problem is ^M vs. ^J. Vim macros will treat either one as a valid end-of-line character for recorded macros. I think the problem is extra newlines.
In your example, there's at least one spurious newline after 2j, and unless you're particularly careful when copying the snippet, there's probably another one after 10k as well. These extra newlines are like pressing <Enter> in Normal mode -- they move the cursor down one line.
Here's what I think you want the snippet to look like:
:s/foo/bar/g
2j:s/1/2/g
10k
(Even that's a little misleading -- you'd still have to be careful not to copy the newline after the 10k.)
Why do these extra newlines make such a big difference? Well, for one thing, they cause you to be at least one line away from where you expect to be, which throws off anything you want to do on a particular line (like execute the :s// command).
More importantly, however -- and this is what I think is happening in your example -- is that Vim stops macro playback if the macro attempts to use <Enter> on the last line of a buffer. (I'm guessing Vim considers it an error, and any error causes a macro to stop running.)
Here's an example. Suppose you've got this snippet stored in register x:
4j
:echo "Done"
(Notice the newline after 4j.)
Furthermore, suppose you have the following five lines (and only these five lines) in a buffer:
line 1
line 2
line 3
line 4
line 5
If you now press #x on line 1, the :echo "Done" never executes. Vim moves the cursor down 4 lines to line 5, then attempts to move down one more line because of the extra newline, but it can't. The macro stops executing at that point, before the :echo command gets a chance to run.
However, it works if you change the x register to this:
4j:echo "Done"
So to return to your original example, I'll bet what's happening is that the extra newline after 2j is attempting to move your cursor somewhere it can't go, and that causes the macro to stop. The bottom line of the screen contains the last command executed (:s/foo/bar/g), which makes it look like Vim is waiting for you to press Return.
Finally, I'd strongly recommend using another method to store and execute Vim command sequences. The technique you're using is tolerable for simple cases, but it's fragile and doesn't scale well. Vim has a full scripting language that includes functions and custom commands, and it can be used to do all the things you're doing now, but in a much more robust fashion. Vim scripting is a big topic, but I'd start here:
:help script
Be sure to read about the :normal command, which lets you execute Normal-mode commands (like 2j and 10k) within scripts.
Good luck!
I finally found the culprit. Somehow I had a command mapping on <C-J> in my .vimrc file. When read with the default cpoptions, this turned into a mapping on <NL>.
How I found out: I noticed that when starting vim with -u ~/.vimrc, it would indeed execute yanked snippets. I generated a session file with and without that commandline option and compared them. This way I found out that a different set of cpoptions where used to read the same .vimrc file, so that in one case the mapping was indeed on <C-J>, in the other it was converted into a mapping on <NL>!
If someone has a similar problem, I suggest to look carefully at the currently set command mappings, with :cmap.

Resources