<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Vim on Clément Joly – Open-Source, Rust &amp; SQLite</title><link>https://joly.pw/tags/vim/</link><description>Recent content in Vim on Clément Joly – Open-Source, Rust &amp; SQLite</description><image><title>Clément Joly – Open-Source, Rust &amp; SQLite</title><url>https://joly.pw/images/open-graph-home-original.png</url><link>https://joly.pw/images/open-graph-home-original.png</link></image><generator>Hugo -- 0.154.3</generator><language>en</language><copyright>Clément Joly</copyright><lastBuildDate>Wed, 11 Mar 2026 03:32:38 +0000</lastBuildDate><atom:link href="https://joly.pw/tags/vim/index.xml" rel="self" type="application/rss+xml"/><item><title>Vim Registers</title><link>https://joly.pw/reg/</link><pubDate>Thu, 29 Jan 2026 14:05:41 +0000</pubDate><guid>https://joly.pw/reg/</guid><description>A quirky but powerful feature.</description><content:encoded><![CDATA[<h2 id="copy-paste-history">Copy-paste History</h2>
<h3 id="implicit">Implicit</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Register</th>
          <th>Content</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><code>&quot;</code></td>
          <td>Effectively last used register. Writes to register <code>0</code>.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>0</code></td>
          <td>Last yank.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>1</code></td>
          <td><code>d</code>/<code>c</code> with <code>%</code>,<code>(</code>, <code>)</code>, <code>`</code>, <code>/</code>, <code>?</code>, <code>n</code>, <code>N</code>, <code>{</code> and <code>}</code> or whole line.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>-</code></td>
          <td>Last small delete (less than a line). Can be the same as <code>1</code>.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>2</code>-<code>9</code></td>
          <td>Last content of <code>1</code>, <code>2</code>, etc. <code>9</code> is lost.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>+</code> <code>*</code></td>
          <td>System clipboards (X11/Wayland clipboard and primary).</td>
      </tr>
  </tbody>
</table>
<h3 id="explicit">Explicit</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Register</th>
          <th>Content</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><code>a</code>-<code>z</code></td>
          <td>Named, use freely. Doesn’t fill numbered registers.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>A</code>-<code>Z</code></td>
          <td>Append to that named register.</td>
      </tr>
  </tbody>
</table>
<h2 id="read-only">Read-Only</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Register</th>
          <th>Content</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><code>.</code></td>
          <td>Last inserted text (similar to <code>.</code> to repeat).</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>%</code></td>
          <td>Current file name.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>#</code></td>
          <td>Alternate file name.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>:</code></td>
          <td>Last executed command line. Run with <code>@:</code>.</td>
      </tr>
  </tbody>
</table>
<h2 id="no-read-or-write">No Read or Write</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Register</th>
          <th>Content</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><code>=</code></td>
          <td>Run an expression.</td>
      </tr>
      <tr>
          <td style="text-align: center"><code>_</code></td>
          <td>Black hole.</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Cargo Info in Neovim, or How Simple Features Go a Long Way</title><link>https://joly.pw/blog/cargo-info-in-neovim/</link><pubDate>Mon, 21 Oct 2024 22:39:34 +0100</pubDate><guid>https://joly.pw/blog/cargo-info-in-neovim/</guid><description>Integrate Vim and Cargo info with just a few lines of Lua using keywordprg.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>Open <code>cargo info</code> in Vim or neovim for the package under the cursor using <a href="#introducing-keywordprg">these 4 lines of Lua</a>.</p>
  </div>



<h2 id="cargo-info">Cargo info</h2>
<p><a href="https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#whats-in-1820-stable">Rust 1.82</a> was released a couple of days ago.
It’s packed with improvements, but one in particular caught my eye.
Cargo now has a <code>info</code> sub-command.
It displays details about a package in the registry from the comfort of your terminal.
Here is an example:</p>
<pre tabindex="0"><code>$ cargo info lazy_static
lazy_static #macro #lazy #static
A macro for declaring lazily evaluated statics in Rust.
version: 1.5.0
license: MIT OR Apache-2.0
rust-version: unknown
documentation: https://docs.rs/lazy_static
repository: https://github.com/rust-lang-nursery/lazy-static.rs
crates.io: https://crates.io/crates/lazy_static/1.5.0
features:
  spin        = [dep:spin]
  spin_no_std = [spin]
note: to see how you depend on lazy_static, run `cargo tree --invert --package lazy_static@1.5.0`
</code></pre><p>Vim and neovim generally composes well with other terminal tools.
So how can we easily integrate <code>cargo info</code> and <code>neovim</code>?</p>
<h2 id="demo">Demo</h2>
<p>The goal is to press a key and display the <code>cargo info</code> for the crate under the cursor in a <code>Cargo.toml</code> file.
Like this:</p>
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/cargo-info-in-neovim/demo.json", document.getElementById('demo3'), {
"idleTimeLimit":  1 ,"poster": "npt:3","preload":  1 ,"speed": "1.5",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://joly.pw/blog/cargo-info-in-neovim/demo.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<h2 id="introducing-keywordprg">Introducing <code>keywordprg</code></h2>
<p>In the above demonstration, we press <a href="https://neovim.io/doc/user/various.html#K"><code>K</code></a> over a crates name. Then neovim executes the program set in <a href="https://neovim.io/doc/user/options.html#'keywordprg'"><code>keywordprg</code></a>, appending the work under the cursor. So if the cursor is on <code>lazy_static</code></p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">dev-dependencies</span>]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">iai</span> = <span style="color:#98c379">&#34;0.1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">insta</span> = <span style="color:#98c379">&#34;1.40.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">lazy_static</span> = <span style="color:#98c379">&#34;1.5.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">#   ^ cursor is here when we press K</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">mktemp</span> = <span style="color:#98c379">&#34;0.5&#34;</span>
</span></span></code></pre></div><p>and <code>keywordprg</code> is set to <code>cargo info</code>, the command <code>cargo info lazy_static</code> is executed in a new terminal.</p>
<p>We can further improve this.
Since we want a fast answer (without checking the remote repository), let’s use <code>cargo info --offline</code>.
And we would like pretty colors, let’s force that using <code>--color=always</code>.</p>
<p>We only want to alter <code>keywordprg</code> when a <code>Cargo.toml</code> file is edited, because it makes no sense to call <code>cargo info</code> in a Python file.
We can use a <a href="https://neovim.io/doc/user/usr_41.html#ftplugin"><code>ftplugin</code></a> for toml files for that<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, so that the plugin is only loaded and executed when a toml file is executed.
Furthermore we can change the setting only when opening a file named “Cargo.toml”, instead of changing it for every toml file.
We can put the necessary configuration in <code>~/.config/nvim/ftplugin/toml.lua</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">vim.endswith</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">bufname</span>(), <span style="color:#98c379">&#34;Cargo.toml&#34;</span>) <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">vim.opt_local</span>.<span style="color:#e06c75">keywordprg</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;cargo info --color=always --offline&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">-- Add chars that are often part of keys, especially in rust crates</span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">-- (https://toml.io/en/v1.0.0#keys)</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">vim.opt_local</span>.<span style="color:#e06c75">iskeyword</span>:<span style="color:#e06c75">append</span> <span style="color:#98c379">&#34;-&#34;</span>
</span></span></code></pre></div><p>The Lua code above also changes the <a href="https://neovim.io/doc/user/options.html#'iskeyword'"><code>iskeyword</code></a> preference.
This is so that <code>tokio-test</code> is considered as one word (a “keyword” in Vim parlance), instead of two (<code>tokio</code> and <code>test</code>).
With this set, doing <code>K</code> on <code>tokio-test</code> will behave properly, because <code>tokio-test</code> is passed to the command, instead of just <code>tokio</code>.</p>
<p>One final note: the above configuration was the only thing sourced in the demo.
All the rest is default, vanilla, neovim 0.10.2.
And this does not even require recent neovim features, it has been supported in Vim for many years.</p>
<h2 id="learnings">Learnings</h2>
<p>Obviously this could be further refined.
Other options could be passed to <code>cargo info</code>, like <code>-v</code> to make it more verbose and list dependencies.
Some more scripting could add useful features to further improve the integration.
For this particular problem, there are even <a href="https://github.com/saecki/crates.nvim">a full plugin</a> with many more features.
But the point is to showcase a simple and robust integration<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, applicable in a wide variety of contexts.
In particular when the use case is too niche, a full plugin is too costly to write and maintain for the benefits it provides.</p>
<p>Let’s highlight the takeaways from this post that are applicable in a wide variety of contexts.</p>
<p>First, a number of Neovim commands can do something with the keyword under the cursor.
Adjusting what counts as a keyword boundary is often, but not always, done in the syntax file of a particular language.
Or you may just want to make a different trade off and set your own <code>iskeyword</code> value.</p>
<p>Second, the <a href="https://neovim.io/doc/user/various.html#K"><code>K</code></a> mapping is often used to look up a word in a documentation (it is set to open man pages by default).
It even works in visual mode, looking up the selected text.
And arbitrary Vim or shell commands can be used, instead of the default <code>:Man</code>.</p>
<p>Please do read the corresponding parts of the Vim manual linked in this section to learn more.
Next time, you may come up with your own quick integration between Vim and another tool.
Happy hacking!</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2024-10-22: Clarify why we use nvim settings instead of a plugin like <a href="https://github.com/saecki/crates.nvim">crates.nvim</a></p>
  </div>



<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>To keep the example simple, we don’t clean-up the local preferences that are set here. See the <a href="https://neovim.io/doc/user/usr_41.html#ftplugin">documentation</a> for best practices when sharing ftplugins.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>It’s also possible to do this in Vimscript instead of Lua.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Thanks to <a href="https://fosstodon.org/@dpom">@dpom@fosstodon.org</a> <a href="https://fosstodon.org/@dpom/113347670141692005">for pointing out</a> that crates.nvim has that feature already.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>From UltiSnips to LuaSnip</title><link>https://joly.pw/blog/ultisnips-to-luasnip/</link><pubDate>Sun, 15 May 2022 07:06:49 +0100</pubDate><guid>https://joly.pw/blog/ultisnips-to-luasnip/</guid><description>Shaving off a few milliseconds from Neovim startup time.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> is fast and doesn’t have to be complicated. Give it a try!</p>
  </div>







  
  
  
    
    
  
  

  <details class="alert alert-note">
    <summary class="alert-heading">
      ℹ️
      
        About UltiSnips
      
    </summary>
    <p>Even if that article shows how <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> shines, I have great respect for the work that has gone into <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. It is still a reliable, reasonably fast plugin given the constraint it operates in (in particular, Vim compatibility requires a fair amount of Vimscript). I’ve written this article shortly after trying LuaSnip and I’m still very much evaluating it.</p>
  </details>



<h2 id="introduction">Introduction</h2>
<p><a href="https://en.wikipedia.org/wiki/Snippet_(programming)">Snippets</a> are a convenient feature of some text editors to insert and adapt reusable pieces of code. For instance, snippets for <code>for</code> loops are common, to get the tedious bits of the syntax out of the way.</p>
<p>To get this feature in Vim back in the days, I started using <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. There are <a href="https://github.com/honza/vim-snippets">default snippets sets</a> for it, and it’s easy to write custom snippets. These custom snippet can call Bash or Python scripts if you need more dynamic replacements. UltiSnips has been very powerful and has served me quite well over the past decade or so, and I have kept it when I migrated to NeoVim a few years ago.</p>
<h2 id="startup-time">Startup time</h2>
<p>Every once in a while though, I run the excellent <a href="https://github.com/rhysd/vim-startuptime">vim-startuptime</a> command to assess the impact of various configurations and plugins on the startup time of Neovim. With UltiSnips and the corresponding completion plugins in my configuration, the first few lines are:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 104.218300 msec
</span></span><span style="display:flex;"><span>Total Max:     109.719000 msec
</span></span><span style="display:flex;"><span>Total Min:     99.533000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>64.218600 70.419000 61.066000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 6.255900  7.238000  5.764000: loading packages
</span></span><span style="display:flex;"><span> 3.939000  5.338000  3.533000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 3.651100  4.003000  3.381000: loading rtp plugins
</span></span><span style="display:flex;"><span> 2.761600  3.957000  2.474000: expanding arguments
</span></span><span style="display:flex;"><span> 2.683900  3.035000  2.566000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.418700  3.610000  2.131000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 2.389600  2.751000  2.228000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 1.954900  2.293000  1.702000: loading after plugins
</span></span><span style="display:flex;"><span> 1.842500  2.015000  1.763000: BufEnter autocommands
</span></span><span style="display:flex; background-color:#3d4148"><span> 1.583350  3.532000  0.018000: ~/.local/share/nvim/site/pack/paqs/start/ultisnips/plugin/UltiSnips.vim
</span></span><span style="display:flex;"><span> 1.027700  1.170000  0.831000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex; background-color:#3d4148"><span> 0.968200  1.071000  0.860000: ~/.local/share/nvim/site/pack/paqs/start/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua
</span></span><span style="display:flex;"><span> 0.859000  1.014000  0.702000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.557200  0.817000  0.493000: init highlight
</span></span><span style="display:flex;"><span> 0.553200  0.898000  0.322000: opening buffers</span></span></code></pre></div>
<p>The <code>init.lua</code> line covers a lot of different plugins and mappings set up in that file. Besides the <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> colorscheme<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, the next biggest contributor is <code>~/…/ultisnips/plugin/UltiSnips.vim</code>. The script to integrate UltiSnips with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a>, <code>~/…/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua</code>, is not far behind. In total, we spend nearly 4 ms of the startup time for UltiSnips-related files, on top of the setup done in <code>init.lua</code>. That feels suboptimal, so I’ve been looking for a possible snippet plugin alternative.</p>
<h2 id="installing-luasnip">Installing LuaSnip</h2>
<p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> aims to be a faster<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> snippet engine, with support of treesitter in the snippets. It can also understand the LSP snippet “format”. Finally, it’s a pure Lua plugin, without any Python requirement, contrary to UltiSnips. That makes installation on various system much easier.</p>
<p>Its drawbacks for me are that it does not support the same snippet definition as UltiSnips and even in its own SnipMate-like syntax, backticks to execute code are not supported. As a result, I’ll have to migrate my (admittedly very small) snippet collection. Conversely, if I want to do more advanced things, I’ll have to learn the relatively complex “VS Studio Code” snippets in JSON or even the pure Lua snippets.</p>
<p>That’s said, I believe I can get the LuaSnip benefits without writing Lua snippets or JSON ones, at least to start. So let’s try and do that, using only the SnipMate-like syntax!</p>
<h2 id="overview-of-the-configuration-changes">Overview of the Configuration Changes</h2>
<p>I’ve made the following changes.</p>
<ol>
<li>Remove UltiSnips and its associated plugins, namely for completion with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a> and the telescope integration.</li>
<li>Remove the UltiSnips mappings.</li>
<li>Add LuaSnip (following the instructions in the <a href="https://github.com/L3MON4D3/LuaSnip">Readme</a>), <a href="https://github.com/saadparwaiz1/cmp_luasnip">its cmp integration</a>, a <a href="https://github.com/rafamadriz/friendly-snippets">set of snippets</a> and a <a href="https://github.com/benfowler/telescope-luasnip.nvim">telescope integration</a> along with some mappings (requires nvim 0.7+<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>):
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">set</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;C-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip forward jump&#34;</span> })
</span></span><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">se</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;M-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#56b6c2">-</span><span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip backward jump&#34;</span> })
</span></span></code></pre></div></li>
<li><a href="#migrate-my-snippet-collection">Migrate my existing snippets</a>, see below.</li>
<li>Add code to load the snippet set and my own snippet collection:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_vscode&#34;</span>).<span style="color:#e06c75">lazy_load</span>()
</span></span><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_snipmate&#34;</span>).<span style="color:#e06c75">lazy_load</span>({ <span style="color:#e06c75">paths</span> <span style="color:#56b6c2">=</span> {<span style="color:#98c379">&#34;./snippets&#34;</span>} })
</span></span></code></pre></div></li>
</ol>
<h3 id="after">After</h3>
<p>Let’s run vim-startuptime again. Here are the new top contributors:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 98.251400 msec
</span></span><span style="display:flex;"><span>Total Max:     100.159000 msec
</span></span><span style="display:flex;"><span>Total Min:     96.519000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>60.469100 62.341000 59.619000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 5.991100  6.219000  5.865000: loading packages
</span></span><span style="display:flex;"><span> 4.088400  4.162000  3.942000: loading after plugins
</span></span><span style="display:flex;"><span> 3.635700  3.752000  3.415000: loading rtp plugins
</span></span><span style="display:flex;"><span> 3.561800  3.936000  3.220000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 2.550500  2.628000  2.492000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.454200  2.525000  2.401000: expanding arguments
</span></span><span style="display:flex;"><span> 2.363000  2.420000  2.270000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 2.271100  2.337000  2.146000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 1.701000  1.767000  1.635000: BufEnter autocommands
</span></span><span style="display:flex;"><span> 1.676700  2.049000  1.366000: opening buffers
</span></span><span style="display:flex;"><span> 1.034100  1.280000  0.782000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex;"><span> 0.901000  1.021000  0.799000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.549500  0.789000  0.514000: init highlight</span></span></code></pre></div></p>
<p>The snippet infrastructure is now absent from that list! More importantly, the overall startup time is down, and we can be confident that the new calls to load the snippets in <code>init.lua</code> are not costlier than UltiSnips settings before, because the <code>init.lua</code> line is down as well.</p>
<p>As a bonus, the snippet expansion feels slightly snappier with LuaSnip, although it might be an illusion and I don’t have hard numbers to back this claim.</p>
<h2 id="migrate-my-snippet-collection">Migrate my Snippet Collection</h2>
<p>Back to the topic of migrating existing UltiSnips snippets: LuaSnip will loudly complain when given UltiSnips snippets but it’s relatively easy to rewrite those snippets to the SnipMate-like format that LuaSnip understands.</p>
<h3 id="two-syntaxes">Two Syntaxes</h3>
<p>On the one hand, UltiSnips snippets roughly follow this syntax</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet trigger &#34;Comment&#34; option
snippet content
endsnippet
</code></pre><p>And so, my markdown snippets would look like this:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">priority 10

snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>On the other hand, LuaSnip uses a simplified version of SnipMate snippets:</p>
<pre tabindex="0"><code># Comment
snippet toggle
  snippet content
</code></pre><h3 id="simple-snippets">Simple Snippets</h3>
<p>Since I heavily rely on snippet sets, I have only about 30 snippets defined in my own snippet collection. Most of them are really simple, with only a few lines and almost no interactive text. So for most of those, the process has been to simply remove the <code>priority …</code> lines and then a simple substitution command<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> did the trick.</p>
<h3 id="advanced-snippets-with-environment-variables">Advanced Snippets With Environment Variables</h3>
<p>However, there is also the case of the snippets calling external commands, like <code>date</code> in the example below:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>The problem is, to call arbitrary commands, one needs to define the snippets in Lua.</p>
<p>It turns out though that nearly all my snippets calling external command were actually inserting a date. And luckily LuaSnip defines “environment variables” holding just the values I need like <code>$CURRENT_MONTH</code>. So that snippet becomes:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet"># “Bullet Journal”-styled date for today
snippet bjtoday
  # ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} $CURRENT_DAY_NAME_SHORT
</code></pre><p>and we get to keep the simple SnipMate-like syntax!</p>
<p>You can find more of those environment variables in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/util/environ.lua">the sources</a>.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-08-03: See also <a href="https://github.com/smjonas/snippet-converter.nvim">snippet-converter.nvim</a> to convert between various snippet formats.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
  </div>



<h2 id="whats-next">What’s Next</h2>
<p>I now have a slightly faster NeoVim and the snippet syntax that I use the most is simpler. However, there is more to explore!</p>
<p>So far, I’ve steered clear of writing more complicated JSON and Lua snippets. The latter would be necessary to unlock smart snippets using treesitter <a href="https://changelog.com/podcast/457#t=01:00:01.17">context</a>. I’ll look into that next, in particular to generate go error handling code.</p>
<p>This is also a very simple configuration, the impact on startup time of LuaSnip might go up slightly as we use more advanced feature, but during my tests, even using all available features, it was much lighter than UltiSnips.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-07-31: <a href="https://joly.pw/tags/luasnip/">Follow-up posts</a> dig deeper in other aspects of LuaSnip</p>
  </div>



<h2 id="resources">Resources</h2>
<ul>
<li>Many more details are covered in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md">LuaSnip documentation</a>.</li>
<li>The <a href="https://github.com/garbas/vim-snipmate/blob/master/doc/SnipMate.txt">SnipMate help</a> contains the SnipMate snippet syntax, if you are unfamiliar with it.</li>
<li>The <a href="https://github.com/microsoft/language-server-protocol/blob/main/snippetSyntax.md">LSP snippet documentation</a> is also helpful.</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That run is actually on a fork of <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> that contains a massive speed up. The changes of that fork are being up streamed in <a href="https://github.com/navarasu/onedark.nvim/pull/76">this PR</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>According to that comment from the author for instance: <a href="https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664">https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>This code snippet uses new APIs introduced in Neovim 0.7 and the newly freed <code>&lt;C-i&gt;</code>, see <a href="https://gpanders.com/blog/whats-new-in-neovim-0-7/">https://gpanders.com/blog/whats-new-in-neovim-0-7/</a> for more details.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>For instance: <code>%s/^\(snippet \+\S\+\) &quot;\(.*\)&quot; \w\+\n\(\_.\{-}\)endsnippet/# \2^M\1^M \3^M</code>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Thanks to <a href="https://www.reddit.com/user/Miserable-Ad-7341">Miserable-Ad-7341</a> for <a href="https://www.reddit.com/r/neovim/comments/weonip/from_ultisnips_to_luasnip/iipheov/">pointing this out</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Local NeoVim Plugin Development</title><link>https://joly.pw/blog/tips/nvim-plugin-development/</link><pubDate>Tue, 02 Nov 2021 14:28:13 +0000</pubDate><guid>https://joly.pw/blog/tips/nvim-plugin-development/</guid><description>&lt;div class="alert alert-note"&gt;
&lt;p class="alert-heading"&gt;
ℹ️
Note
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2023-05-20&lt;/strong&gt;: Updated to account for the features of NeoVim 0.9 and obsolete plugins&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You have found a (Neo)Vim plugin that you want to fiddle with, either to contribute changes upstream or for your own use. Sounds familiar? Here are some tips and tricks I use for my &lt;a href="https://joly.pw/telescope-repo-nvim/"&gt;NeoVim plugin development&lt;/a&gt;. The aim of these small tricks is to iterate faster on changes, by loading your changes in a live NeoVim instance as quickly as possible.&lt;/p&gt;</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p><strong>2023-05-20</strong>: Updated to account for the features of NeoVim 0.9 and obsolete plugins</p>
  </div>



<p>You have found a (Neo)Vim plugin that you want to fiddle with, either to contribute changes upstream or for your own use. Sounds familiar? Here are some tips and tricks I use for my <a href="https://joly.pw/telescope-repo-nvim/">NeoVim plugin development</a>. The aim of these small tricks is to iterate faster on changes, by loading your changes in a live NeoVim instance as quickly as possible.</p>
<p>We will use <a href="https://joly.pw/telescope-repo-nvim/">telescope-repo.nvim</a> as an example but it is applicable to any plugin (although some sections of this post only apply to Lua NeoVim plugins).</p>
<h2 id="load-local-plugin-version">Load Local Plugin Version</h2>
<p>When developing, you make your changes to a local git repository, for instance <code>~/ghq/github.com/cljoly/telescope-repo.nvim</code>. To test those changes, you need to tell NeoVim to load the plugin from the local repository, instead of using the location set by your plugin manager. To do this, just append the following near the beginning of your <code>init.lua</code> file:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">vim.opt</span>.<span style="color:#e06c75">runtimepath</span>:<span style="color:#e06c75">prepend</span>(<span style="color:#98c379">&#34;~/ghq/github.com/cljoly/telescope-repo.nvim&#34;</span>)
</span></span></code></pre></div><p>Note the <code>:prepend</code>. It will ensure that your local dev version will override anything installed by your nvim package manager. This way no need to uninstall your plugin when developing!</p>
<h2 id="reloading-changes-in-a-live-neovim-instance">Reloading Changes in a Live NeoVim Instance</h2>
<p>You have now made some changes to the plugin that you would like to test. In doing so, you start a second NeoVim instance and open a set of files, change a bunch of settings…</p>
<p>You then change something in the code, but you don’t want to restart you test NeoVim instance, as that would mean reopening files and altering settings all over again. But just doing <code>require(…)</code> is not enough, because <code>require</code> caches already loaded files and doesn’t reload them if they are loaded already.</p>
<p>If you use <a href="https://github.com/nvim-telescope/telescope.nvim">telescope</a>, you can use the <code>reloader</code> picker. You can then select the module you want to reload, like so:</p>
<figure>
    <img loading="lazy" src="./telescope-reloader.png"
         alt="You type “telescop repo” and the corresponding Lua module surfaces. It will be reloaded when you hit Enter."/> <figcaption>
            Telescope reloader in action<p>You type “telescop repo” and the corresponding Lua module surfaces. It will be reloaded when you hit Enter.</p>
        </figcaption>
</figure>

<p>Under the hood, <code>telescope reload</code> <a href="https://github.com/nvim-telescope/telescope.nvim/blob/587a10d1494d8ffa1229246228f0655db2f0a48a/lua/telescope/builtin/internal.lua#L712">uses plenary</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;plenary.reload&#34;</span>).<span style="color:#e06c75">reload_module</span>(<span style="color:#e06c75">selection.value</span>)
</span></span></code></pre></div><p>which handle various cases (like whether <a href="https://github.com/lewis6991/impatient.nvim">impatient.nvim</a> is used at the time of writing). Despite this careful handling, sometimes, a plugin may not fully reload. In that case, you want to automate as much of the setup as possible on NeoVim restart.</p>
<h2 id="if-all-else-fails">If All Else Fails</h2>
<p>Sometimes, a “soft” reloading is not enough and you need to restart NeoVim. For instance, if we are testing the <code>:Telescope repo list</code> command and need to open <code>/tmp/file</code>, we can do:</p>
<pre tabindex="0"><code>nvim +&#39;Telescope repo list&#39; /tmp/file
</code></pre><p>and even place this in an infinite loop with a <code>sleep</code> command, to escape the loop more easily once we are done.</p>
<p>Of course, you will want to replace <code>Telescope repo list</code> with the command you want to test. You can also add more setup by supplying multiple “<code>+'…'</code>” arguments)</p>
<h2 id="all-set">All Set</h2>
<p>Now that you are all set, you can go on and write complete plugins! You may find the following resources useful in your journey:</p>
<ul>
<li>the <a href="https://neovim.io/doc/user/lua-guide.html"><code>:help lua-guide.txt</code></a> has a lot of resources to write plugins and alter NeoVim’s configuration,
<ul>
<li><a href="https://www.2n.pl/blog/how-to-write-neovim-plugins-in-lua">2n.pl</a> walks through writing a simple plugin,</li>
</ul>
</li>
<li><a href="https://web.archive.org/web/20211207190156/https://www.chrisatmachine.com/Neovim/28-neovim-lua-development/">set up a Language Server Protocol for Lua</a>,
<ul>
<li>and maybe even a full-blown <a href="https://github.com/folke/neodev.nvim">plugin</a> for Lua plugin development,</li>
</ul>
</li>
<li>to test a piece of Lua code real quick, <code>:=my.lua.code(here)</code> is great. For bigger pieces of code, <a href="https://github.com/rafcamlet/nvim-luapad">luapad</a> can be useful: it provides a scratch buffer where the result of a Lua snippet is displayed as you type.</li>
</ul>
]]></content:encoded></item></channel></rss>