vim scripting damian conway

2020-09-04 06:23

load a file

:source %

note continuation character is on next line

let full_name =
\   first_name . ' ' . middle_initial

can have multiple statements with bar

echo hello | echo hello

every colon command is also a statement



" this is a comment

(be careful since it might expect a string)

several comment confusion mitigation strategies:

echo "this is a string" | " comment
"# this is a more friendly comment
"// this is a more friendly comment


String, Number, List, Dictionary (maybe float)

int: - 999 - 999e01 - 0777 - 0xFACADE

string: - “your "text":2D <C-Z> and backslash \” - ‘you’‘re text here is not special’

list: - [1,2,3,4]

dict: - { ‘a’: 1, ‘b’:2, ‘c’: ‘3PO’}

:help expr9


dynamically typed system:

let name = "Logan"
let height = 165
let talks = ['VIM','P6U','KEY']
let duration = {'VIM':1,'PY':2}
let task = function('myFunc')
unlet duration

once a variable has taken a value, that variable cannot become another type

(this throws a type error unless you unlet from the namespace)

scope can be made explicit with a prefix

g:var global
s:var script
w:var window
t:var tab
b:var buffer
l:var func
a:var arg
v:var vim predefined variables

(idea: let code do different things depending on context)

Scoping is encouraged, but not manditory. Otherwise defaults to function.

(Note you can also get the LUT with g:, etc)

Also pseudovariables - variable-like direct access to vim environment components

let &tabstop = 4             options
let &l:matchpairs .= '<:>'   local opt
let &g:digraph = !&g:digraph global opt
let @/ = ""                  registers
echo $HOME                   env vars


similar to other languages


=  +=  -=  .= (append)


condition ? expr : expr


&&  ||

comparitors (string or numeric, honors ignorecase!)

==  !=  >  >=  <=

--? (explicit case insensitive)
--# (explicit case sensitive)

pattern match:

=~  !~


+ - . (concat)


*  /  %

unary: ##

The single most important thing to understand for expressions is the way that arithmetic operations work. Float support was added later on and the method used to keep backwards compatibility is frustrating.

for filenum in range(filecount)
    echo (filenum / filecount * 100) . '%done'
    call process_file(filenum)

The issue here is that int / int will not reflect a float

Pre-multiply by 1.0 to force float

The second most common problem is seen by:

for result in result_list
    sum += result
echo "Sum was: " . sum

You must use “let” to assign variables in all cases!

control structures

if condition
elseif other_condition

No parentheses required. All conditions end with end

for varname in list

for [x, y, z] in listoflists

let g:words = {1:'one',2:'two'}
for [key,val] in items(g:words)
    echo key . ': ' . val

continue and break make an appearance in vim script as well

for number in list_of_numbers:
    if number > 0
    elseif number < 9
    echo number

built-in functions

hundreds of functions

:help function-list

most important:

let len = strlen(str)
let substring = strpart(str, start, len)
let tail = strpart(str, start)

let substring = str[start : end]
let tail = str[start:]
let tail = str[:end]

let middle_chars = str[1:-2]
let three_last = str[-3:-1]

Note that negative single chars won’t work:

let last_char = str[-1]

Instead use this:

let last_char = str[-1:]

sprintf is actually printf (doesn’t print, just returns the string)

let f = printf("%-2s %d, text, num)

let soft = tolower(str)
let loud = toupper(str)
let tagged = tr(str, "{}", "<>")

let line = repeat('-',78)

expand built-in concepts

let thisfile = expand('%')
let prevfile = expand('#')
let cursorword = expand('<cword>')
let cursorpath = expand('<cfile>')
let cfg_file = expand('~/.vimrc')


:nmap ;e :execute 'next ' . expand('<cfile>')<CR>

pattern matching

let matches = str =~ 'pattern'
let matchloc = match(str, 'pattern', start)
let matchend = matchend(str, 'pattern')
let matchend = matchend(str, 'pattern', start, n)

Most useful is probably the substring one:

let matchedstr = str  matchstr(str, 'pattern')

Capture groups are done with ( and ) Note of form: [fullmatch, cap1, cap2]

let cap = matchlist(str, 'p\(.*\)')

Note that substitue does not modify text

let sub = substitute(text, pattern, replacement, '')
let sub = substitute(text, pattern, replacement, 'g')


echo "Hello World"
echo generalFunction()
echo "this" "that"
echoerr "Oops! This is an error"

let name = input("Enter your name: ")

if confirm('Your name is reall ' . name . '?', "&Yes\n&No", 1)
    let choices = ['Shall I call you:',
        \ ' 1: ' . name,
        \ ' 2: Bruce'
        \ ]o
    let nicknum = inputlist(choices)
    echo "\n\nYou chose:" nicknum


echo getline(2)

let all_lines = getline(1,'$')

let currline = line('.')
let context = getline(currline-2,currline+2)

let CURR_LINE_TEXT = toupper(getline('.'))
let failed = setline('.', CURR_LINE_TEXT)

call append('$', '__END__')

" insert as new first line
let failed = append(0, '#!/usr/local/bin/python')

:nmap ;c :call append(line('.')-1 '//=====[ Comment ]====')<CR>

Cursor Control

let abs_cursor_column = col('.')     | " physical position in memory
let abs_cursor_column = virtcol('.') | " treats tabs, etc as expected
let abs_cursor_line = line('.')

let rel_cursor_column = wincol()     | " relative to current view into text
let rel_cursor_line = winline()

move the cursor:

call cursor(line, col)

call search('^\s*#')
call search('^\s*#', 'b')            | " back to previous
call search('^\s*#', 'n')            | " no actual movement, just return

NOTE: vimscript throws an error if you discard function return value, so we use “call”

let save_cursor = getpos('.')
let setpos('.', save_cursor)

interacting with the system / filesystem functions

let filepaths = glob('~/src/**/*README*')
let filepaths = globpath('.,~,/usr/local/src','**/*README*')

let filename = 'subdir/file.txt'
let interesting_bit = fnamemodify(filename, modify_how)
:help filename-modifiers

e.g. ‘:p’ - full path ‘:t’ - file name ‘:e’ - extension ‘:p:r’ - fill path without extension ‘:s/file/life/’ - substitute command

let cwd = getcwd()

Note chdir is a command, not a function– call with “execute”

execute 'chdir ' . new_dir

call mkdir(new_dir_path)
call mkdir(new_dir_path, 'p') | " creates intermediate folders if not exist

let failed = rename(old_filename, new_filename)

let failed = delete(filename)

let lines = readfile(filename)
let failed = writefile(lines, filename)

let html_source = system('curl -f ' . url)

let unique_words = system('uniq', words)     | " second argument piped

Code that Runs Code

call func(args)         | " ignores the result of the call

evaluate commands

execute '%s/' . pattern . '/' . replacement . '/g'

let cmd = '.-' . context_lines . 'delete ' . (2*context_lines + 1)
execute cmd

evaluate expressions

let expr = input('Type in a Vim expression: ')
echo eval(expr)

Data Structures (to be continued)

Lists are a possibly heterogeneous sequence of values stored in a variable

let var = [1,2,3,4,‘five’] echo var[0] let var[0] = 42 let var[4] .= ‘-thirty’

user functions

function SaveBackup () abort
    execute 'saveas ' . bufname('%') . '.backup_' . g:backup_count
    let g:backup_count += 1

Note names are uppercase or explicitly scoped (i.e. g: or s:). Arguments are prefixed with a:

function ExpurgateText (text) abort
    let expurgated = a:text
    for expletive in g:expletives_list
        let expurgated = substitute(expurgated, expletive, '[DELETED]', 'g')
    return expurgated

Note cannot redeclare a function without error due to interpreted nature of vim. Avoid with func!

function! SaveBackup () abort ...

The abort keyword assures a failure terminates execution of a subroutine

let success = setline('.', ExpurgateText(getline('.')))
call SaveBackup()

Call commands are ranged!

function! DeAmp ()

Do something iteratively per line in the range:

:.,+9call DeAmp()

Avoid annoyance for overkill errors (x9 -> x1) by running:

function! DeAmp () range
    echo 'DeAmping lines ' . a:firstline . ' to ' . a:lastline
    execute a:firstline . ',' . a:lastline . 's/&/&amp;/g'

Functions and Maps

function! DeAmp (lines)
    if a:lines > 1
        echo 'DeAmping ' . a:lines . ' lines'
    execute '.,+' . (a:lines-1) . 's/&/&amp;/ge'

map <silent> && :<C-U>call DeAmp(v:count1)<CR>

That is, no error messages removes range v:count1 tells each function how many lines of range you specified

variable numbers of arguments

function! BackupAndEdit (...)
    for arg in a:000
        call system(printf('cp %s %s.bak', arg, arg))
    execute 'next ' . join(a:000, ' ')

command -nargs=+ Bedit call BackupAndEdit(<f-args>)

function! CommentBlock(comment, ...)
    let indroducer = a:0 >= 1 ? a:1 : "//"
    let box_char = a:0 >= 2 ? a:2 : "*"
    let width  = a:0 >= 3 ? a:3 : strlen(a:comment) + 2

    return introducer . repeat(box_char,width) . "\n"
    \ .  introducer . " " . a:comment          . "\n"
    \ .  introducer . repeat(box_char,width)   . "\n"

function references

function! DoNothing (msg)
    return 0

function! ErrorMsg (msg)
    echohl ErrorMsg
    echo a:msg
    echohl none
    return 1

unlet b:ErrorHandler
let b:ErrorHandler = g:silent_mode ? function('DoNothing')
               \                   : function('ErrorMsg')

let error_handled = call(b:ErrorHandler, ['error message'])

Note: call() is a function call is a command line command

full example

:imap <TAB> <C-N>

helpful, but annoying when you want to put actual tabs in

function TabOrCompletion()
    let col = col('.') - 1
    if col==0 || getline('.')[col - 1] !~ '\k'
        return "\<TAB>"
        return "\<C-N>"

:inoremap <expr> <TAB> TabOrCompletion()