without a doubt though, it is a good starting point (even though about 13 years old as I write this!). Age is not a liability here. VimScript classic, for all of its warts and idiosyncracies, has not changed over that span of time.
Use :set to manipulate vim options. Use :set noxxx to turn off xxx.
Use :set xxx! to flip the value of a boolean option.
Use :set xxx? to inspect the value of an option.
Use "map" to redefine what keys do. Don't try to use comments here. Plain "map" applies to all modes. The varieties nmap, vmap, and imap restrict the map to the specified mode. This is called "modal mapping".
To put control keys into vimscript use <c-d> and <esc> To remove maps use "unmap" or "nunmap"
These mappings will take other mappings into account, which can lead to infinite recursions and other mischief. To avoid this (which you always want to), use noremap, nnoremap, vnoremap, inoremap instead.
To add new things to vim, rather than redefining existing things, you will generally use a "leader" (and thus add a 2 character command) Popular leaders are comma (,) and dash (-). There is also a "localleader" that can be restricted to certain file types. Here is a mapping to surround a word with quotes:
:nnoremapAnd this one lets you type "jk" rather than" viw a" bi" lel
:inoremap jk:inoremap
The hard way adds -ev to open your .vimrc in a split window so that it is easy to add stuff the moment you think about it. EV stands for "edit .vimrc". This goes along with -es which sources the new .vimrc.
Abbreviations can be added. To add an insert mode abbrev: ":iabbrev xxx yyy". He lets @@ be an abbreviation for his email, and "ccopy" an abbreviation for his copyright line.
You can create buffer-local mappings. You will generally use localleader:
:nnoremapYou can also have buffer-local settings (and buffer local abbreviations if you like).x dd
:setlocal nowrapYou may have launched vim on a new filename, then decided to exit without doing anything. No empty file gets created on disk. To change this behavior:
:autocmd BufNewFile * :writeHere we have triggered on the "BufNewFile" event. Here we have a filter *, but we could be more restrictive. We can specify multiple events:
:autocmd BufWritePre,BufRead *.html :normal gg=G :autocmd BufNewFile,BufRead *.html setlocal nowrapThe first line above re-indents our file when we read or write it.
The "FileType" event triggers when vim sets the filetype for a buffer. This can let us set up specific mappings for file types.
:autocmd FileType javascript nnoremap <buffer> <localleader>c I//<esc> :autocmd FileType python nnoremap <buffer> <localleader>c I#<esc>These comment out lines in each file type.
When you define an autocmd, it "accumulates" rather than replacing any existing autocmd. So if you source your .vimrc (as you might), each time you source it, you replicate all of the autocmd in the file. If nothing else, this affects performance. The fix is:
augroup filetype_html
autocmd!
autocmd FileType html nnoremap <buffer> <localleader>f Vatzf
augroup END
The kicker is the "autocmd!" line which clears any preexisting autocmd
in that group.
A final topic is "operator pending" mappings. Just prepend an "o" to any of the preceding (such as onoremap). The "d" command is an example. It is waiting (pending) for you to tell it what to delete. An example:
:onoremap b /return<cr>Now if we type "db" it deletes all lines up to and including a return.
:onoremap in( :<c-u>normal! f(vi(<cr>You will need to visit his page for the details on this. He says the c-u thing is essential but isn't going to explain why yet. I'm not itching to use these, so I'll just keep moving along.
:normal -- just executes what follows as a normal command (like dd)
:execute -- takes what follows as a vimscript string and runs it (more later on this)
normal won't recognize special characters (like <cr> and so we use execute.
We can fool with the status line at the bottom of the screen. The game here is to use ":set statusline=...". See "help statusline" and/or find the page in "Hard Way" for details. You can use setlocal to have different status lines for different file types.
noremap , /ret<cr>To get the word under the cursor, use expand('<cword>')
noremap , :echo expand('<cword>')<cr>
The following solves the problem that first started me on this page.
It takes the word under the cursor, then searches for that word with
a variable amount of white space prepended, and at the start of a line.
function Jumper()
let word = expand('<cword>')
call search ( "^ *" . word )
endfun
noremap , :call Jumper()<cr>
There is probably some way to get this all on the noremap line,
but this works, and I am not macho enough yet to play vimscript golf.
We begin with an essay on "responsible coding". We also get a warning about how vimscript has grown over time with various weird issues. Both vim9script and Lua are perhaps answers to this. He talks about using vim code folding to cope with long complex .vimrc files
If you want to reference the value of an option, use &wrap You can have local options via &l:wrap.
Vim has "if" conditionals via:
:if xyz : echo "spam" ;endifWe also get else and elseif (not elsif).
Vimscript has funky rules about true and false. The value 0 is false. Vim also has weird ideas about types and when and how to convert one to another.
Beware == it may or may no be case sensitive depending on the setting of "ignorecase". Use ==? which is always case insensitive. Use ==# which is always case sensitive.
And we get functions. The first weird thing is they must start with a capital letter. (If they are unscoped, so just capitalize them).
You can use "call" to call them, or just use them in expressions. They can return values. They can have arguments also. In the function, refer to arguments as a:arg1 -- this is scoping.
You can do variable length argument lists:
:function Myfunc ( ... ) :echom a:0 :echom a:1 :endfunctionHere a:0 tells you how many arguments and a:1 gets the first one.
Numbers and strings are basically as you might expect. You use the dot (.) to concatenate two strings. Using single quotes says no escaped special characters, everything is verbatim.
String functions:
strlen() len() split() join(list,joiner) tolower() toupper()
The execute() command evaluates a string as vimscript. This allows you to build and then execute commands on the fly. Execute() sometimes allows you to bypass Vim quirks.
The :normal command takes whatever follows and treats it as if you typed it in normal mode. Plain old :normal will honor any key mappings. Using normal! will not. However :normal doesn't understand <cr> or <esc>.
Now look at this:
:execute "normal! gg/foo\<cr>dd"This goes to the top of the file (gg) then searches for "foo" and when it finds it, deletes that line.
This idiom gets around the business of :normal not understanding <cr>. What is happening is that :execute is mapping <cr> to an actual return and that is what :normal! sees. Note that the backslash is needed.
Take a look at :help helpgrep, but sometimes Google is faster and easier.
Plenty of excitement when we get to regular expressions. Consider this:
:execute "normal! gg/for .\\+ in .\\+:\<cr>"This searches for the first for loop in a file. We need double backslashes because :execute removes one and we need one left for the regex that normal sees.
Vim has 4 ways to parse regular expressions! Something to read about elsewhere. If we start the regex with \v we get "very magic" mode. Stick to this and avoid the other 3 weird vim regex flavors.
:help magic :help match :help pattern-overview :help grep :help make
The tutorial now launches into an extended case study of "grep". What grep does is to run an external grep and load the results into the quickfix list. The <leader>g will trigger our new feature. It will then take a "motion" like "w", so we use it like:
<leader>giw -- grep for word under cursor <leader>giW -- grep for WORD under cursor <leader>gi' -- grep for what is inside the single quoted string you are on.So this would give us the first case:
:nnoremap <leader>g :grep -R <cword> .<cr> :nnoremap <leader>g :grep -R '<cword>' .<cr>The second form is better as it forces literal interpretation of the word.
:nnoremap <leader>g :execute "grep -R expand('<cWORD>') ."<cr>
:nnoremap <leader>g :execute "grep -R " . shellescape(expand("<cWORD>")) . " ."<cr>
:nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>
In most cases, it is all the same but we get more insurance using the shellescape wrapper.
The final case displays the quickfix window and avoids going to the first result.
He now has us create a file in the .vim/plugin directory. On my linux system this is ~tom/.vim/plugin. We can call it "operator.vim"
Such files get automatically loaded and sourced just like .vimrc He says to make this the following, which he calls "black magic" at this point:
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
function! GrepOperator(type)
echom "Test"
endfunction
We test this by typing <leader>giw. What we do is to set the operatorfunc,
then the g@ calls the function as an operator.
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>The c-u thing here clears out the : line (vim puts a range there to help us in visual mode, but we don't want that)
I like his approach. Get something simple working and transform it until it becomes what we want and need.
The function visualmode() returns a single character to tell us the type of visual mode -- v if characterwise, V if linewise, Ctrl-v if blockwise. We can get "char" or "line" if we are in normal mode.
I skim over this grep example because I have little or no interest (at this time) in visual mode. He calls "shellescape(@@)" which I find interesting. This takes @@ (the contents of the unnamed register) and escapes any tricky characters so they don't cause trouble. Then he does this:
silent execute "grep! -R " . shellescape(@@) . " ."He wraps this up by adding code to save and restore the @@ register. He also puts his GrepOperator function into a specific namespace by prefixing it with "s:". This gives it script local scope. This needs to then be referenced as <SID>GrepOperator.
Vimscript has lists. [1, 2, 3]. They are zero indexed. You can use xyz[-1] to get the last element (like python). You can slice too, but not in the stupid way python does. You use xyz[1,2] to get 2 and 3 from the above. You can also slice strings. Combine lists with +
len(xyz) get(xyz,1) -- fetch an element index(xyz, "dog") -- find an item in a list join(list, ",") -- joins a list into a string reverse(list)We have for and while loops like:
for x in [1,2,3,4] endfor while aa > 99 endwhileWe have dictionaries (hashes).
table = { 'a' : 1, 'b' : 2 }
thing = table[key]
thing = table.key
table[key] = thing
remove(table, key)
x = get(table,key,default)
has_key(table,key)
list = items(table)
keys = keys(table)
vals = values(table)
I skip a section on toggling.
Now he is going to talk (of all things) about functional programming! He hand-crafts and plays with several functional style functions.
Now some helpers to work with paths on filesystems.
expand('%') gives the relative path of the file you are now editing
(% means "the current file").
expand('%:p') gives the absolute path of the file you are now editing
globpath ('.', '*') lists all files in a directory in one string.
split(globpath ('.', '*'),'\n') makes that into a list.
Now we are on to part 3. He suggests that this is a good place to start and that you should come back when you want to make a full plugin. He creates a plugin to add support for the Potion programming language.
That is what I will do (stop here and come back later).
Tom's Electronics pages / tom@mmto.org