<?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>LuaSnip on Clément Joly – Open-Source, Rust &amp; SQLite</title><link>https://cj.rs/tags/luasnip/</link><description>Recent content in LuaSnip on Clément Joly – Open-Source, Rust &amp; SQLite</description><image><title>Clément Joly – Open-Source, Rust &amp; SQLite</title><url>https://cj.rs/images/open-graph-pages.jpg</url><link>https://cj.rs/images/open-graph-pages.jpg</link></image><generator>Hugo</generator><language>en</language><copyright>Clément Joly</copyright><atom:link href="https://cj.rs/tags/luasnip/index.xml" rel="self" type="application/rss+xml"/><item><title>Luasnip and Treesitter for Smarter Snippets</title><link>https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/</link><pubDate>Wed, 30 Aug 2023 13:28:14 +0100</pubDate><guid>https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/</guid><description>An example on Go error handling.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>A context-aware snippet for Go error handling code, returning the right types, with the default values.</p>
<ul>
<li><a href="#demo">Demo</a></li>
<li><a href="#code">Snippet</a></li>
</ul>
  </div>







  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>TJ DeVries made a <a href="https://www.youtube.com/watch?v=aNWx-ym7jjI">video</a> explaining an improved version of this snippet. You may still find the discussion on Go errors in this post interesting.</p>
  </div>



<h2 id="introduction">Introduction</h2>
<p><a href="https://go.dev">Golang’s</a> error handling is notoriously verbose. It was also the top pain point in the <a href="https://go.dev/blog/survey2022-q2-results">Go Developer Survey Q2 2022</a>. <a href="https://github.com/golang/go/issues/56165">Numerous</a> <a href="https://github.com/golang/go/issues/32437">proposals</a> <a href="https://github.com/golang/go/issues/37141">to simplify</a> error handling have been written, but at the time of writing, none have been accepted.</p>
<p>So we have to live with the current state of things. It’s ok, Neovim with Treesitter has a good “understanding” of code, so we can use it to generate the error handling code.</p>
<h3 id="treesitter">Treesitter</h3>
<p>One of <a href="https://github.com/neovim/neovim/releases/tag/v0.5.0">Neovim 0.5</a> most exciting features was the introduction of Treesitter.
This new parsing system gives the editor a basic understanding of the code at hand: the editor “knows” what the function name is, where a variable is defined…
It enables great plugins, <a href="https://github.com/SmiteshP/nvim-gps">showing the context</a> around some code, <a href="https://github.com/nvim-treesitter/nvim-treesitter-textobjects">improved selection</a>, <a href="https://github.com/nvim-treesitter/nvim-treesitter-refactor">better renaming and navigation</a> and more!
In this post, we will look into how Treesitter can be combined with <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> for smarter snippets.</p>
<h2 id="error-handling-in-go">Error Handling in Go</h2>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p>Feel free to <a href="#clever">skip</a> this section if you already know about Go error handling.</p>
  </div>



<p>In <a href="https://go.dev">Golang</a>, errors are handled by returning a value of type <code>error</code>, sometimes along the types returned when everything goes well. For instance, the <a href="https://pkg.go.dev/time#Parse"><code>time.Parse</code></a> function has the following signature:</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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#e06c75">layout</span>, <span style="color:#e06c75">value</span> <span style="color:#e5c07b">string</span>) (<span style="color:#e06c75">Time</span>, <span style="color:#e5c07b">error</span>)
</span></span></code></pre></div><p><code>time.Parse</code> will try to parse a string with the given format.
If it’s successful, it’ll return a value of type <code>Time</code> and the error will be <code>nil</code>.
If a date can’t be parsed, then the error won’t be <code>nil</code>.
And so it’s common in Go to check for errors 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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">timeStr</span> <span style="color:#56b6c2">:=</span> <span style="color:#98c379">&#34;Aug 6, 2022 @ 7:54pm&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">t</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#e06c75">time</span>.<span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#98c379">&#34;Jan 2, 2006 @ 3:04pm&#34;</span>, <span style="color:#e06c75">timeStr</span>)
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">log</span>.<span style="color:#61afef;font-weight:bold">Fatal</span>(<span style="color:#e06c75">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#7f848e">// Now, we can safely use t</span>
</span></span></code></pre></div><p>In the example above, we just log the error and panic. But if we are in a function, we will likely return an error to the caller, wrapping the error returned by <code>time.Parse</code>. For instance:</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-go" data-lang="go"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">1</span><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">parseLong</span>(<span style="color:#e06c75">timeStr</span> <span style="color:#e5c07b">string</span>) (<span style="color:#e06c75">Time</span>, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">2</span><span>    <span style="color:#e06c75">t</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#e06c75">time</span>.<span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#98c379">&#34;Jan 2, 2006 @ 3:04pm&#34;</span>, <span style="color:#e06c75">timeStr</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">3</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">4</span><span>        <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;couldn’t parse %v: %v&#34;</span>, <span style="color:#e06c75">timeStr</span>, <span style="color:#e06c75">err</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">5</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">6</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>, <span style="color:#e5c07b">nil</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">7</span><span>}
</span></span></code></pre></div><p>The key takeaway here is that the <code>return</code> line in the error case (line 4 above) depends on the return type of the function and so can vary significantly.</p>
<p>This is covered in more details in this <a href="https://go.dev/blog/error-handling-and-go">post</a> on the Go blog.</p>
<h2 id="clever">Clever Snippets with LuaSnip and Treesitter</h2>
<p>As we have seen, error handling code varies depending on the number and type of the values returned by the parent function.
So we are very often writing variations of the following in our Go 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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">f</span>(<span style="color:#e06c75">arg1</span> <span style="color:#e5c07b">string</span>, <span style="color:#e06c75">arg2</span> <span style="color:#e5c07b">string</span>) (…, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">someFunction</span>(<span style="color:#e06c75">arg1</span>, <span style="color:#e06c75">arg2</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#c678dd">return</span> …, <span style="color:#e06c75">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    …
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Or with 3 return values:</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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">f</span>(<span style="color:#e06c75">arg</span> <span style="color:#e5c07b">string</span>) (…, …, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">someFunction</span>(<span style="color:#e06c75">arg</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#c678dd">return</span> …, …, <span style="color:#e06c75">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    …
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It would be handy to have a snippet that would adjust the <code>return</code>’s shape depending on the return type of the function that contains it.</p>
<h3 id="demo">Demo</h3>
<p>Here is a short asciicast demonstration<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> of the snippet we are going to build. It is called <code>smart_err</code>:</p>

    <script src="https://cj.rs/js/asciinema-player.min.0a355ec0e0db21622c1aa829f4ffd49e.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cj.rs/css/asciinema-player.min.b76f6c28744857ba1315b54d09acafff.css" /><div id="demo1"></div>
<script>
AsciinemaPlayer.create("/blog/luasnip-and-treesitter-for-smarter-snippets/demo.json", document.getElementById('demo1'), {
"idleTimeLimit":  0.4 ,"loop":  true ,"poster": "npt:0:31","speed":  1.2 ,
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/demo.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<p>The snippet inserted (line 11 in the asciicast) is</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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">myVal</span>, <span style="color:#e06c75">myErr</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">anotherFunc</span>(<span style="color:#e06c75">arg1</span>, <span style="color:#e06c75">arg2</span>)
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">myErr</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;anotherFunc: %v&#34;</span>, <span style="color:#e06c75">myErr</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>when the return type of the function was <code>(bool, error)</code>.</p>
<p>And</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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">f</span>()
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;f: %v&#34;</span>, <span style="color:#e06c75">err</span>), <span style="color:#d19a66">0</span>, <span style="color:#98c379">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>when the return type was <code>(bool, error, int, string)</code>.</p>
<p>And it even works in nested functions:</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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">myFunc</span>() (<span style="color:#56b6c2">*</span><span style="color:#e06c75">MyStruct</span>, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#7f848e">// ...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e06c75">a</span> <span style="color:#56b6c2">:=</span> <span style="color:#c678dd">func</span>() (<span style="color:#e5c07b">bool</span>, <span style="color:#e5c07b">error</span>, <span style="color:#e5c07b">int</span>, <span style="color:#e5c07b">string</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">// The snippet detected that we need 4 values here,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">// and not 2 as in the outer function</span>
</span></span><span style="display:flex;"><span>		<span style="color:#c678dd">return</span> <span style="color:#e06c75">v</span>, <span style="color:#e5c07b">nil</span>, <span style="color:#d19a66">0</span>, <span style="color:#98c379">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>Also note that we can choose to wrap the containing error, or not, with a keystroke (bound to <code>require(&quot;luasnip&quot;).change_choice(1)</code>). The snippet switches between the following 3 forms (line 18 in the asciicast):</p>
<ol>
<li>
<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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">err</span>
</span></span></code></pre></div></li>
<li>
<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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;f: %v&#34;</span>, <span style="color:#e06c75">err</span>)
</span></span></code></pre></div></li>
<li>
<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-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">errors</span>.<span style="color:#61afef;font-weight:bold">Wrap</span>(<span style="color:#e06c75">err</span>, <span style="color:#98c379">&#34;f&#34;</span>)
</span></span></code></pre></div></li>
</ol>
<p>That’s pretty cool! Let’s see how to add it to your Neovim configuration.</p>
<h3 id="code">Code</h3>
<p>The code to define this snippet was initially written by <a href="https://github.com/tjdevries">TJ DeVries</a><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. I’ve only slightly adapted it so that it fits in one file. I believe the author welcomes this<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>:</p>




  <figure>
    <blockquote cite="https://github.com/tjdevries/config_manager#readme">
      <p>This is TJ&rsquo;s configuration repo. Feel free to use whatever you would like from it! It&rsquo;d be great if you mentioned where it came from if you think it&rsquo;s cool.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://github.com/tjdevries/config_manager#readme">https://github.com/tjdevries/config_manager#readme</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Copy this alongside your other LuaSnip snippets, for instance in <a href="./go.lua"><code>~/.config/nvim/snippets/go.lua</code></a>:</p>
<div class="highlight" id="luasnip"><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-lua" data-lang="lua"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  1</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ls</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;luasnip&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  2</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">f</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.function_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  3</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">s</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.s</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  4</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">i</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.insert_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  5</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">t</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.text_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  6</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">d</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.dynamic_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  7</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">c</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.choice_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  8</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">snippet_from_nodes</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.sn</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 10</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ts_locals</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;nvim-treesitter.locals&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 11</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ts_utils</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;nvim-treesitter.ts_utils&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 12</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">get_node_text</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">get_node_text</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 14</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 15</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">same</span>(<span style="color:#e06c75">index</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 16</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 17</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">1</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 18</span><span>  <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">index</span> })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 19</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 20</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 21</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 22</span><span><span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">query.set</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 23</span><span>  <span style="color:#98c379">&#34;go&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 24</span><span>  <span style="color:#98c379">&#34;LuaSnip_Result&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 25</span><span>  <span style="color:#98c379">[[ [
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 26</span><span><span style="color:#98c379">    (method_declaration result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 27</span><span><span style="color:#98c379">    (function_declaration result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 28</span><span><span style="color:#98c379">    (func_literal result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 29</span><span><span style="color:#98c379">  ] ]]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 30</span><span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 31</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 32</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 33</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">transform</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">text</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 34</span><span>  <span style="color:#c678dd">if</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;int&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 35</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;0&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 36</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;error&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 37</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">info</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 38</span><span>      <span style="color:#e06c75">info.index</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">info.index</span> <span style="color:#56b6c2">+</span> <span style="color:#d19a66">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 39</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 40</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">c</span>(<span style="color:#e06c75">info.index</span>, {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 41</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;fmt.Errorf(&#34;%s: %%v&#34;, %s)&#39;</span>, <span style="color:#e06c75">info.func_name</span>, <span style="color:#e06c75">info.err_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 42</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">info.err_name</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 43</span><span>        <span style="color:#7f848e">-- Be cautious with wrapping, it makes the error part of the API of the</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 44</span><span>        <span style="color:#7f848e">-- function, see https://go.dev/blog/go1.13-errors#whether-to-wrap</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 45</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;fmt.Errorf(&#34;%s: %%w&#34;, %s)&#39;</span>, <span style="color:#e06c75">info.func_name</span>, <span style="color:#e06c75">info.err_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 46</span><span>        <span style="color:#7f848e">-- Old style (pre 1.13, see https://go.dev/blog/go1.13-errors), using</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 47</span><span>        <span style="color:#7f848e">-- https://github.com/pkg/errors</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 48</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;errors.Wrap(%s, &#34;%s&#34;)&#39;</span>, <span style="color:#e06c75">info.err_name</span>, <span style="color:#e06c75">info.func_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 49</span><span>      })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 50</span><span>    <span style="color:#c678dd">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 51</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;err&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 52</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 53</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;bool&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 54</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 55</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;string&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 56</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#39;&#34;&#34;&#39;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 57</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">string.find</span>(<span style="color:#e06c75">text</span>, <span style="color:#98c379">&#34;*&#34;</span>, <span style="color:#d19a66">1</span>, <span style="color:#e5c07b">true</span>) <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 58</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;nil&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 59</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 60</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 61</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>(<span style="color:#e06c75">text</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 62</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 63</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 64</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">handlers</span> <span style="color:#56b6c2">=</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 65</span><span>  [<span style="color:#98c379">&#34;parameter_list&#34;</span>] <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 66</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">result</span> <span style="color:#56b6c2">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 67</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 68</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">count</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">node</span>:<span style="color:#e06c75">named_child_count</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 69</span><span>    <span style="color:#c678dd">for</span> <span style="color:#e06c75">idx</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>, <span style="color:#e06c75">count</span> <span style="color:#56b6c2">-</span> <span style="color:#d19a66">1</span> <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 70</span><span>      <span style="color:#e06c75">table.insert</span>(<span style="color:#e06c75">result</span>, <span style="color:#e06c75">transform</span>(<span style="color:#e06c75">get_node_text</span>(<span style="color:#e06c75">node</span>:<span style="color:#e06c75">named_child</span>(<span style="color:#e06c75">idx</span>), <span style="color:#d19a66">0</span>), <span style="color:#e06c75">info</span>))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 71</span><span>      <span style="color:#c678dd">if</span> <span style="color:#e06c75">idx</span> <span style="color:#56b6c2">~=</span> <span style="color:#e06c75">count</span> <span style="color:#56b6c2">-</span> <span style="color:#d19a66">1</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 72</span><span>        <span style="color:#e06c75">table.insert</span>(<span style="color:#e06c75">result</span>, <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;, &#34;</span> })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 73</span><span>      <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 74</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 75</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 76</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">result</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 77</span><span>  <span style="color:#c678dd">end</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 78</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 79</span><span>  [<span style="color:#98c379">&#34;type_identifier&#34;</span>] <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 80</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">get_node_text</span>(<span style="color:#e06c75">node</span>, <span style="color:#d19a66">0</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 81</span><span>    <span style="color:#c678dd">return</span> { <span style="color:#e06c75">transform</span>(<span style="color:#e06c75">text</span>, <span style="color:#e06c75">info</span>) }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 82</span><span>  <span style="color:#c678dd">end</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 83</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 84</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 85</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 86</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">go_result_type</span>(<span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 87</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">cursor_node</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ts_utils.get_node_at_cursor</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 88</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">scope</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ts_locals.get_scope_tree</span>(<span style="color:#e06c75">cursor_node</span>, <span style="color:#d19a66">0</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 89</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 90</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">function_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 91</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">v</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">ipairs</span>(<span style="color:#e06c75">scope</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 92</span><span>    <span style="color:#c678dd">if</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 93</span><span>      <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;function_declaration&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 94</span><span>      <span style="color:#56b6c2">or</span> <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;method_declaration&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 95</span><span>      <span style="color:#56b6c2">or</span> <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;func_literal&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 96</span><span>    <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 97</span><span>      <span style="color:#e06c75">function_node</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">v</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 98</span><span>      <span style="color:#c678dd">break</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 99</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">100</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">101</span><span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">102</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">query</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">query.get</span>(<span style="color:#98c379">&#34;go&#34;</span>, <span style="color:#98c379">&#34;LuaSnip_Result&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">103</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">node</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">query</span>:<span style="color:#e06c75">iter_captures</span>(<span style="color:#e06c75">function_node</span>, <span style="color:#d19a66">0</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">104</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">handlers</span>[<span style="color:#e06c75">node</span>:<span style="color:#e06c75">type</span>()] <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">105</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">handlers</span>[<span style="color:#e06c75">node</span>:<span style="color:#e06c75">type</span>()](<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">106</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">107</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">108</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">109</span><span>  <span style="color:#c678dd">return</span> { <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;nil&#34;</span> }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">110</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">111</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">112</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">113</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">go_ret_vals</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">114</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">snippet_from_nodes</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">115</span><span>    <span style="color:#e5c07b">nil</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">116</span><span>    <span style="color:#e06c75">go_result_type</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">117</span><span>      <span style="color:#e06c75">index</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">118</span><span>      <span style="color:#e06c75">err_name</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">1</span>][<span style="color:#d19a66">1</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">119</span><span>      <span style="color:#e06c75">func_name</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">2</span>][<span style="color:#d19a66">1</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">120</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">121</span><span>  )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">122</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">123</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">124</span><span><span style="color:#c678dd">return</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">125</span><span>  <span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">126</span><span>  <span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;smart_err&#34;</span>, {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">127</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">1</span>, { <span style="color:#98c379">&#34;val&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">128</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;, &#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">129</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">2</span>, { <span style="color:#98c379">&#34;err&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">130</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34; := &#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">131</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">3</span>, { <span style="color:#98c379">&#34;f&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">132</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;(&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">133</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">4</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">134</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;)&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">135</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#98c379">&#34;if &#34;</span> },
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">136</span><span>    <span style="color:#e06c75">same</span>(<span style="color:#d19a66">2</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">137</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34; != nil {&#34;</span>, <span style="color:#98c379">&#34;</span><span style="color:#98c379">\t</span><span style="color:#98c379">return &#34;</span> },
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">138</span><span>    <span style="color:#e06c75">d</span>(<span style="color:#d19a66">5</span>, <span style="color:#e06c75">go_ret_vals</span>, { <span style="color:#d19a66">2</span>, <span style="color:#d19a66">3</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">139</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#98c379">&#34;}&#34;</span> },
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">140</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">0</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">141</span><span>  }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">142</span><span>}
</span></span></code></pre></div><p>We start by importing and aliasing some LuaSnip functions. The snippet itself is defined at the end (line 126). Most of those functions were covered in the <a href="https://cj.rs/blog/generating-snippets-with-luasnip/#3-generating-snippets-variants">previous post</a>, see there for calls to <code>s</code>, <code>i</code> and <code>t</code>. The clever part of this snippet is the <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#dynamicnode">dynamic node</a> <code>d</code> on line 138. There, we call a function that queries (line 22 and 102) Treesitter to find the return types of the current function in Go code. Depending on the types found, default values for the type are used (line 33).</p>
<h3 id="treesitter-query">Treesitter Query</h3>
<p>The Treesitter query is where the magic happens, so let’s take a deeper look.</p>
<p>Treesitter parses code and builds a tree with node for various parts of the code. Each node can have children, like a function definition has a name, a body… This tree can be queried in a Lisp-like language (s-expressions). For instance, in our snippet definition, we use this query:</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-scheme" data-lang="scheme"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">1</span><span>[
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">2</span><span>    (<span style="color:#61afef;font-weight:bold">method_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">3</span><span>    (<span style="color:#61afef;font-weight:bold">function_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">4</span><span>    (<span style="color:#61afef;font-weight:bold">func_literal</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">5</span><span>]
</span></span></code></pre></div><p>The <a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">bracket syntax</a> returns the nodes that match any of the patterns inside the brackets. So it’s effectively the union of all the nodes matched by any of the 3 patterns (on lines 2 to 4). Let’s take a concrete example and put the <a href="https://gist.github.com/cljoly/0b754259df036ba0163f2cdaad48e048#file-main-go">Go code</a> in the <a href="https://tree-sitter.github.io/tree-sitter/playground">Treesitter playground</a>, with a trimmed down version of the query:</p>
<p>
<img alt="The Treesitter playground with the example Go code above inserted at the top, the query in the middle and the tree at the bottom" loading="lazy" src="/blog/luasnip-and-treesitter-for-smarter-snippets/treesitter-playground.png"></p>
<p>The tree built by Treesitter is at the bottom, with the corresponding code at the top and the query in the middle. The <code>function_declaration</code> node is highlighted in gray in the tree, like the corresponding part of the code (from line 13 to line 22). This node has a number of children, with the function <code>name</code>, the <code>parameters</code> of the function, a <code>result</code> node for the return type and <code>body</code> for the body of the function between curly braces.</p>
<p>Taking the first pattern on the playground, slightly modified:</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-scheme" data-lang="scheme"><span style="display:flex;"><span>    (<span style="color:#61afef;font-weight:bold">function_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id2</span>)
</span></span></code></pre></div><p>The part it captures is highlighted in blue in the Go code: <code>(*MyStruct, error)</code>. It makes sense, since the <code>function_declaration</code> (the part on the gray background) has a <code>result</code> child, that’s captured by <code>@id</code>.</p>
<p>Now, on to the second pattern:</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-scheme" data-lang="scheme"><span style="display:flex;"><span>    (<span style="color:#61afef;font-weight:bold">func_literal</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id3</span>)
</span></span></code></pre></div><p>Similarly, further down in the <code>body</code> of that function, the return type <code>(bool, error, int, string)</code> of the anonymous function is matched.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I started writing this post over a year ago. The snippet has been very stable and kept working as without changes, which is a testament to the stability of the LuaSnip (and Treesitter) APIs. I’m still using it when I write Go code, even if some corner cases (like custom structs) aren’t handled. The snippet is quite complicated and niche, so I’m not sure that I would have invested the time to write it myself. I probably don’t write enough Go code to <a href="https://xkcd.com/1205/">justify the investment</a>, but finding it shared by someone buried in their config file made it worth it.
I hope you have found this port useful, either to copy this particular snippet or as an example of advanced snippets.</p>
<p>This is the last post in a <a href="https://cj.rs/tags/luasnip/">series on LuaSnip</a>.</p>
<h2 id="resources">Resources</h2>
<p>If you want to dig deeper:</p>
<ul>
<li><a href="https://go.dev/blog/go1.13-errors">Working with Errors in Go 1.13</a></li>
<li><a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md">LuaSnip documentation</a></li>
<li>Explore the Treesitter tree for any Neovim buffer where it is active with <code>:lua vim.treesitter.inspect_tree()</code></li>
<li>Treesitter’s <a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">Pattern Matching with Queries</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The test code is available as <a href="https://gist.github.com/cljoly/0b754259df036ba0163f2cdaad48e048#file-main-go">gist</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>By the way, he has a very interesting <a href="https://www.twitch.tv/teej_dv">Twitch channel</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>At the time of writing at least. See <a href="https://perma.cc/XZW9-B2UA">perma.cc</a> and <a href="https://web.archive.org/web/20221202063059/https://github.com/tjdevries/config_manager/blob/master/README.md">the wayback machine</a> for a snapshot.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Generating Snippets with LuaSnip in Neovim</title><link>https://cj.rs/blog/generating-snippets-with-luasnip/</link><pubDate>Sun, 31 Jul 2022 22:10:49 +0100</pubDate><guid>https://cj.rs/blog/generating-snippets-with-luasnip/</guid><description>Generating repetitive snippets with code, like time variant of section titles.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>When you have many variations of the same snippet, one option is to generate those with Lua code. The complete example is <a href="#the-final-code">at the end</a>.</p>
  </div>



<p>I’ve <a href="https://cj.rs/blog/ultisnips-to-luasnip/">recently moved</a> to <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> as my snippets plugin for Neovim.
When I first started, I sticked to the simplest features of LuaSnip, in particular the SnipMate-like syntax for snippets.
But I have now started to explore the more distinctive features of LuaSnip, like Lua-defined snippets.
It turns out that generating snippets with code can save tedious repetition.</p>
<h2 id="markdown-journaling">Markdown Journaling</h2>
<p>I tend to take notes in Markdown documents.
I usually set up one text file for each recurring themes or project.
Then, there is a “h1” title per day, a bit like the <a href="https://help.bulletjournal.com/article/27-daily-log">Bullet Journal daily log</a>.
The resulting file looks like this:</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; background-color:#3d4148"><span># 2022-07-31 Sun
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 1
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 2
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 3
</span></span></code></pre></div><p>I have a snippet to generate the <code># 2022-07-31 Sun</code> line, based on the current date and day of the week, as I explained <a href="https://cj.rs/blog/ultisnips-to-luasnip/#advanced-snippets-with-environment-variables">last time</a>.
For reference, here is the snippet from back then:</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><h2 id="lua-generated-snippets">Lua-Generated Snippets</h2>
<p>But then, I also sometimes want to put notes for the next session.
I usually know when that will be, because the meeting happens, say, every Thursday or every month.
So it would be great to have snippets for those dates as well.
Let’s build them step by step.</p>
<h3 id="1-computing-the-date-of-next-friday">1. Computing the Date of “Next Friday”</h3>
<p>The first step is to calculate the date and weekday from human concepts like “next Friday” or “next week”.
Thankfully <a href="https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html#date-invocation">GNU date</a> supports this, and it’s preinstalled on most GNU/Linux systems:</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-bash" data-lang="bash"><span style="display:flex;"><span>$ date -d <span style="color:#98c379">&#39;next Friday&#39;</span> +<span style="color:#98c379">&#39;%F %a&#39;</span>
</span></span><span style="display:flex;"><span>2022-08-05 Fri
</span></span></code></pre></div><h3 id="2-putting-those-dates-in-luasnip">2. Putting Those Dates in LuaSnip</h3>
<p>The second step is to use the output of the <code>date</code> command in LuaSnip.</p>
<p>We could <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#variables">define our own variables</a> like <code>${NEXT_MONDAY_DATE}</code>.
That would be similar to the <code>${CURRENT_DATE}</code> we used before.
But we would need many such variables, for the date and weekday of the next Monday, Tuesday, …, next week or next month.
Plus I use those dates only in Markdown snippets, so it feels a bit wasteful to define those variables everywhere.</p>
<p>In the end, I chose to use <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#functionnode">function nodes</a> to call the <code>date</code> command on the fly, when the snippet gets expanded.
This node is inserted in a particular snippet definition and is thus contained to that particular snippet.
This way, we avoid creating values everywhere, and we keep the global namespace clean.
It looks like this:</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">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>, <span style="color:#e06c75">snip</span>, <span style="color:#e06c75">user_arg_1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">trim</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">system</span>(<span style="color:#98c379">[[date -d &#39;]]</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span> <span style="color:#56b6c2">..</span> <span style="color:#98c379">[[&#39; +&#39;%F %a&#39;]]</span>))
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>, {}),
</span></span></code></pre></div><h3 id="3-generating-snippets-variants">3. Generating Snippets Variants</h3>
<p>The last step is to generate the variants of the base snippet: <code>bj_today</code>, <code>bj_next_monday</code>, …, <code>bj_next_week</code> and <code>bj_next_month</code>.</p>
<p>In LuaSnip, snippets defined using Lua are just a table calling some agreed-upon functions.
Here is an example from the <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#basics">documentation</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">ls.add_snippets</span>(<span style="color:#98c379">&#34;all&#34;</span>, {
</span></span><span style="display:flex;"><span>	<span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;ternary&#34;</span>, {
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">-- equivalent to &#34;${1:cond} ? ${2:then} : ${3:else}&#34;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#e06c75">i</span>(<span style="color:#d19a66">1</span>, <span style="color:#98c379">&#34;cond&#34;</span>), <span style="color:#e06c75">t</span>(<span style="color:#98c379">&#34; ? &#34;</span>), <span style="color:#e06c75">i</span>(<span style="color:#d19a66">2</span>, <span style="color:#98c379">&#34;then&#34;</span>), <span style="color:#e06c75">t</span>(<span style="color:#98c379">&#34; : &#34;</span>), <span style="color:#e06c75">i</span>(<span style="color:#d19a66">3</span>, <span style="color:#98c379">&#34;else&#34;</span>)
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>This creates a snippet (the <code>s</code> function) for <code>all</code> file types. This snippet will expand <code>ternary</code> into <code>cond ? then : else</code>, prompting the user to change the <code>cond</code>, <code>then</code> and <code>else</code> bits (function <code>i</code>) while <code>?</code> and <code>:</code> are “static text” (function <code>t</code>).</p>
<p>We want to write a function that builds a table of date snippet definitions.
That will be returned when the Lua module is loaded.</p>
<h4 id="the-final-code">The Final Code</h4>
<p>The final snippet looks like this:</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-lua" data-lang="lua"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span><span style="color:#7f848e">-- Bullet Journal styled dates in the future</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">gen_date_snippets</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">snippets</span> <span style="color:#56b6c2">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">target_dates</span> <span style="color:#56b6c2">=</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>    <span style="color:#98c379">&#34;today&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span>    <span style="color:#98c379">&#34;tomorrow&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span>    <span style="color:#98c379">&#34;next monday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>    <span style="color:#98c379">&#34;next tuesday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>    <span style="color:#98c379">&#34;next wednesday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span>    <span style="color:#98c379">&#34;next thursday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span>    <span style="color:#98c379">&#34;next friday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span>    <span style="color:#98c379">&#34;next week&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span>    <span style="color:#98c379">&#34;next month&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">14</span><span>  }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">15</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">target_date</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">pairs</span>(<span style="color:#e06c75">target_dates</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">16</span><span>    <span style="color:#e06c75">table.insert</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">17</span><span>      <span style="color:#e06c75">snippets</span>,
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">18</span><span>      <span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;bj_&#34;</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span>:<span style="color:#e06c75">gsub</span>(<span style="color:#98c379">&#34; &#34;</span>, <span style="color:#98c379">&#34;_&#34;</span>), {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">19</span><span>        <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;# &#34;</span>,
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">20</span><span>        <span style="color:#e06c75">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>, <span style="color:#e06c75">snip</span>, <span style="color:#e06c75">user_arg_1</span>)
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">21</span><span>          <span style="color:#c678dd">return</span> <span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">trim</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">system</span>(<span style="color:#98c379">[[date -d &#39;]]</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span> <span style="color:#56b6c2">..</span> <span style="color:#98c379">[[&#39; +&#39;%F %a&#39;]]</span>))
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">22</span><span>        <span style="color:#c678dd">end</span>, {}),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">23</span><span>      })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">24</span><span>    )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">25</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">26</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">27</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">snippets</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">28</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">29</span><span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">30</span><span><span style="color:#c678dd">return</span> <span style="color:#e06c75">gen_date_snippets</span>()
</span></span></code></pre></div><p>This generates the snippets name (line 18), for instance <code>bj_today</code> or <code>bj_next_tuesday</code> from the arguments passed to the <code>date</code> command.
The body of the snippet is made of text (<code>t &quot;# &quot;</code>) and our function node from earlier (line 20-22).
All those snippets get collected in a table that is returned at the end of the Lua module (line 30).
It’s then usable by LuaSnip.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Generating a bunch of similar snippets like this turned out to be an unanticipated benefit of using Lua to define snippets.
Before using LuaSnip, I only had <code>bj_today</code>, <code>bj_tomorrow</code> and <code>bj_next_monday</code>.
If I had wanted to expand this set further, I would have to do some more copying and pasting, which quickly becomes unmaintainable.</p>
<p>Of course for simpler or less repetitive snippets, it’s probably best to use the less verbose SnipMate-like syntax presented in the <a href="https://cj.rs/blog/ultisnips-to-luasnip/">previous post</a>.</p>
]]></content:encoded></item><item><title>From UltiSnips to LuaSnip</title><link>https://cj.rs/blog/ultisnips-to-luasnip/</link><pubDate>Sun, 15 May 2022 07:06:49 +0100</pubDate><guid>https://cj.rs/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://cj.rs/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></channel></rss>