<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Scott Berrevoets - Developer Tools</title><link href="https://scottberrevoets.com/" rel="alternate"/><link href="https://scottberrevoets.com/feeds/developer-tools.atom.xml" rel="self"/><id>https://scottberrevoets.com/</id><updated>2026-07-01T00:00:00-07:00</updated><entry><title>How GitHub handles Git LFS</title><link href="https://scottberrevoets.com/2026/07/01/how-github-handles-git-lfs/" rel="alternate"/><published>2026-07-01T00:00:00-07:00</published><updated>2026-07-01T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2026-07-01:/2026/07/01/how-github-handles-git-lfs/</id><summary type="html">&lt;p&gt;Snapshot tests have become an increasingly popular tool for mobile
teams to verify UI changes, but one question all teams run into is: where
do we store hundreds if not thousands of PNG files? Git Large File Storage
(LFS) is one of the more common ways to do this for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Snapshot tests have become an increasingly popular tool for mobile
teams to verify UI changes, but one question all teams run into is: where
do we store hundreds if not thousands of PNG files? Git Large File Storage
(LFS) is one of the more common ways to do this for various reasons&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;At Speak we recently went down this same path and in doing so learned a few
things about how Git LFS works and how GitHub handles it. Initially we just
learned the basic premise: instead of checking in a file, check in a small
human-readable pointer file that identifies the large file:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary aria-label="Toggle code block"&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;version https://git-lfs.github.com/spec/v1
oid sha256:907b5c652cce59e009e3c7fb2dc92d3bf598251315bed741aee037f2046bd32e
size 251222
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;The &lt;code&gt;oid&lt;/code&gt; is an object ID for the file that's fetched from the configured LFS
server. Use &lt;code&gt;git lfs track ./Snapshots/*.png&lt;/code&gt; to specify snapshots should be
stored in LFS and then commit &lt;code&gt;.gitattributes&lt;/code&gt; to preserve that setting.&lt;/p&gt;
&lt;p&gt;But soon we started getting warnings from GitHub that we were exceeding our
Git LFS budget. In investigating, we learned five more things that formed
our mental model around how to think about Git LFS when using GitHub:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub charges for &lt;a href="https://docs.github.com/en/billing/concepts/product-billing/git-lfs#how-use-of-git-lfs-is-measured"&gt;storage and bandwidth&lt;/a&gt;&lt;/strong&gt;: similar to S3, both storage
   and bandwidth cost money. Our snapshots don't take up too much storage but CI
   clones the iOS and Android repos for every build, which is when all PNGs are
   downloaded, incurring bandwidth usage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can skip downloading certain LFS objects to avoid bandwidth usage&lt;/strong&gt;: to
   avoid this, Git LFS offers a clone option to avoid downloading large files
   when they aren't needed through &lt;code&gt;git clone --config
   lfs.fetchexclude="./Snapshots/*.png"&lt;/code&gt;. This saves bandwidth, clone time, and
   local storage. &lt;code&gt;git lfs pull --include="./Snapshots/*.png"&lt;/code&gt; lets you
   redownload them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removing a tracked file only removes it from git, not from storage&lt;/strong&gt;:
   removing the pointer files that get checked in and committing that change
   does nothing to the actual object in storage. Removing the file from git
   leaves a dangling object that still takes up storage space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fully removing references requires &lt;a href="https://docs.github.com/en/repositories/working-with-files/managing-large-files/removing-files-from-git-large-file-storage#removing-a-single-file"&gt;rewriting history&lt;/a&gt;&lt;/strong&gt;: removing a
   file from git isn't sufficient because older commits may still refer to an
   LFS file and fail future clones. Fully purging it requires rewriting the git
   history using &lt;code&gt;git filter-repo&lt;/code&gt;, which is obviously not ideal but arguably
   still better than irreversibly not being able to check out certain commits
   in the repo ever again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rewriting history doesn't delete the file from storage&lt;/strong&gt;: even after all
   references to a file are deleted, the LFS object itself still needs to be
   deleted as well for storage to be freed up. The only way to do this other
   than to contact support, is to delete the repo (and all its associated data)
   and recreate it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these details make it clear that Git LFS isn't a regular git repository
that handles large files better; there's a sophisticated cloud storage layer
behind it that needs the same considerations that more mainstream cloud storage
does. Although git does the version control of those files, it doesn't touch the
raw objects itself.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Checking in large binary files that change regularly causes repo
  bloat, and cloud storage solutions like S3 or GCS make for a more
  complicated setup.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Developer Tools"/><category term="Testing"/><category term="Developer Experience"/></entry><entry><title>Scaling my git/tmux workflows for AI agents</title><link href="https://scottberrevoets.com/2026/02/17/scaling-my-gittmux-workflows-for-ai-agents/" rel="alternate"/><published>2026-02-17T00:00:00-08:00</published><updated>2026-02-17T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2026-02-17:/2026/02/17/scaling-my-gittmux-workflows-for-ai-agents/</id><summary type="html">&lt;p&gt;After I started to ramp up on agentic coding, I found my workflow lagging behind
how I ended up working. Previously I would clone my project's repo twice and
switch back and forth between the clones. I used stack-based git workflow
&lt;a href="https://github.com/keith/git-pile"&gt;git-pile&lt;/a&gt;, which eliminated the overhead of switching branches and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After I started to ramp up on agentic coding, I found my workflow lagging behind
how I ended up working. Previously I would clone my project's repo twice and
switch back and forth between the clones. I used stack-based git workflow
&lt;a href="https://github.com/keith/git-pile"&gt;git-pile&lt;/a&gt;, which eliminated the overhead of switching branches and stashing
changes.&lt;/p&gt;
&lt;p&gt;When I was occasionally in the middle of something and needed to context switch
and have a clean repo, I would have the other clone and that was about as much
as I could handle at a time. I knew of git worktrees but never needed it so
didn't bother upgrading my workflow.&lt;/p&gt;
&lt;p&gt;In the age of AI I found myself needing a lot more. Understanding various parts
of the codebase is one prompt away and making changes requires a much smaller
context switch than before so I do it much more frequently. However, the two
clones weren't really sufficient for this anymore, as it became more painful to
sync changes back and forth before they landed on &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So I had Claude design me a new workflow that scaled better and allowed more
parallelization. I wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one repo with &lt;code&gt;n&lt;/code&gt; worktrees with no prior set up required&lt;/li&gt;
&lt;li&gt;tmux integration so each worktree gets its own tmux window&lt;/li&gt;
&lt;li&gt;no dangling worktrees and branches if I abandon work&lt;/li&gt;
&lt;li&gt;compatibility with git-pile, but no direct integration&lt;/li&gt;
&lt;li&gt;packaged in an easy to use CLI with powerful commands that capture a lot of
  complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted to use worktrees under the hood, but also have that be an
implementation detail as I'd prefer to not deal with them directly.&lt;/p&gt;
&lt;p&gt;The end result is a Python script I called &lt;code&gt;agents&lt;/code&gt; that has 6 commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new [name]&lt;/code&gt; - create a new worktree&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt; - lists all existing tasks/worktrees&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merge&lt;/code&gt; - merge the current changes into the parent repo on &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm&lt;/code&gt; - delete this worktree and close its tmux window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; - create a tmux window with the specified worktree (in case it was
  closed)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clean&lt;/code&gt; - delete all stale worktrees and branches, and closes their tmux
  windows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This provides a very simple workflow:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Initialize worktree&lt;/span&gt;
&lt;span class="c1"&gt;# No subcommand is shorthand for `new`&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;find-bug

&lt;span class="c1"&gt;# Run some agents&lt;/span&gt;
codex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fix the race condition in DataManager&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# switch to another agent/tmux window, do some work there, then return&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# Codex output looks good - let&amp;#39;s upstream (or have the agent do it)&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fix race condition&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Merge commit back to main repo&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;merge

&lt;span class="c1"&gt;# Delete worktree and close tmux window&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;rm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; branch on my repo clone now has this commit, and I can use &lt;code&gt;git-pile&lt;/code&gt;
to submit a pull request with this change. This workflow works in any git repo
out of the box, no special setup required.&lt;/p&gt;
&lt;p&gt;While Codex is running, I can switch back to the previous tmux window and start
another agent, or I can even branch off that particular worktree into a new one.
I'm not sure I want that from the workflow since I prefer working on the repo's
&lt;code&gt;main&lt;/code&gt; branch in the first place, so time will tell if I end up using that
branching behavior.&lt;/p&gt;
&lt;p&gt;Ideally these worktrees are shortlived and will just get their work merged in.
However, there are situations where there's longer running work or work I end up
not moving forward with, and for that I had Claude implement an &lt;code&gt;agents clean&lt;/code&gt;
command. It deletes all stale worktrees, with some protections for unmerged work
to avoid losing uncommitted changes.&lt;/p&gt;
&lt;p&gt;To understand what work is in progress, &lt;code&gt;agents list&lt;/code&gt; gives a nicely-formatted
output that's piped to &lt;code&gt;fzf&lt;/code&gt; for easy selection:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Terminal output after listing agents" src="/images/agents-list.png"&gt;&lt;/p&gt;
&lt;p&gt;The arrows indicate commit status, which is helpful to understand if any
worktrees may need rebasing.&lt;/p&gt;
&lt;p&gt;After building and working this tool a bit I realized the workflow works even
when not using an agentic programming workflow, so &lt;code&gt;agents&lt;/code&gt; is probably not the
best name, but since I developed it with that use case in mind I'll probably
continue to use that for now.&lt;/p&gt;
&lt;p&gt;The full script is in my &lt;a href="https://github.com/sberrevoets/dotfiles/blob/master/bin/agents"&gt;dotfiles&lt;/a&gt;' &lt;code&gt;./bin&lt;/code&gt; folder, which is automatically
accessible anywhere from my Terminal. I've also written a Claude/Codex skill to
run this automatically for me in case I forget to do so myself, and I'll
probably keep tweaking the workflow to my liking as I work with it more.&lt;/p&gt;</content><category term="Developer Tools"/><category term="Developer Experience"/></entry><entry><title>Composing a resume in markdown</title><link href="https://scottberrevoets.com/2025/03/15/composing-a-resume-in-markdown/" rel="alternate"/><published>2025-03-15T00:00:00-07:00</published><updated>2025-03-15T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-03-15:/2025/03/15/composing-a-resume-in-markdown/</id><summary type="html">&lt;p&gt;I recently updated my resume, and after a few iterations of tweaking the same
Pages file I started more than a decade ago, I decided that I really wanted
source control to make it easier to keep track of changes and to have different
versions of the same resume. Additionally …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently updated my resume, and after a few iterations of tweaking the same
Pages file I started more than a decade ago, I decided that I really wanted
source control to make it easier to keep track of changes and to have different
versions of the same resume. Additionally, the very consistent structure and
simplicity of a resume makes it a perfect candidate to be written in markdown,
which is a text-based format and easy to have in source control.&lt;/p&gt;
&lt;p&gt;Some quick searching confirmed my suspicion that many people were already doing
this and that there are various tools available to make this easier. The
primary tool is &lt;a href="http://pandoc.org/"&gt;pandoc&lt;/a&gt;, which is a universal document
converter. It can convert between many different formats, including markdown and
PDF.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The other tool that makes for a very structured resume is &lt;a href="https://jsonresume.org"&gt;JSON
Resume&lt;/a&gt;. It's a JSON schema with standard fields for
common resume sections like 'experience' and 'education' that can then
easily be themed. I found this a little too limiting and not as readable in
plain text as markdown.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my case, I'm converting from markdown to HTML, injecting some CSS to make it
look the way I want it to, and then converting that to PDF. LaTeX was another
option but I wasn't familiar with either LaTeX or ConTeXt and didn't want to
bother learning it just for this exercise.&lt;/p&gt;
&lt;p&gt;Converting to HTML was generally easy.&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;--include-in-header&lt;span class="w"&gt; &lt;/span&gt;resume.css&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;This gives a pretty good starting point even with no CSS at all. &lt;code&gt;pandoc&lt;/code&gt; uses a
custom markdown dialect with some extensions and other features, which makes it
easy to add ids and classes to &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;-&lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; elements. This is useful as it
makes applying the right style to the right element easy. The resume itself is
simple and so is the resulting HTML. &lt;code&gt;pandoc&lt;/code&gt; does inject its own CSS as well,
which is easy to override in the custom CSS file.&lt;/p&gt;
&lt;p&gt;After tweaking the CSS to my liking, the next step was to convert the HTML to
PDF format. This was a bit more complicated as there were multiple ways to do
that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Safari&lt;/strong&gt; (or another browser): There is an export as PDF option or the
  option to print and then save as PDF. This worked but required manual steps I
  wanted to automate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wkhtmltopdf&lt;/strong&gt;: a command line tool that converts HTML to PDF using a WebKit
  engine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pandoc&lt;/strong&gt;: can convert to PDF directly with some options that, supposedly,
  affect the final PDF output. I tried using it with &lt;code&gt;wkhtmltopdf&lt;/code&gt; as its
  engine, and it can also use LaTeX or ConTeXt as an intermediate format that
  bypasses HTML generation altogether.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I struggled a bit with which to choose, as the format for all of them wasn't
quite right except for the print to PDF option in Safari. I ultimately settled
on using &lt;code&gt;wkhtmltopdf&lt;/code&gt; directly (without using &lt;code&gt;pandoc&lt;/code&gt; for this step), after
experimenting with these options and seeing which output I liked best.
Converting the HTML to PDF was as simple as:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wkhtmltopdf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;letter&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;The &lt;code&gt;-s letter&lt;/code&gt; option sets the paper size to US Letter since the default is A4.
Nowadays it doesn't really matter as resumes are only ever used in digital
format so the size of the PDF doesn't really matter anyway.&lt;/p&gt;
&lt;p&gt;I then wrapped both commands in a simple shell script to make it easier to run:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--include-in-header&lt;span class="w"&gt; &lt;/span&gt;resume.css&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;resume.md

wkhtmltopdf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;letter&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.pdf

open&lt;span class="w"&gt; &lt;/span&gt;resume.pdf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;resume.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;After some modifications to the markdown, running this script will quickly
generate the HTML and PDF files and open them both so I can validate the result.&lt;/p&gt;
&lt;p&gt;This setup ends up working pretty well for me for 3 reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;With everything in a git repo I can make changes as I like, undo them easily,
   and use all of git's features on my resume. I push the markdown, HTML, and
   PDF versions to GitHub so I always have the latest version handy in a few
   different formats.&lt;/li&gt;
&lt;li&gt;The format and the content are for the most part separate, making it easier
   to focus on one or the other.&lt;/li&gt;
&lt;li&gt;Since everything is now text-based and not in proprietary binary formats,
   it's also easy to overengineer this in the future by including certain
   sections when applying to different jobs, including certain information only
   when certain flags are set, differentiating between a CV and a resume, etc.&lt;/li&gt;
&lt;/ol&gt;</content><category term="Developer Tools"/><category term="Developer Experience"/></entry><entry><title>Looking up words in a dictionary with Neovim</title><link href="https://scottberrevoets.com/2024/12/08/looking-up-words-in-a-dictionary-with-neovim/" rel="alternate"/><published>2024-12-08T00:00:00-08:00</published><updated>2024-12-08T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2024-12-08:/2024/12/08/looking-up-words-in-a-dictionary-with-neovim/</id><summary type="html">&lt;p&gt;When writing code in neovim, I frequently use &lt;code&gt;K&lt;/code&gt; to look up documentation,
function signatures, variable definitions, etc. In markdown files that doesn't
make too much sense, but I wanted to use it to look up words in the dictionary
instead. This didn't appear to be possible out of the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When writing code in neovim, I frequently use &lt;code&gt;K&lt;/code&gt; to look up documentation,
function signatures, variable definitions, etc. In markdown files that doesn't
make too much sense, but I wanted to use it to look up words in the dictionary
instead. This didn't appear to be possible out of the box but the power of
(neo)vim is its extensibility so I decided to build it myself.&lt;/p&gt;
&lt;p&gt;The steps to build this roughly were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a dictionary lookup tool&lt;/li&gt;
&lt;li&gt;Parse this tool's output and show it in neovim&lt;/li&gt;
&lt;li&gt;Configure neovim's &lt;code&gt;K&lt;/code&gt; functionality&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="dict"&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dict&lt;/code&gt; seemed like an appropriate, easy name to look up a word in the
dictionary. As I'm a macOS user I first wanted to use the built-in dictionary
through
&lt;a href="https://gist.github.com/lambdamusic/bdd56b25a5f547599f7f"&gt;&lt;code&gt;pyobjc-framework-CoreServices&lt;/code&gt;&lt;/a&gt;
but &lt;code&gt;DCSCopyTextDefinition&lt;/code&gt; doesn't return dictionary entries in a
well-structured or consistent way, so I ultimately decided to write a quick
lookup using &lt;a href="https://dictionaryapi.dev"&gt;dictionaryapi.dev&lt;/a&gt;. I also added some
other formatting and ANSI coloring to improve the CLI output:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Python&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;

&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[0m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_BOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[1m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_DIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[2m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_RED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[91m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_GREEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[92m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_BLUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[94m&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_fetch_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.dictionaryapi.dev/api/v2/entries/en/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle API errors&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_format_definition_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;error: word not found&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;word&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;meanings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;meanings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_GREEN&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_BOLD&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part_of_speech&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;meanings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;partOfSpeech&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_BLUE&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;synonyms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;synonyms&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_DIM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;synonyms&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;synonyms&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;definitions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;definition_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;definition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;N/A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;example&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;definition_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; (e.g., &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;word_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_fetch_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_format_definition_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fetch word definitions from API dictionary.dev.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;word&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Word to look up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_get_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;This file needs to be in the &lt;code&gt;PATH&lt;/code&gt; which I did by putting it in
&lt;code&gt;~/.bin/dict.py&lt;/code&gt; and setting &lt;code&gt;export PATH="$HOME/.bin:$PATH"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dict ambiguous&lt;/code&gt; now gives a nicely formatted definition:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dictionary lookup of the word ambiguous in the
terminal" src="/images/dictionary-lookup-cli.png"&gt;&lt;/p&gt;
&lt;h2 id="neovim-integration"&gt;Neovim integration&lt;/h2&gt;
&lt;p&gt;I wanted to invoke &lt;code&gt;dict&lt;/code&gt; using &lt;code&gt;K&lt;/code&gt; passing in the word under the cursor as the
trigger and showing the output in a popup window. Other than the plain vim
configuration this required some text manipulation so I put all that in
&lt;code&gt;~/.vim/plugin/dictionary.lua&lt;/code&gt;:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Lua&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a new buffer with the given message and apply ANSI highlighting&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[[0-9;]+m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_buf_set_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;apply_ansi_highlighting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;

  &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Show a popup window with the given buffer&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;show_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_open_win&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;relative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;cursor&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;NW&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;minimal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;border&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;single&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Close the popup window&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ClosePopup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt;
  &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_win_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_win_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Look up the definition of the word under the cursor&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DictionaryLookup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;cword&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;io.popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dict &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;show_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;This creates 2 functions, &lt;code&gt;DictionaryLookup()&lt;/code&gt; and &lt;code&gt;ClosePopup()&lt;/code&gt; that show and
dismiss the popup, stripping the ANSI codes from the output as they were
rendered raw in the popup.&lt;/p&gt;
&lt;p&gt;However, note the call to &lt;code&gt;apply_ansi_highlighting(buf, i - 1, line)&lt;/code&gt; near the
end of &lt;code&gt;create_buf()&lt;/code&gt;, which replaces the ANSI codes with neovim specific
highlight groups. This turned
out to be quite involved but I wanted the formatted output and used this as an
opportunity to learn lua a bit more. &lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Lua&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;underline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;italic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bold&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;underline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dim&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gray&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;italic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Green&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;green&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ansi_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[0m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[1m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bold&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[2m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dim&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[91m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Red&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[92m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Green&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[94m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Blue&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;-- Find the highlight group for a given ANSI escape code&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;find_ansi_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ansi_patterns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
      &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;highlight&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
  &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Apply ANSI highlighting to a line of text in the given buffer&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apply_ansi_highlighting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[0m$&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[[0-9]+m&amp;quot;&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="kr"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;start_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_buf_add_highlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;find_ansi_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt;
      &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;h2 id="configuring-k"&gt;Configuring &lt;code&gt;K&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Opening any file and calling &lt;code&gt;:lua DictionaryLookup()&lt;/code&gt; already works (and &lt;code&gt;:lua
ClosePopup()&lt;/code&gt; to dismiss it) but that's a lot of typing and it better fits &lt;code&gt;K&lt;/code&gt;
in markdown files since that's otherwise unused anyway.&lt;/p&gt;
&lt;p&gt;I mapped the first to a vim command &lt;code&gt;:Dict&lt;/code&gt; and wanted to hide the window with
any cursor movement, similar to how this works for other documentation lookups. 
&lt;code&gt;keywordprg&lt;/code&gt; is the command that gets invoked through &lt;code&gt;K&lt;/code&gt; (and can also be set
through &lt;code&gt;set keywordprg&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I put this in &lt;code&gt;~/.vim/ftplugin/markdown.lua&lt;/code&gt; so that it automatically is applied
to markdown files:&lt;/p&gt;
&lt;details class="code-block" open&gt;&lt;summary&gt;&lt;span class="code-block-arrow" aria-hidden="true"&gt;&lt;/span&gt;&lt;span&gt;Lua&lt;/span&gt;&lt;/summary&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DictionaryLookup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keywordprg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;:Dict&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_autocmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CursorMoved&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClosePopup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;With the end result being:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dictionary lookup of the word ambiguous in
neovim" src="/images/dictionary-lookup-neovim.png"&gt;&lt;/p&gt;
&lt;p&gt;Dictionary lookup also still works in non-markdown files by directly invoking
&lt;code&gt;:Dict&lt;/code&gt;, which is a bit more typing but also much less common. That's all that
was needed, now &lt;code&gt;K&lt;/code&gt; performs a dictionary lookup and shows it nicely formatted
in-line and is automatically hidden when needed. This may be nice as a generic
plugin as well so I might do that at some point in the future as well.&lt;/p&gt;</content><category term="Developer Tools"/><category term="Developer Experience"/></entry></feed>