blog/public/so-you-want-to-write-a-neovim-plugin-with-lua/index.html
2025-08-11 18:27:20 +01:00

102 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="https://nonsense.dymc.win/favicon.ico">
<link rel="stylesheet" href="/css/style.min.css">
<link rel="canonical" href="https://nonsense.dymc.win/so-you-want-to-write-a-neovim-plugin-with-lua/" />
<title>so you want to write a neovim plugin with lua</title>
</head>
<body><header id="banner">
<h2><a href="https://nonsense.dymc.win/">James&#39; Blog :-)</a></h2>
<nav>
<ul>
<li>
<a href="/info/" title="info">info</a>
</li>
</ul>
</nav>
</header>
<main id="content">
<article>
<header id="post-header">
<h1>so you want to write a neovim plugin with lua</h1>
<div>
<time>April 6, 2024</time>
</div>
</header><p>I&rsquo;ve recently been messing around with writing neovim plugins.
When I initially got going I found it a little tricky to know how to get started.
There&rsquo;s the <a href="https://neovim.io/doc">official neovim docs</a> which are great; but in my beginner experience exhaustive to the point of slight impenetrability.
Beyond that, the thing I found most useful was simply reading the source of some popular plugins to get an idea of how things worked.
I would recommend sticking to plugins with a smaller scope though.</p>
<p>As a demostrative MVP (minimal viable plugin) jumping-off-point, I&rsquo;m going to make a very simple note-taking plugin.
It will provide a command to neovim which when run opens a file titled with the date and time in a specific notes directory.
Vamos.</p>
<p>This is what you will want your directory structure to look like.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── lua
</span></span><span class="line"><span class="cl">│ └── note
</span></span><span class="line"><span class="cl">│ └── init.lua
</span></span><span class="line"><span class="cl">└── plugin
</span></span><span class="line"><span class="cl"> └── note.vim
</span></span></code></pre></div><p>The <code>plugin/note.vim</code> file will look like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">command</span><span class="p">!</span> <span class="nx">Note</span> <span class="nx">lua</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;note&#34;</span><span class="p">)</span>.<span class="nx">main</span><span class="p">()</span>
</span></span></code></pre></div><p>This creates a custom command <code>Note</code> which when run will call a lua function.
Now on to where that function and the meat of the plugin logic will live: the <code>lua/note/init.lua</code> file.
With more complex plugins this section will often be split into many files but we&rsquo;ve just got one here as it&rsquo;s so simple.</p>
<p>First things first we create a plugin object.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">note</span> <span class="o">=</span> <span class="p">{}</span>
</span></span></code></pre></div><p>Then we will define some default options for the plugin in a table.
These are variables you want the user to be able to change when they call the setup function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">defaults</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">note_directory</span> <span class="o">=</span> <span class="s2">&#34;~/notes/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">date_format</span> <span class="o">=</span> <span class="s2">&#34;%Y-%m-%d %H:%M&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">file_extension</span> <span class="o">=</span> <span class="s2">&#34;.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next we need the setup function.
This takes the user&rsquo;s options and merges them with our default options.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kr">function</span> <span class="nc">note</span><span class="p">.</span><span class="nf">setup</span><span class="p">(</span><span class="n">user_options</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="n">options</span> <span class="o">=</span> <span class="n">vim.tbl_deep_extend</span><span class="p">(</span><span class="s2">&#34;force&#34;</span><span class="p">,</span> <span class="n">defaults</span><span class="p">,</span> <span class="n">user_options</span> <span class="ow">or</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span></code></pre></div><p>This is the main function where the magic happens.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kr">function</span> <span class="nc">note</span><span class="p">.</span><span class="nf">main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="kd">local</span> <span class="n">dir</span> <span class="o">=</span> <span class="n">options.note_directory</span>
</span></span><span class="line"><span class="cl"> <span class="kd">local</span> <span class="n">name</span> <span class="o">=</span> <span class="n">os.date</span><span class="p">(</span><span class="n">options.date_format</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="kd">local</span> <span class="n">ext</span> <span class="o">=</span> <span class="n">options.file_extension</span>
</span></span><span class="line"><span class="cl"> <span class="kd">local</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">string.format</span><span class="p">(</span><span class="s2">&#34;%s%s%s&#34;</span><span class="p">,</span> <span class="n">dir</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">ext</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="kd">local</span> <span class="n">command</span> <span class="o">=</span> <span class="n">string.format</span><span class="p">(</span><span class="s2">&#34;edit %s&#34;</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_command</span><span class="p">(</span><span class="n">command</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span></code></pre></div><p>Finally we return the plugin obect.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kr">return</span> <span class="n">note</span>
</span></span></code></pre></div><p>At this point you should have a working plugin :)
As a little coda, this is how you can use your fancy new plugin using <a href="https://github.com/folke/lazy.nvim/">lazy.nvim</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;lazy&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1">-- local</span>
</span></span><span class="line"><span class="cl"> <span class="n">dir</span> <span class="o">=</span> <span class="s2">&#34;~/neovim-note-plugin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">-- github</span>
</span></span><span class="line"><span class="cl"> <span class="c1">-- &#34;me/neovim-note-plugin&#34;,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">-- alternative non github hosting</span>
</span></span><span class="line"><span class="cl"> <span class="c1">-- url = &#34;https://git.example.com/me/neovim note-plugin&#34;,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">config</span> <span class="o">=</span> <span class="n">fucntion</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;note&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="n">file_extension</span> <span class="o">=</span> <span class="s2">&#34;.org&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>Hope you&rsquo;ve enjoyed.</p>
</article>
</main><footer id="footer">
<p>~~~ made with <a href="https://gohugo.io">hugo</a> and my bastardised version of <a href="https://github.com/LukasJoswiak/etch">this nice theme</a> ~~~</p>
</footer>
</body>
</html>