A simple, Hello World-style tutorial for how to create a vim-ale
plugin,
including formatter, linter, and/or LSP server (Language Server Protocol).
vi ./example.hello.txt
![vim-ale-hello-world in action](https://private-user-images.githubusercontent.com/122831/258193340-844d9522-b2be-4642-8e14-6f848d5d2126.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMDUwMzYsIm5iZiI6MTcyNDEwNDczNiwicGF0aCI6Ii8xMjI4MzEvMjU4MTkzMzQwLTg0NGQ5NTIyLWIyYmUtNDY0Mi04ZTE0LTZmODQ4ZDVkMjEyNi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODE5JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgxOVQyMTU4NTZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT01ZDQ3OTBjYjEyMDQ1ZDdjOTY0YmMzNWQzMWJjNjVlODMyYWNjYjBiN2U3MzE2ZWU3YjhkNTNmOGU3ZmYzMWZiJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.V9EA3IdqNw5HE6TZOnjUB-qkLq-56sMDCbpJaU3Audc)
In this tutorial / example project we have:
hellolang |
A fictional language for this example (*.hello.txt ) |
---|---|
hello-fmt |
The formatter; What vim-ale calls a "fixer" |
hello-lint |
The linter with LSP support |
- HelloLang Demo
- Anatomy of a vim-ale plugin
- A Short Intro to VimScript
- The Language Server Protocol
- Debugging
-
Install
vim-ale-hello-world
to~/.vim/pack/plugin/start/
git clone --depth=1 \ https://github.com/beyondcodebootcamp/vim-ale-hello-world.git \ ~/.vim/pack/plugins/start/vim-ale-hello-world
-
Update
~/.vimrc
to recognizehellolang
let g:ale_fix_on_save = 1 let g:ale_lint_on_save = 1 let g:ale_fixers = { \ 'hellolang': ['hellofmt'], \} " NOTE: generally you don't need manual linter config " let g:ale_linters = { " \ 'hellolang': ['hellolint'], " \}
-
Install
node
and thehello-lint
Language Servercurl https://webi.sh/node | sh
pushd ~/.vim/pack/plugins/start/vim-ale-hello-world/ npm ci
-
Update your
PATH
to includehello-fmt
andhello-lint
at~/.vim/pack/plugins/start/vim-ale-hello-world/bin/
export PATH="$HOME/.vim/pack/plugins/start/vim-ale-hello-world/bin/:$PATH"
Now *.hello.txt
is registered as hellolang
which using hello-fmt
as a
fixer, hello-lint
as a linter (and for LSP), and javascript
for syntax
highlighting (because custom syntax highlighting is outside the scope of this
tutorial).
You can try opening and saving (:w
) a file to watch the magic happen:
vi ./example.hello.txt
:w
// Needs more Hello, World!
There are TWO possible configurations for a plugin:
An internal plugin is a Pull Request to vim-ale
:
~/
├── bin/
│ ├── hello-fmt
│ └── hello-lint
│
└── .vim/
├── pack/plugins/start/ale/
│ │
│ ├── ale_linters/hellolang/
│ │ └── hello_lint.vim
│ │
│ └── autoload/ale/
│ ├── fix/
│ │ └── registry.vim
│ └── fixers/
│ └── hello_fmt.vim
│
└── plugins/
└── hellolang.vim
The advantage of an external plugin is that you don't have to get your plugin into "core" in order for others to use it.
THIS plugin is external (obviously vim-ale
wouldn't want our demo language in
core).
~/
├── bin/
│ ├── hello-fmt
│ └── hello-lint
│
└── .vim/
├── pack/plugins/start/vim-ale-hello-world/
│ │
│ ├── ale_linters/hellolang/
│ │ └── hello_lint.vim
│ │
│ ├── autoload/hello_world/fixers/
│ │ └── hello_fmt.vim
│ │
│ └── plugin/
│ └── hello_world.vim
│
└── plugins/
└── hellolang.vim
Here's the minimum you need to know about vimscript to create a plugin:
- special directories
- autoload
- plugin
- functions & paths
- variables & scope
The autoload
directory will LAZY LOAD functions when they are called.
Vim files in plugin
will always load as soon as vim
starts.
-
A function declaration looks like this:
function! hello_world#fixers#hello_fmt#GetExecutable(buffer) abort " ... endfunction
function
means declare andfunction!
means declare or replace- The function name is
GetExecutable
. - The function path is
hello_world#fixers#hello_fmt
. - File and function paths MUST use
_
(underscore) rather than-
(hyphen)
-
A function invocation looks like this:
call hello_world#fixers#hello_fmt#GetExecutable(buffer)
-
If the function is not defined at the time it is called, autoload will attempt to find it by translating its function path to a file path:
Function Path: hello_world#fixers#hello_fmt#GetExecutable Plugin Root: ~/.vim/pack/plugins/start/vim-ale-hello-world/ Fixer Path: ./autoload/ale/fixers/hello_fmt.vim
There are some special scope prefixes. The prefixes allow you to reuse the same name in different scopes or contexts.
a:foo
refers to a function argument (even if it was declared as 'foo')let g:foo = 'bar'
refers to a global variable (accessible everywhere)let l:foo = 'bar'
refers to a local variable (scoped to a function)function! s:foo()
declares the function local to the current script
LSP Messages are HTTP-like and
- Start with
Content-Type: <byte-length>
- Separated by
\r\n
- Contain a JSON body
LSP has tonnes of features, very few of which are needed by vim-ale
for
linting.
See ./events/.
:messages
:ALEInfo
--log <logfile>
Many errors are silenced by default and not available in :messages
.
Often the last few lines of :ALEInfo
will contain more helpful error messages
and warnings.
You can use --log <logfile>
to view the output of ch_log
, which can help in
debugging a variety of problems.
vim --log ~/vim.log
tail -f ~/vim.log