# `failwind.nvim`
It all started here...
![[Pasted image 20240806165512.png|500]]
When I hear someone say "configuration", I immediately start salivating like Pavlov getting excited to ring the bell. I thought to myself
> Surely I can make Neovim's primary configuration language be CSS
> [!NOTE]
> I want to mention that I only asked whether I could do this thing, not whether I should. Something my twitch chat reminded me of regularly
# Configuration
Let's start with the simplest configuration we can specify using `failwind`: Neovim options.
## Configuration: Options
```neovim {"update":true,"path":"_/code/1722978571694.css","timestamp":1725472708323}
<pre>
<span class="Tag"><span class="-type">options</span></span> <span class="Delimiter">{</span>
<span class="-property">number</span><span class="Delimiter">:</span> <span class="String">true</span><span class="Delimiter">;</span>
<span class="-property">relativenumber</span><span class="Delimiter">:</span> <span class="String">false</span><span class="Delimiter">;</span>
<span class="Comment"><span class="-spell">/* ... other options ... */</span></span>
<span class="-property">shiftwidth</span><span class="Delimiter">:</span> <span class="-number">4</span><span class="Delimiter">;</span>
<span class="Comment"><span class="-spell">/* Language specific options */</span></span>
<span class="Tag"><span class="-type">css</span></span> <span class="Delimiter">{</span>
<span class="-property">expandtab</span><span class="Delimiter">:</span> <span class="String">true</span><span class="Delimiter">;</span>
<span class="-property">shiftwidth</span><span class="Delimiter">:</span> <span class="-number">2</span><span class="Delimiter">;</span>
<span class="-property">tabstop</span><span class="Delimiter">:</span> <span class="-number">2</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Tag"><span class="-type">javascript</span></span><span class="Delimiter">,</span> <span class="Tag"><span class="-type">typescript</span></span> <span class="Delimiter">{</span>
<span class="-property">tabstop</span><span class="Delimiter">:</span> <span class="-number">2</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
</pre>
```
^1722978571694
Neovim options are configured by including a `tag selector` or `options`, and then providing a key-value pairing of options and values. Rich option types are accepted, not just strings. So you can pass numbers, strings, booleans, and lists.
Furthermore, you can specify filetype specific options using nested `tag selectors` - even passing multiple `tag selectors` as a list to configure multiple filetypes at once!
## Configuration: Keymaps
Of course, basic configuration wouldn't be complete without being able to specify keymaps!
One of the nice things about turning CSS into a DSL is that I can do anything I want with it! For example, we have a different ways of specifying what happens on the right-hand side of a keymap.
```neovim {"update":true,"path":"_/code/1722999600204.css","timestamp":1725472708075}
<pre>
<span class="Comment"><span class="-spell">/* Top-level keymaps container */</span></span>
<span class="Tag"><span class="-type">keymaps</span></span> <span class="Delimiter">{</span>
<span class="Tag"><span class="-type">normal</span></span> <span class="Delimiter">{</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<esc>"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span> <span class="-property">command</span><span class="Delimiter">:</span> <span class="String">"nohlsearch"</span><span class="Delimiter">;</span> <span class="Delimiter">}</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<leader>q"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">action</span><span class="Delimiter">:</span> <span class="-function">lua</span><span class="Delimiter">(</span><span class="String">"vim.diagnostic.setloclist()"</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Tag"><span class="-type">terminal</span></span> <span class="Delimiter">{</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<esc><esc>"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">action</span><span class="Delimiter">:</span> <span class="String">"<C-\><C-n>"</span><span class="Delimiter">;</span>
<span class="-property">desc</span><span class="Delimiter">:</span> <span class="String">"wow, escape terminal mode"</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
</pre>
```
^1722999600204
The first item you'll see is using the `command` property. This turns any string into a `<cmd>{command}<CR>` mapping - which is to say something you'd run as an ex-command.
```neovim {"update":true,"path":"_/code/1722999742398.css","timestamp":1725472707846}
<pre>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<esc>"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span> <span class="-property">command</span><span class="Delimiter">:</span> <span class="String">"nohlsearch"</span><span class="Delimiter">;</span> <span class="Delimiter">}</span>
</pre>
```
^1722999742398
In our example of `<esc>`, this is the same as writing:
```
-- vimscript
nnoremap <esc> <cmd>nohlsearch<cr>
-- lua
vim.keymap.set("n", "<esc>", "<cmd>nohlsearch<cr>")
```
So I actually think it looks pretty nice the way we describe it in CSS! I'm still playing around with some other options for specifying the key (it's generally a bit difficult to do because keymaps can have arbitrary characters, which don't play well with CSS identifiers).
```neovim {"update":true,"path":"_/code/1722999867231.css","timestamp":1725472707614}
<pre>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<leader>q"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">action</span><span class="Delimiter">:</span> <span class="-function">lua</span><span class="Delimiter">(</span><span class="String">"vim.diagnostic.setloclist()"</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
</pre>
```
^1722999867231
Beyond just specifying an ex-command, we can also specify an `action`, which will be placed directly inside of a `function` that is created for the keymap. The above code is equivalent to:
```neovim {"update":true,"path":"_/code/1722999911679.lua","timestamp":1725472707388}
<pre>
<span class="-variable">vim</span><span class="Delimiter">.</span><span class="-variable">keymap</span><span class="Delimiter">.</span><span class="-variable">set</span><span class="-function-bracket">(</span><span class="String">"n"</span><span class="Delimiter">,</span> <span class="String">"<leader>q"</span><span class="Delimiter">,</span> <span class="-keyword">function</span>()
<span class="-variable">vim</span><span class="Delimiter">.</span><span class="-variable">diagnostic</span><span class="Delimiter">.</span><span class="-variable">setqflist</span><span class="-function-bracket">(</span><span class="-function-bracket">)</span>
<span class="-keyword">end</span><span class="-function-bracket">)</span>
</pre>
```
^1722999911679
I think this one is probably just objectively worse because we lose all of our LSP support for this, but it's still cool that we can embed the Lua code directly into the CSS and then call it later from within Neovim.
Lastly, if the action is just a string, then we place that on the right-hand side of the mapping, like below:
```neovim {"update":true,"path":"_/code/1722999986048.css","timestamp":1725472707159}
<pre>
<span class="Tag"><span class="-type">terminal</span></span> <span class="Delimiter">{</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">key</span></span></span><span class="Delimiter">(</span><span class="String">"<esc><esc>"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">action</span><span class="Delimiter">:</span> <span class="String">"<C-\><C-n>"</span><span class="Delimiter">;</span>
<span class="-property">desc</span><span class="Delimiter">:</span> <span class="String">"wow, escape terminal mode"</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
</pre>
```
^1722999986048
This just directly maps `<esc><esc>` in terminal mode to the sequence of characters used to escape terminal mode (a pretty convenient mapping!).
Of course, we support adding descriptions to mappings as well.
## Configuration: Plugins
I think this is too long for the first post, so let me know if you want to read a more in-depth item about this by commenting on this article. I'm happy to write something up if people find this interesting and got this far in the post. The teaser looks something like this:
```neovim {"update":true,"path":"_/code/1723000097577.css","timestamp":1725472706926}
<pre>
<span class="Tag"><span class="-type">plugins</span></span> <span class="Delimiter">{</span>
<span class="Comment"><span class="-spell">/* Plugin, with default setup configuration */</span></span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">repo</span></span></span><span class="Delimiter">(</span><span class="String">"echasnovski/mini.nvim"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">setup</span><span class="Delimiter">:</span> <span class="String">"mini.trailspace"</span> <span class="String">"mini.ai"</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Comment"><span class="-spell">/* Plugin, with custom setup configuration */</span></span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">repo</span></span></span><span class="Delimiter">(</span><span class="String">"lewis6991/gitsigns.nvim"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">setup</span></span></span><span class="Delimiter">(</span><span class="String">"gitsigns"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="Tag"><span class="-type">signs</span></span> <span class="Delimiter">{</span>
<span class="Tag"><span class="-type">add</span></span> <span class="Delimiter">{</span> <span class="-property">text</span><span class="Delimiter">:</span> <span class="String">'+'</span> <span class="Delimiter">}</span>
<span class="Tag"><span class="-type">change</span></span> <span class="Delimiter">{</span> <span class="-property">text</span><span class="Delimiter">:</span> <span class="String">'~'</span> <span class="Delimiter">}</span>
<span class="Tag"><span class="-type">delete</span></span> <span class="Delimiter">{</span> <span class="-property">text</span><span class="Delimiter">:</span> <span class="String">'_'</span> <span class="Delimiter">}</span>
<span class="Tag"><span class="-type">topdelete</span></span> <span class="Delimiter">{</span> <span class="-property">text</span><span class="Delimiter">:</span> <span class="String">'‾'</span> <span class="Delimiter">}</span>
<span class="Tag"><span class="-type">changedelete</span></span> <span class="Delimiter">{</span> <span class="-property">text</span><span class="Delimiter">:</span> <span class="String">'~'</span> <span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">repo</span></span></span><span class="Delimiter">(</span><span class="String">"nvim-telescope/telescope.nvim"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">depends</span><span class="Delimiter">:</span> <span class="String">"nvim-lua/plenary.nvim"</span><span class="Delimiter">;</span>
<span class="-property">setup</span><span class="Delimiter">:</span> <span class="String">'telescope'</span><span class="Delimiter">;</span>
<span class="Tag"><span class="-type">keymaps</span></span> <span class="Delimiter">{</span>
<span class="Tag"><span class="-type">normal</span></span> <span class="Delimiter">{</span>
<span class="Delimiter">:</span><span class="Tag">key</span><span class="Delimiter">(</span><span class="String">' sh'</span><span class="Delimiter">)</span> <span class="Delimiter">{</span> <span class="-property">command</span><span class="Delimiter">:</span> <span class="String">"Telescope help_tags"</span><span class="Delimiter">;</span> <span class="Delimiter">}</span>
<span class="Comment"><span class="-spell">/* ... */</span></span>
<span class="Delimiter">:</span><span class="Tag">key</span><span class="Delimiter">(</span><span class="String">' '</span><span class="Delimiter">)</span> <span class="Delimiter">{</span> <span class="-property">command</span><span class="Delimiter">:</span> <span class="String">"Telescope buffers"</span><span class="Delimiter">;</span> <span class="Delimiter">}</span>
<span class="Delimiter">:</span><span class="Tag">key</span><span class="Delimiter">(</span><span class="String">' sn'</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">desc</span><span class="Delimiter">:</span> <span class="String">"Search Neovim Files"</span><span class="Delimiter">;</span>
<span class="-keyword">@call</span> <span class="-function">require</span><span class="Delimiter">(</span><span class="String">'telescope.builtin'</span><span class="Delimiter">)</span><span class="Delimiter">.</span><span class="-type"><span class="-property">find_files</span></span> <span class="Delimiter">{</span>
<span class="-property">prompt_title</span><span class="Delimiter">:</span> <span class="String">"Search Neovim DotFiles"</span><span class="Delimiter">;</span>
<span class="-property">cwd</span><span class="Delimiter">:</span> <span class="-function">vim-fn-stdpath</span><span class="Delimiter">(</span><span class="String">"config"</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">repo</span></span></span><span class="Delimiter">(</span><span class="String">"neovim/nvim-lspconfig"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">depends</span><span class="Delimiter">:</span>
<span class="String">"williamboman/mason.nvim"</span>
<span class="String">"williamboman/mason-lspconfig.nvim"</span>
<span class="String">"WhoIsSethDaniel/mason-tool-installer.nvim"</span><span class="Delimiter">;</span>
<span class="-property">setup</span><span class="Delimiter">:</span> <span class="String">"mason"</span> <span class="String">"mason-lspconfig"</span><span class="Delimiter">;</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">setup</span></span></span><span class="Delimiter">(</span><span class="String">"mason-tool-installer"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-property">automatic_installation</span><span class="Delimiter">:</span> <span class="String">true</span><span class="Delimiter">;</span>
<span class="-property">ensure_installed</span><span class="Delimiter">:</span> <span class="Delimiter">[</span><span class="String">"lua_ls"</span><span class="Delimiter">,</span> <span class="String">"gopls"</span><span class="Delimiter">]</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">:</span><span class="-type"><span class="PreProc"><span class="-property">config</span></span></span> <span class="Delimiter">{</span>
<span class="-keyword">@call</span> <span class="-function">require</span><span class="Delimiter">(</span><span class="String">'mason-lspconfig'</span><span class="Delimiter">)</span><span class="Delimiter">.</span><span class="-type"><span class="-property">setup_handlers</span></span> <span class="Delimiter">{</span>
<span class="-property">lua_ls</span><span class="Delimiter">:</span> <span class="-function">lua</span><span class="Delimiter">(</span><span class="String">'require("lspconfig").lua_ls.setup {}'</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-property">gopls</span><span class="Delimiter">:</span> <span class="-function">lua</span><span class="Delimiter">(</span><span class="String">'require("lspconfig").gopls.setup {}'</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
</pre>
```
^1723000097577
In this configuration we setup a few simple plugins, but also get far enough to start attaching LSPs to Lua and Go (and setup auto-installation as well!).
Of course we have telescope working, but I removed a few of the keymaps for brevity.
## Configuration: Evaluation
Once you have your configuration in some `css` file, it can be loaded by doing:
```neovim {"update":true,"path":"_/code/1723000261387.lua","timestamp":1725472706710}
<pre>
<span class="-keyword">local</span> <span class="-variable">css_file</span><span class="Operator"> =</span> <span class="String">"/path/to/file.css"</span><span class="-variable"><span class="-function-call-lua">
require</span></span><span class="-function-bracket">(</span><span class="String">"failwind"</span><span class="-function-bracket">)</span><span class="Delimiter">.</span><span class="-variable"><span class="-function-call-lua">evaluate</span></span><span class="-function-bracket">(</span><span class="-variable">css_file</span><span class="-function-bracket">)</span>
</pre>
```
^1723000261387
That's it! `failwind` will load and evaluate the file, install any necessary plugins and then setup them up based on your configuration. It actually works surprisingly well so far.
I am still playing quite a bit with some ideas about how we're going to do some of the more complicated configuration items, importing other configurations and more. I have been having an absolute blast goofing around with this idea on stream lately.
# Implementation
Very short summary of the implementation is that I use `tree-sitter` and the CSS parser to walk the tree and effectively have a tree-walking interpreter written in Lua that executes Lua based on the nodes we hit in the AST.
If you're interested in a more detailed write-up for this, let me know. It's not all done, so this part is a bit more in flux anyway.
Thanks for reading :)
# Acknowledgements
Thanks [@jarredsumner](https://x.com/jarredsumner) for this terrible idea! It has been a blast making [this idea](https://x.com/jarredsumner/status/1817174551022969042) happen.