Add vim-folds post
This commit is contained in:
parent
dfd5d1ea05
commit
4546d30a43
|
@ -0,0 +1,210 @@
|
|||
---
|
||||
title: Sorting VCF files with Vim folds
|
||||
tags: vim neovim
|
||||
---
|
||||
|
||||
A while ago I wanted to sort my contacts which had become a mess. I wanted to
|
||||
use this opportunity to do something nice with Vim too. Enter Vim-folds.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Getting Started
|
||||
|
||||
I initiated a repository for the plugin.
|
||||
|
||||
```sh
|
||||
cd ~/repos/
|
||||
git init vcf.Vim
|
||||
```
|
||||
|
||||
I use [Vim-plug](https://github.com/junegunn/Vim-plug) for package management
|
||||
in Vim and added that to `Vim-plug`
|
||||
|
||||
```Vim
|
||||
Plug '~/repos/vcf.Vim/'
|
||||
```
|
||||
|
||||
Next, I exported all my contacts into a Virtual Contact File.
|
||||
|
||||
## Filetype Detection
|
||||
|
||||
To get started I needed Vim to assign a filetype to VCFs. A small `autocmd` is
|
||||
enough to take care of that. By convention, filetype detection files go in
|
||||
`ftdetect` directory.
|
||||
|
||||
`ftdetect/vcf.Vim`
|
||||
```Vim
|
||||
au BufNewFile,BufRead *.vcf set filetype=vcf
|
||||
```
|
||||
|
||||
This sets the filetype of all files with filenames ending with `.vcf` to `vcf`
|
||||
|
||||
## Folding
|
||||
|
||||
Plugins for specific filetypes are stored in the `ftplugin` directory. Vim
|
||||
sources the file if the filetype matches the filename without the `.vim`
|
||||
extension e.g. if the filetype is set to `javascript`, `javascript.vim` in
|
||||
`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
|
||||
|
||||
```
|
||||
Fname Lname····································
|
||||
```
|
||||
|
||||
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 `set
|
||||
foldexpr=OurFunction()`.
|
||||
|
||||
Let's write a function first and set `foldexpr` to it. Define the function by
|
||||
the usual syntax.
|
||||
|
||||
```vim
|
||||
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
|
||||
`getline` function.
|
||||
|
||||
```vim
|
||||
let thisline = getline(v:lnum)
|
||||
```
|
||||
|
||||
We want to start a new level fold at every line which starts with `BEGIN`. For
|
||||
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 `n`th level fold there
|
||||
+ `=` - This tells vim that the fold level is same as previous line.
|
||||
+ `n` - This tells vim that the fold level is `n`
|
||||
|
||||
There are more return values. Check `:help fold-expr`
|
||||
|
||||
We can simply set the fold level to `>1` at every `BEGIN` and set it to `=` on
|
||||
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 `=`.
|
||||
|
||||
|
||||
```vim
|
||||
if match(thisline, '^BEGIN') >= 0
|
||||
return ">1"
|
||||
endif
|
||||
return "="
|
||||
```
|
||||
|
||||
Putting it all together, we get.
|
||||
|
||||
```vim
|
||||
function! VCFFold()
|
||||
let thisline = getline(v:lnum)
|
||||
if match(thisline, '^BEGIN') >= 0
|
||||
return ">1"
|
||||
endif
|
||||
return "="
|
||||
endfunction
|
||||
set foldmethod=expr
|
||||
set foldexpr=VCFFold()
|
||||
```
|
||||
|
||||
## Fold Text
|
||||
|
||||
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.
|
||||
|
||||
```vim
|
||||
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:foldstart` and `v:foldend` which are the line numbers
|
||||
where the current fold starts and ends. We can iterate from `v:foldstart` to
|
||||
`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 `No Name`.
|
||||
|
||||
```vim
|
||||
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.
|
||||
|
||||
```vim
|
||||
let l:parts = split(l:thisline, ';')
|
||||
return substitute(join(l:parts[1:], " ") . l:parts[0][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.
|
||||
|
||||
## Complete Program
|
||||
|
||||
```vim
|
||||
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[0][2:], '\s\+', ' ', 'g')
|
||||
endif
|
||||
endfor
|
||||
return "No Name"
|
||||
endfunction
|
||||
|
||||
set foldtext=VCFFoldText()
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
If you just want the above program, it is available as
|
||||
[vcf.vim](https://gitlab.com/ceda_ei/vcf.vim). You can install it with
|
||||
[Vim-plug](https://github.com/junegunn/Vim-plug) via:
|
||||
|
||||
```vim
|
||||
Plug 'https://gitlab.com/ceda_ei/vcf.vim.git'
|
||||
```
|
Loading…
Reference in New Issue