This article provides an introduction to the internals of SpaceVim
layers, with the end goal being to prepare interested SpaceVim users
with creating their own. To achieve this, I'll first present a
high-level overview of how SpaceVim layers work in general, then give a
demonstration by example of how to create a new one.
Theoretical Overview
Bootstrapping
When Vim is launched, SpaceVim takes over by sourcing
~/.SpaceVim/config/main.vim
, where after some compatibility and
environment checks and sets, all SpaceVim features and functionalities
are set into motion sequentially by the 3 core functions below:
-
SpaceVim#begin()
, where various basic options are set, common
functions and keybindings defined, and default layers initialized
-
SpaceVim#custom#load()
, where the user's personal configuration,
namely their init.toml
inside ~/.SpaceVim.d/
, is processed
-
and finally SpaceVim#end()
, where among other important setup steps
– such as initializing autocommands, statusline info, a few key mappings
and CWD management – the user-enabled layers and their corresponding
plugins are loaded
It is from inside SpaceVim#end()
where functions responsible for the
loading and configuring of SpaceVim's layers (and thus plugins by proxy)
are called. And these will be the focus of our explorations.
Rationale
As I'd explained in my previous
post
on SpaceVim, layers are the modular building blocks of SpaceVim.
The prototypical layer bundles together related plugins, configures
them appropriately, and add keybindings to conveniently integrate their
functionalities into SpaceVim.
As SpaceVim development continues, with new features, functionalities
and components consistently added, the modularity provided by layers
help to ensure the project remains its maintainability and extensibility.
Organization
Inside ../autoload/SpaceVim/layers/
, you'll find the vimscript sources
for the actual layers, for example:
../layers/edit.vim
../layers/git.vim
../layers/fzf.vim
../layers/lsp.vim
../layers/shell.vim
There also exist layers organized hierarchically under 3 subdirectories
of ../layers/
itself, with some examples being:
../layers/core/statusline.vim
../layers/core/tabline.vim
../layers/tools/dash.vim
../layers/tools/screensaver.vim
../layers/lang/haskell.vim
../layers/lang/javascript.vim
../layers/lang/python.vim
../layers/lang/vim.vim
SpaceVim currently provides layer support for 80+ programming languages,
with more constantly being added all the time.
Layer Callbacks
A loadable layer "foo" corresponds to a single vimscript source
../layers/foo.vim
, inside which callback functions responsible for
hooking up the layer's functionalities to SpaceVim are defined. In
general, there are 3 functions that make up a layer source:
-
SpaceVim#layers#foo#plugins()
adds the plugins to be included in
layer "foo"
-
SpaceVim#layers#foo#set_variable(var)
retrieves options local
to layer "foo", if any exists, that are set by the user in their
init.toml
config
-
SpaceVim#layers#foo#config()
configures the plugins added by layer
"foo"; and if applicable, defines appropriate SpaceVim autocommands
and/or keybindings for the functionalities offered by this layer
When SpaceVim#end()
is called, SpaceVim will look for the callbacks
defined above for each enabled layer, thereby loading and configuring
their functionalities.
Practical Demonstration
Now let's put theory to practice with a concrete example.
For this, I shall provide a condensed walkthrough that'll
highlight the key steps involved in creating my own layer for
CtrlSpace, a project
navigation and workflow management plugin.
1. Create layer source
First and foremost, you need to create the vimscript source for the
new layer under ../layers/
. So for our example, that would be
../layers/ctrlspace.vim
.
2. Define layer callback
SpaceVim#layers#ctrlspace#plugins
for specifying the plugin included in this layer
SpaceVim#layers#ctrlspace#set_variable
for retrieving layer-local options set by the user
SpaceVim#layers#ctrlspace#config
for configuring key aspects of the plugin and layer
function! SpaceVim#layers#ctrlspace#plugins() abort
" add plugins for layer
endfunction
function! SpaceVim#layers#ctrlspace#set_variable(var) abort
" set options local to layer
endfunction
function! SpaceVim#layers#ctrlspace#config() abort
" configure plugins
" ...
" add keybindings to SpaceVim
" ...
endfunction
And now we shall implement them one by one.
3. Specify layer plugins
The CtrlSpace layer contains the single plugin, "vim-ctrlspace", so we
can directly return it.
function! SpaceVim#layers#ctrlspace#plugins() abort
return [ ['vim-ctrlspace/vim-ctrlspace', {'merged': 0}], ]
" 'merged' is an optimization flag used by the plugin manager
endfunction
For layers with more than one plugin, you'd add all of them to the
return list. Note: the second element in each plugin-list is a table
of some flags understood by the plugin manager, which we need not be
concerned with for this guide.
4. Get layer-local options
In our layer example, the option home-mapping-key
, which defaults
to <C-Space>
, sets the main keybinding for invoking CtrlSpace.
Because for many users <C-Space>
may already be occupied, especially
if they hail from a non-English speaking part of the world, this
option allows them to change their home-mapping-key
to something more
suitable, such as <C-p>
.
function! SpaceVim#layers#ctrlspace#set_variable(var) abort
let s:home_mapping_key = get(a:var, 'home-mapping-key', s:home_mapping_key)
" ...
" other layer options
" ...
endfunction
The get
function looks for a string name matching home-mapping-key
(2nd arg) in the list of layer-local variables
(1st arg) set by the user in their init.toml
. If it exists,
it will set s:home_mapping_key
to the value defined by the user
in their TOML config; if not, it will fall back to an appropriately
initialized default value passed as the 3rd argument to
get
.
5. Configure layer
a) main plugin settings
With the plugin made available, and user configurations parsed, the
plugin can finally be appropriately configured.
function! SpaceVim#layers#ctrlspace#config() abort
let g:CtrlSpaceDefaultMappingKey = s:home_mapping_key
" ...
" configuration of other plugin settings
" ...
endfunction
Often times, layer options are made available to user to begin with due
to the existence of the corresponding settings in your layer plugin(s)
of choice. So in such cases, just hook them up directly like we did
above.
b) other plugin settings
An important setting for CtrlSpace is its glob command, which determines
how it collects and populates the files list for new projects.
function! SpaceVim#layers#ctrlspace#config() abort
" ...
" ...
if executable("rg")
let g:CtrlSpaceGlobCommand = 'rg --color=never --files'
elseif executable("ag")
let g:CtrlSpaceGlobCommand = 'ag -l --nocolor -g ""'
else
let g:CtrlSpaceGlobCommand = ''
call SpaceVim#logger#info('CtrlSpace: **an appropriate warning message**')
endif
" ...
" ...
endfunction
The above snippet also conveniently showcases SpaceVim's logging API,
which has 3 levels: info
, warn
and error
. The runtime log (shown
below) can be viewed with the command :SPRuntimeLog
or with the SPC h
L
keybinding.
c) SpaceVim keybindings
Here's also a good place to configure keybindings to integrate your
plugins' functionalities into SpaceVim.
function! SpaceVim#layers#ctrlspace#config() abort
" ...
" ...
call SpaceVim#mapping#space#def('nnoremap', ['b', 'b'], 'CtrlSpace A', 'CtrlSpace: search all buffers', 1)
call SpaceVim#mapping#space#def('nnoremap', ['p', 'f'], 'CtrlSpace O', 'CtrlSpace search project files', 1)
call SpaceVim#mapping#space#def('nnoremap', ['p', 'b'], 'CtrlSpace B', 'CtrlSpace: search bookmarks', 1)
" ...
" more keybindings
" ...
endfunction
To define a SPC
-prefixed keybinding for some functionality of your
layer, SpaceVim#mapping#space#def
is used. This function takes 5
arguments:
- the mapping type
- the input keys to follow the
SPC
-prefix
- the command to be executed
- a description for the key-mapped command, which will be displayed in
SpaceVim's discoverable guide prompt
- an integer flag specifying the type of command (i.e. a
:
command,
<plug>
mapping, etc.) of the 3rd argument
The most commonly used function you'll be using will be
SpaceVim#mapping#space#def
. But mapping APIs for other prefixes (such
as <Leader>
) also exist under SpaceVim#mapping#
.
6. Test the new layer
To enable your newly created layer, just add in in your init.toml
.
For my example, the name of the layer to be enabled is of course
"ctrlspace"
. I change the main mapping key for invoking the plugin
from its default of <C-Space>
to <C-p>
, and also enable the SpaceVim
styled keybindings, some of which you saw defined above.
[[layers]]
name = "ctrlspace"
home-mapping-key = "<C-p>"
enable-spacevim-styled-keys = true
If all went well, then you'll have just created a fully functional new
SpaceVim layer, just like the CtrlSpace example we went through above.
Final Words
In no particular order, here are a some additional caveats and
miscellaneous notes on creating new layers:
-
Language layers are often expected to provide runtime support in
the form of REPLs or 'runners'. The APIs for setting those up can
be found in repl.vim
and runner.vim
respectively (both under
../autoload/SpaceVim/plugins/
)
-
Some plugins may require special configurations either at the time of
their loading or possibly even before. This can be achieved by creating
vimscript sources for them under ~/.SpaceVim/config/plugins/
and
~/.SpaceVim/config/plugins_before/
respectively; then passing the
loadconf
and loadconf_before
flags to the plugin manager (just like
the merged
flag seen earlier)
-
Occasionally you might find layers that interact with or depend
on others. For example, the git and GitHub layers, and the various
lang
layers supported by the LSP layer
-
Last but not least, every new layer should be thoroughly documented.
Just create a markdown doc, add it under ~/.SpaceVim/docs/layers/
,
and SpaceVim will automatically render it for viewing on its site using
Jekyll.
The primary purpose of this article is to serve as a useful introduction
into the internals of SpaceVim layers. But perhaps more importantly, I
hope it also provided a bit of inspiration for some of you to give it at
shot at creating your own layers. I certainly will continue to do so!
This post originally appeared on Medium.