I’ve longed for a tool which would help me in manual investigation of server logs.
I couldn’t find any thus I created a small Vim plugin
which highlights text in the log.
Getting to the state of having the plugin forced me to learn basics of
the Vim scripting language.
Here I’m going to summarize few findings.
There are many articles about Vim script. I found nice in particular these two series:
Ok, I want to write my script, how to get it running?
When testing my steps are creating a new source code file
where I put the script - normally I would create a function.
At the next step I use command to load the code from the file from Vim.
I use command :source <file path>
for loading the code and
:call <function name>
to execute the function.
Let’s take an example. I have the function which I save into the file /tmp/test.function.vim
.
function! ListBuffers()
let buffers = filter(range(1, bufnr('$')), 'bufexists(v:val)')
for buffer in buffers
if getbufvar(buffer, "&filetype") == 'help'
" this is a 'help' buffer, skipping
continue
endif
" ec is short version for command echo
ec 'Having an opened buffer: ' . buffer
endfor
endfunction
Note
|
notice use of the ! at the end of the function declaration. This says
loading the function for the second time will override the existing function with the same name.
If you do not add the ! then the Vim will not load the function and only
shouts at you that the error happened.
|
The function iterates over all opened buffers
and prints their name. It will skip to print the name of the buffer of type 'help'
.
When I have the function I can load and execute it.
" loading the source code file aka. loading the function
" shortened version of :source command is :so
:source /tmp/test.function.vim
" invoking the function ListBuffers
" shortened version of :call command is :cal
:call ListBuffers()
Note
|
If you want to get printed the return value of the function you can use
There is more echo commands than only the |
Note
|
if you have opened n empty Vim window the function ListBuffers prints only number 1 .
To open another buffer (to get at least printed two numbers 1 and 2 ), use "badd command.
:badd open buffer with a file but it does not load it.
For example :badd something .
|
Now I can start to tune my function. But it’s a bit cumbersome
to write :so
and :cal
for any change in the script.
What about map these commands to a keyboard shortcuts? In the following example
if I press ;s
the source is loaded from the file and after pressing ;b
the function listing buffers is invoked.
" mapping loading the file with :source command to the keyboard shortcut ';s'
:nmap ;s :source /tmp/test.function.vim<CR>
" mapping calling the function ListBuffers to the keyboard shortcut ';b'
:nmap ;b :call ListBuffers()<CR>
:nmap <silent> ;= :call ListBuffers()<CR>
<CR>
means literal carriage return
which is needed when function should be executed
(it replaces hitting `<enter>
when you write it at the Vim command line)
<silent>
causes that nmap mapping does not echo the command which is executing.
In our case where the function just prints to the command line the usage of the silent
won’t make any difference in the behaviour.
Note
|
Let me know better ways in this. |
Why mapping keys have to be put between <C-R>=
and <CR>
in the insert mode?
<C-R>=
tells Vim to insert the result of the command (a function name which ends with ()
) to the text.
<CR>
tells Vim to actually evaluate the preceding expression.
For example you can create mapping to insert current date at place the cursor stands
when you double press the Ctrl+D
in the insert mode this way
" double CTRL+D prints date into the place of cursor
:imap <silent> <C-D><C-D> <C-R>=strftime("%e %b %Y")<CR>
" double CTRL+T prints time into the place of cursor
:imap <silent> <C-T><C-T> <C-R>=strftime("%l:%M %p")<CR>
" CTRL+Y prints output of the function 'ListBuffers' at the place of the cursor
:imap <silent> <C-Y> <C-R><C-R>=ListBuffers()<CR>
<C-R>
abbreviation for Ctrl+R
is not the same as <CR>
(literal carriage return
)
<C-R><C-R>
it inserts the result of the subsequent expression as if it had been directly typed, which means that any special characters within the result retain their special meanings and behavior
To run a function that needs to escape from the insert mode use C-O
(:help i_CTRL-O
)
`
let cursor_back = "\<C-O>:call setpos('.'," . string(cursorpos) . ")\<CR>"
`
For mapping TAB
use inoremap
(see :help i_CTRL-R
).
The inoremap
is a form of the imap
. It’s used when the returned characters could contain the TAB
character.
That means if TAB
is returned it won’t invoked by the mapped function again.
See more about mapping at Vim mapping keys wiki page.
To show messages printed during the execution try :messages
(shortened to :mes
).
For writing functions check quick tutorial at http://learnvimscriptthehardway.stevelosh.com/chapters/23.html
The Vim uses symbol .
for the strings concatenation.
Comments to be placed with "
(double-quote
)
String in Vim script can be declared within "
too. If you want to use comment but vim script expects
"
for the string declaration then use |"
(vertical bar
+ double-quote
) as the separator.
Each statement ends with end of line. For the statement over more lines use \
(backslash
) at the start of the line, e.g.
call SetName(
\ first_name,
\ second_name
\)
On the other hand more statements could be put onto one line when separated with the vertical bar
(|
)
echo "Starting..." | call ListBuffers() | echo "Done"
A strange thing (at least for me) is existence of the prefixed variables. If you won’t use the prefix they do not work for you as you would expect. See below or check list of prefixes at https://codeyarns.com/2010/11/26/how-to-view-variables-in-vim.
For adding a text to the current line you can use function setline
.
:call setline(line('.'), getline('.') . ' hello')
which adds text ' hello'
at the end of the current line
For pasting a new line to the position of the particular row you can use function append
.
:call append(line('.'), "new line to be added at the bottom of the current line")
type | example |
---|---|
scalar |
|
list |
|
dictionary |
|
Warning
|
Variable types, once assigned, are permanent and strictly enforced at runtime. As we set the interests as a list there will be error now (see at ibm.com, vim script part 1)
|
Prefix | Meaning |
---|---|
|
The variable is global |
|
The variable is local to the current script file |
|
The variable is local to the current editor window |
|
The variable is local to the current editor tab |
|
The variable is local to the current editor buffer |
|
The variable is local to the current function |
|
The variable is a parameter of the current function |
|
The variable is one that Vim predefines |
Prefix | Meaning |
---|---|
|
A Vim option (local option if defined, otherwise global) |
|
A local Vim option |
|
A global Vim option |
|
A Vim register |
|
An environment variable |
Operation | Operator syntax |
---|---|
Assignment |
|
Numeric-add-and-assign |
|
Numeric-subtract-and-assign |
|
String-concatenate-and-assign |
|
Ternary operator |
|
Logical OR |
`bool |
bool` |
|
Logical AND |
|
Numeric or string equality |
|
String case insensitive eq |
|
String case sensitive eq |
|
Numeric or string inequality |
|
Numeric or string greater-then |
|
Numeric or string gr-or-eq |
|
Numeric or string less than |
|
Numeric or string l-or-eq |
|
Numeric addition |
|
Numeric subtraction |
|
String concatenation |
|
Numeric multiplication |
|
Numeric division |
|
Numeric modulus |
|
Convert to number |
|
Numeric negation |
|
Logical NOT |
|
Parenthetical precedence |
|
numeric value zero is false in the boolean context; any non-zero numeric value is considered true
when a string is used as a boolean, it is first converted to an integer, and then evaluated for true
for checking emptiness is needed to be used a function: empty(a_string)
comparators always perform numeric comparison (unless both operands are strings). In particular, if one operand is a string and the other a number, the string will be converted to a number.
let ident='Vim'
ident == 0
always numeric equality - always true (string 'Vim'
converted to number 0
)
ident == '0'
uses string equality if ident
contains string but numeric equality if ident
contains number
case sentistive/insensitive could be used with any comparators (<#
operator means less-than case sensitive
)
string comparision honor the settings of vim’s ignorecase
option, using the #
and ?
will force
either case sensitive or case insensitive comparision
Floating point has to be explicitly marked. Floating point arithmetic comes at Vim 7.2 and later.
let filecount = 234
echo filecount/100 |" echoes 2
echo filecount/100.0 |" echoes 2.34
if left_width >= 0
let max_align_col = max([max_align_col, left_width])
...
endif
for linetext in getline(firstline, lastline)
" working with text on the line
...
endfor
for linenum in range(firstline, lastline)
" working with line numbers
...
for [name, rank, serial] in list_of_lists
echo rank . ' ' . name . '(' . serial . ')'
endfor
return completion . (restore ? cursor_back : "")
if curr_line =~ '\k' . curr_pos_pat
return "\<C-N>"
endif
if 0
echom "if"
elseif "nope!"
echom "elseif"
else
echom "finally!"
endif``
" mkdir can throw E739 error when is unable to create the requested directory
try
call mkdir( required_dir, 'p' )
catch
echo "Can't create directory " . required_dir
endtry
let data = [1,2,3,4,5,6,"seven"]
And various manipulation on it - e.g. indices less than zero, which then count backward from the end of the list:
let data[-1] .= ' samurai'
operator =
compares values (all values are the same, containers can be different)
operator is
compares identity (containers have to be the same)
let pow = [
\ [ 1, 0, 0, 0 ],
\ [ 1, 1, 1, 1 ],
\]
" and later...
echo pow[x][y]
let activities = ['sleep', 'eat'] + ['game', 'drink']
let activities += ['code']
let weekdays = week[1:5]
list assignment to a variable is assignment of pointer/reference. For having copy use function copy()
or deepcopy()
.
That’s specific functionality working on lists. Filter
filters values
and map
applies some function on each value of the list.
let positive_only = filter(copy(list_of_numbers), 'v:val >= 0')
let increased_numbers = map(copy(list_of_numbers), 'v:val + 10')
let seen = {} " Haven't seen anything yet
let daytonum = { 'Sun':0, 'Mon':1, 'Tue':2, 'Wed':3, 'Thu':4, 'Fri':5, 'Sat':6 }
let day = daytonum['Sun']
for [next_key, next_val] in items(dict)
let result = process(next_val)
echo "Result for " next_key " is " result
endfor
remove(dictionary, 'key')
unlet dictionary['key'] "command unlet used
function <name>()
declares function, name has to be unique, parentheses can contain arguments of the function
function! <name>()
declares function, if function of the name exists it’s overridden
function ends with definition endfunction
function name has to start with capital letter or with s:
which declares it as local for the current script file
Function could be scoped in the same way as variables can be - e.g. function s:<name>()
says that function is visible only in scope of current script file (see s:
).
Important
|
In difference from other scripting languages you can’t ignore return value of function.
If function returns anything you need to use it - ie. |
If function does not return anything then you can invoke it by calling through call
like call s:functionname()
.
function name(param, param2)
You can access to parameters by name or by position. In both cases you need to use prefix a:
for get value.
function printme(text)
echo a:text
echo a:1
endfunction
Note
|
if position argument is used then a:0 contains number of arguments.
|
For undefined number of arguments use …
. For examplee function CommentBlock(comment, …)
.
Now you can access to comment as a:comment
and any other arguments are accesible
via position parameter declaration. You can check number of argument by let introducer = a:0 >= 1 ? a:1 : "//"
.
Normally the function is called in scope of current line. You can define scope that function work at,
by scope definition <from,to>call <function_name>
. For example 5,$call CommentBlock
will call CommentBlock function
for each line starting line number 5 and ending at the end of the file.
If we want to have special handling of the ranges we can say that range attributes
won’t be considered and function will called just once function DeAmperfyAll() range
.
The word range
says to call function only once (not once for each line in the range).
Then there could be added special parameters a:firstline
and a:lastline
which returns the range that user called the function within.
We can use it for example for linenum in range(a:firstline, a:lastline)
If visual mode is used then function for the range of the visual block could be used as Vip:call DeAmperfyAll()
.
To help and to see what functions we can use
:help functions
and listing :help function-list
|
internal statement showing a string (result of function) on line at bottom of window |
|
says if particular property is declared/exists - e.g. |
|
ask user to write a text and that is returned from the function |
|
returns on-screen column (or "virtual column"), '.' argument specifies that you want the column number of the current cursor position |
|
returns position of cursor - for current line it’s |
|
setting position of cursor |
|
function to look backwards through the file from the cursor position, search(regexp pattern to find, configuration string - e.g. |
|
shows guifont name, works only for gvim, font name could be set with |
|
returns line defined with number - getline(line_number) |
|
brother of |
|
returns line number e.g. |
|
returns part of the string that matches pattern - ie. matchstr(string_to_check, target_pattern) - returns that part of the string where target pattern matches, ie. matchstr('abc', 'b.') returns 'bc' |
|
tries to match a character from text defined by regexp - ie. |
|
returns a list of all the fields captured by the regex - ie. |
|
substitutes text - |
|
returns bigger number from a list (see below) |
|
returns length of a string |
|
printing text in reformated way specified by formatter |
|
used to evaluate a string as if it were a Vimscript command |
|
executing regexp stuff - e.g. |
|
expanding expression, expansion could be modified (see |
|
is requested path directory which exists |
|
creating directory, flags could be used |
|
asking user for confirmation, it will gives options for user in way of |
|
inner command that exits function or so |
|
returns current date |
`` |
|
providing shallow copy of a list (as normally assigning a list to different property means only referencing the same pointer) |
|
providing deep copy of a list |
|
length of list |
|
is list empty?, the same as |
|
maximum or minimum from list of numbers |
|
index of first occurrence of value or pattern in list, is |
|
index of first occurrence of value or pattern in list, is |
|
generating list of numbers in some range e.g. |
|
split to sequence of word ie. |
|
joining list values |
|
|
|
|
|
|
|
remove({list},{index}) removes item from the list and returns it |
|
|
|
|
list of keys from dictionary |
|
list of values from dictionary |
|
say if particular dictionary has a key |
|
list of lists where each sublist contains 'key' and 'value' of the dictionary item |
|
adding other dictionary to a dictionary |
|
remove key from dictionary |
|
remove from dictionary (inner command, not a function) |
|
true if no entries at all |
|
how many entries? |
|
how many values are equal to str? |
|
find largest value of any entry |
|
find smallest value of any entry |
|
transform values by eval’ing string |
|
print dictionary as key/value pairs |
To get info about what are available events for the hooks run
:help autocmd-events
or for detailed info :help autocmd-events-abc
.
Mechanism of interception of events is known as autocommand
.
autocmd EventName filename_pattern :command
autocmd EventName,EventName2,... filename_pattern :command
autocmd EventName filename_pattern :silent command
EventName
is one of help page :help autocmd-events
, if more events are specified the autocmd
will be invoked for any of them
filename_pattern
is similar to bash pattern see :help autocmd-patterns
command is any valid vim command (colon at the start of command is optional but recommended)
Vim normally displays a notification after command completes. To disable that the option silent
could be used
Autocommands could be grouped and then worked together
augroup GROUPNAME
" autocommand specifications here ...
augroup END
Deactivation with autocmd!
command
" generic syntax
autocmd! [group] [EventName [filename_pattern]]
" deactivate whole group not depending on event name
autocmd! Unfocussed * *.txt
" deactivate whole group not concerning file type
autocmd! Unfocussed
autocmd!
itself deactivates from current group and is useful for doing cleanup
before settings group autocommands.
Adding an autocmd!
to the start of every group is important because autocommands
do not statically declare event handlers; they dynamically create them.
augroup Unfocussed
autocmd!
autocmd FocusLost *.txt :call Autosave()
...
augroup END
Another useful set of events are BufWritePre
, FileWritePre
, and FileAppendPre
.
These Pre
events are queued just before your Vim session writes a buffer back to disk
(as a result of a command such as :write
, :update
, or :saveas
).
For all three types of events, Vim sets the special line-number aliases '[
and ']
to the range of lines being written. For example usage for s
is:
'[,']s/^This file last updated: \zs.*/\= strftime("%c") /
:help user-commands
Simple example how user can create his own command: command Showme echo 'hello'
Note
|
user command has to start with the capital letter |
For command that will call a function and pass arguments do
command! -nargs=1 MyCommand call s:MyFunc(<f-args>)
command! -nargs=1 Showme echo <f-args>
For quoting arguments use (as seen above for echo we can do it easier without execute
command being used but this is for showing how that could be)
:command! -nargs=1 FW execute "echo" string(<q-args>)
These were my notes on Vim scripting. Hopefully somebody finds them useful. I will be happy if you let me know how to enhance this guide.