How to create a VIM Plugin
Thomas Brisbout
Freelance JS developer
tbrisbout

Vim Paris Meetup - 2015-12-14
Contents
- Introduction
- Vimscript basics
- Structure
- Patterns
- Plugin Examples
- Publication
Introduction
Why ?
- Does this need a plugin ?
- Is there an existing one ?
- Can I do It ?
- What kind of Plugin ?
- What language ?
- How to Publish it ?
Can I do it ?
When I first wrote vim-bufferline and vim-airline I was very much a newbie Vim scripter
@bling
Types of Plugins
- Motions / Shortcuts / text-objects
- CLI wrapper
- Language Support Plugin
- New Functionality (Ctrl-p / NERDTree...)
Little bit of Vimscript
Properties
- Interpreted
- Main paradigm: procedural
- functional paradigm possible with some helpers (haya14busa/underscore.vim, :help deepcopy())
- basic prototype-OOP with dictionary type
- Dynamic Typing
Basics
" Variable assignment
let x = ''
let x = 2
" Conditionals
if x
echo 'ok'
elseif y
echo 'not ok'
else
echo 'ko'
endif
" Loops
for i in [1, 2, 3, 4]
echo i
endforStrings
" Normal Strings
echo "This is a string"
echo "whith some\n escape chars\n"
" Literal Strings
echo 'A literal \ String'
" Concatenation
echo "Hello " . "World"Lists
echo ['a', 3, 'c']
let l = [1, 2, 3]
echo l[1] "=> 2
echo l[-1] "=> 3
" Concat
let concat = [1, 2] + [3]
" Slicing
echo ['a', 'b', 'c', 'd', 'e'][0:2]
" => ['a', 'b', 'c']Lists Functions
" Modifiers mutates the list
get() get an item without error for wrong index
len() number of items in a List
empty() check if List is empty
insert() insert an item somewhere in a List
add() append an item to a List
extend() append a List to a List
remove() remove one or more items from a List
copy() make a shallow copy of a List
deepcopy() make a full copy of a List
filter() remove selected items from a List
map() change each List item
sort() sort a List
reverse() reverse the order of a List
split() split a String into a List
join() join List items into a String
range() return a List with a sequence of numbers
string() String representation of a List
call() call a function with List as arguments
index() index of a value in a List
max() maximum value in a List
min() minimum value in a List
count() count number of times a value appears in a List
repeat() repeat a List multiple timesDictionaries
" Keys are string
" or coerced to string
echo {'foo': 1, 100: 'bar'}
" => {'foo': 1, '100': 'bar'}Dictionaries Functions
get() get an entry without an error for a wrong key
len() number of entries in a Dictionary
has_key() check whether a key appears in a Dictionary
empty() check if Dictionary is empty
remove() remove an entry from a Dictionary
extend() add entries from one Dictionary to another
filter() remove selected entries from a Dictionary
map() change each Dictionary entry
keys() get List of Dictionary keys
values() get List of Dictionary values
items() get List of Dictionary key-value pairs
copy() make a shallow copy of a Dictionary
deepcopy() make a full copy of a Dictionary
string() String representation of a Dictionary
max() maximum value in a Dictionary
min() minimum value in a Dictionary
count() count number of times a value appears
Functions
" Function declaration
function HelloWorld()
echo "Hello World!"
endfunction
" use ! to override
function! HelloWorld()
...
endfunction
" With arguments
function! HelloMe(name)
echo "Hello " . a:name
endfunction
" Invocation
call HelloWorld()
echo HelloWorld()Scoping
(nothing) In a function: local to a function;
otherwise: global
b: Local to the current buffer.
w: Local to the current window.
t: Local to the current tab page.
g: Global.
l: Local to a function.
s: Local to a |:source|'ed Vim script.
a: Function argument (only inside a function).
v: Global, predefined by Vim.
Vimscript WTF?!
" String coercion
echo "foo" + "bar"
" => 0
echo "10foo" + 5
" => 15
echo "10foo" + "5bar"
" in conditionals
if "foo"
echo "OK"
elseif "123foo"
echo "Wait, what !?"
else
echo "why not..."
endifVimscript WTF again...
" Case sensitive comparisons
echo "foo" == "FOO"
" => 0
set ignorecase
echo "foo" == "FOO"
" => 1
" Specific operator
set ignorecase
echo "foo" ==# "FOO"
" => 0Structure
Simple Plugin Skeleton
" 1 - Mappings
nnoremap K :MyPlugin
" 2 - Functions
function! MyPlugin()
" do something
endfunction
" Commands
command! MyPlugin call MyPlugin()
Project Structure
project/
|
├── after/
├── autoload/
├── colors/
├── compiler/
├── doc/
├── ftdetect/
├── ftplugin/
├── indent/
├── plugin/
├── LICENSE.md
└── README.md
ftdetect
- Run once when vim starts
- Used to set a filetype autocommand
ftplugin
- Run each time a filetype is set
- So each time a file is opened with a given filetype (ex: myfiletype) the ftplugin/filetype.vim runs
- Useful to define file specific mappings and functions
plugin
- Run once when vim starts
- Most of the logic can go here if the plugin is simple
autoload
- Hack used to perform lazy-loading
- The file is run when the function is first called and then cached
" Autoloaded function syntax
" This will look for ./autoload/myplugin.vim
call myplugin#MyFunc()Patterns and tips
Useful mappings
" Time-saving mapping
nnoremap <leader>s :source %
" Even better
nnoremap <leader>s :w | source %
" Even even better
augroup vim_scripts
autocmd!
autocmd Filetype vim nnoremap <buffer> <leader>s :w | source %
augroup END
" Useful Plugin
Plugin 'tpope/vim-scriptease'Execute Normal!
" Common pattern
execute "normal! gg/foo<cr>"Global Setting
" User configuration
if !exists("g:plugin_option")
let g:plugin_option = "Default Value"
endif
Good Practices
- Do not overwrite registers / settings / etc...
- Use function namespaces
Plugin Examples
Better Grep
nnoremap <leader>g :set operatorfunc=<SID>GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call <SID>GrepOperator(visualmode())<cr>
function! s:GrepOperator(type)
let saved_unnamed_register = @@
if a:type ==# 'v'
execute "normal! `<v`>y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
silent execute "grep! -R " . shellescape(@@) . " ."
copen
let @@ = saved_unnamed_register
endfunction
Babel CLI wrapper 1/2
" ./ftdetect/es6.vim
autocmd BufNewFile,BufRead *.es6 set filetype=javascript
Babel CLI wrapper 2/2
" ./plugin/babeljs.vim
if !exists("g:babeljs_command")
let g:babeljs_command = "babel"
endif
if !exists("g:babeljs_presets")
let g:babeljs_presets = "es2015"
endif
function! BabelES5()
let file = expand('%')
execute "new | r !" . g:babeljs_command . " --presets " . g:babeljs_presets. " " . file
set filetype=javascript
normal! ggdd
endfunction
command! Babel call BabelES5()
Publication
Source
- Github
- vim.org
Choose a License
- VIM License
- MIT / Apache 2
- Other (WTFYWT...)
Documentation
- doc in .txt and README.md
- What the plugin does
- Setup (Pathogen compatible)
- API
- Mapping example
- FAQ
- Thanks ...
Some links
- http://learnvimscriptthehardway.stevelosh.com/
- http://vimdoc.sourceforge.net/htmldoc/usr_41.html
- http://www.ibm.com/developerworks/linux/library/l-vim-script-1/index.html
- Every plugin from Tim Pope ...
How to create a VIM Plugin
By Thomas BRISBOUT
How to create a VIM Plugin
This is an introduction to Neovim (Project Status, installation and configuration tips ...)
- 1,474