I initiated a repository for the plugin.
cd ~/repos/ git init vcf.Vim
I use Vim-plug
for package management
in Vim and added that to
Next, I exported all my contacts into a Virtual Contact File.
To get started I needed Vim to assign a filetype to VCFs. A small
enough to take care of that. By convention, filetype detection files go in
au BufNewFile,BufRead *.vcf set filetype=vcf
This sets the filetype of all files with filenames ending with
Plugins for specific filetypes are stored in the
ftplugin directory. Vim
sources the file if the filetype matches the filename without the
extension e.g. if the filetype is set to
ftplugin directory will be sourced.
Each contact in a VCF looks like this:
BEGIN:VCARD VERSION:2.1 N:Lname;Fname;;; FN:Fname Lname TEL;VOICE:+911234567890 END:VCARD
I want my folds to look like
I am going to use
expr foldmethod. This can simply be done using
set foldmethod=expr. What this tells vim is to run a function on every line and
set the fold level based on that. To set the expression to run, we use
Let’s write a function first and set
foldexpr to it. Define the function by
the usual syntax.
function! VCFFold() endfunction set foldmethod=expr set foldexpr=VCFFold()
When the function is run, vim sets a special variable
v:num that tells us
which line the function is being run on. To get the current line, we use the
let thisline = getline(v:lnum)
We want to start a new level fold at every line which starts with
all the other lines, we want to keep the same fold level as previous line since
we don’t have nested folds in VCF. Vim folds with expr work by running the
function on each line and determining the fold level of that line based on the
return value of that function. Some of the return values are:
>n- This tells vim to start a new
nth level fold there
=- This tells vim that the fold level is same as previous line.
n- This tells vim that the fold level is
There are more return values. Check
We can simply set the fold level to
>1 at every
BEGIN and set it to
every other line. This way, every contact will end up in a fold starting at the
BEGIN of every contact. We can simply use
match function to check if the
line begins with
BEGIN and return
>1 in that case, else we will return
if match(thisline, '^BEGIN') >= 0 return ">1" endif return "="
Putting it all together, we get.
function! VCFFold() let thisline = getline(v:lnum) if match(thisline, '^BEGIN') >= 0 return ">1" endif return "=" endfunction set foldmethod=expr set foldexpr=VCFFold()
To set the fold text, we have to set the
foldtext to a function. Let’s create
a funtion named
VCFFoldText for this purpose and set
foldtext to it.
function! VCFFoldText() endfunction set foldtext=VCFFoldText()
The name is stored as a line
N:Lname;Fname;;;. Two special variables set for
foldtext function are
v:foldend which are the line numbers
where the current fold starts and ends. We can iterate from
v:foldend using the range function. While iterating, we can simply look for a
line that begins with
N: and return the name from it. If we don’t find any
such line, we can return
function! VCFFoldText() for i in range(v:foldstart, v:foldend) let l:thisline = getline(i) if match(l:thisline, '^N:') >= 0 " Return the string here endif endfor return "No Name" endfunction
All we need to do is split the parts on semi-colons and join that array back depending on our preferences of whether we want first name first or last name first. In my case I want first name first.
let l:parts = split(l:thisline, ';') return substitute(join(l:parts[1:], " ") . l:parts[2:], '\s\+', ' ', 'g')
split splits the string into an array with the second parameter (
; in this
case) as delimiter. I then join all the elements from first element (skipping
the zeroth element which is the last name) and then append the zeroth element
without the first two characters (since those are
N:). Finally, I replace
multiple spaces with one space.
function! VCFFold() let thisline = getline(v:lnum) if match(thisline, '^BEGIN') >= 0 return ">1" endif return "=" endfunction set foldmethod=expr set foldexpr=VCFFold() function! VCFFoldText() for i in range(v:foldstart, v:foldend) let l:thisline = getline(i) if match(l:thisline, '^N:') >= 0 let l:parts = split(l:thisline, ';') return substitute(join(l:parts[1:], " ") . l:parts[2:], '\s\+', ' ', 'g') endif endfor return "No Name" endfunction set foldtext=VCFFoldText()