At a Glance

Intro

I’ve known of the display-popup feature in tmux for a while now but have been happy enough with my Neovim using FTerm until recently. As I’ve made more and more use of this functionality I’ve found myself being tripped up by my muscle memory. Using the terminal inside Neovim comes with some overlapping shortcuts when doing things inside it. I’m also being tripped up by the differences between tmux and the Neovim terminal being close but just different enough.

A particularly nasty situation comes about because I do all of my development inside of tmux. This means I’m running Neovim inside of tmux, then I’ll open the Neovim terminal to create a git commit, which spawns another Neovim inside of that.

This isn’t insurmountable but it is quite annoying and I finally gave tmux display-popup a try and it solved a lot of these muscle memory issues and overlapping keyboard shortcut problems for me.

Once everything is setup, it looks like this:

The Files

You can just grab my dotfiles!

Or if you just want super basic versions of the files:

.tmux.conf

# Popups are neat bind t display-popup -w 80% -h 80% -T "Shell" -E "tmux-popup" bind -T popup C-b switch-client -T popup-prefix bind -T popup-prefix t run "tmux display-popup -c $( tmux list-clients -F '##{client_name}' | head -n 1 ) -C" bind -T popup-prefix [ copy-mode bind -T popup-prefix Any switch-client -T popup

#!/usr/bin/env bash set -euo pipefail session = "popup" if ! tmux has -t " $session " 2> /dev/null ; then tmux new-session -d -s " $session " tmux set-option -s -t " $session " key-table popup tmux set-option -s -t " $session " status off tmux set-option -s -t " $session " prefix None fi exec tmux attach -t " $session " > /dev/null

Breaking It Down

That’s a lot to take in all at once! Let’s break it down a bit and look at how it all works.

bind t display-popup -w 80% -h 80% -T "Shell" -E "tmux-popup"

This creates a key bind that launches a popup that takes up 80% width and 80% height of the terminal. It gives the popup a title of “Shell” and launches our tmux-popup script inside of it.

Before we look at the rest of .tmux.conf file, let’s look at our tmux-popup script as that will make the rest of the config file make sense.

This is just a variable assignment, the blog I took inspiration from had a much more complicated setup that I’ve altered.

session = "popup"

This checks if a tmux session with that name exists and if it doesn’t it then executes…

if ! tmux has -t " $session " 2> /dev/null ; then

This creates the new session named popup then…

tmux new-session -d -s " $session "

We configure that session to use a tmux key-table called popup so it doesn’t use the default prefix key-table that tmux uses.

tmux set-option -s -t " $session " key-table popup

We hide the status bar if it’s turned on globally.

tmux set-option -s -t " $session " status off

We also disable prefix so you can’t use a lot of tmux’s features. This is a personal preference as I found it got a bit fiddly otherwise.

tmux set-option -s -t " $session " prefix None

Finally we actually attach to the new popup session.

exec tmux attach -t " $session " > /dev/null

With all that out of the way, let’s take a look at our keybinds in .tmux.conf now. This is a trick I used to make it seem like we’re still using the prefix of Ctrl + b (the tmux default and the one I still use). This switches us to another key-table so we can easily limit what we’re able to do here.

bind -T popup C-b switch-client -T popup-prefix

This is where a lot of the complication of this setup comes from. I wanted the same set of key presses to both open the popup and also close it. This proved a lot more difficult than expected and if you’re more of a tmux/bash guru than me you’ll probably want to change this line.

This binds tmux display-popup -C which closes any open popups, the trick part comes from needing to specify the client to actually send that request too. This is because if you send it to the popup session it doesn’t work, you need to select the parent session. Since my workflow is really simple. I only ever have one session in a server plus my popup session. So we just use tmux list-clients to grab the first one and send the command there.

bind -T popup-prefix t run "tmux display-popup -c $( tmux list-clients -F '##{client_name}' | head -n 1 ) -C"

This let’s us enter copy-mode the same as normal a tmux session which will act the same as always.

bind -T popup-prefix [ copy-mode

Finally this catches any other key and sets the key-table back so we need to do Ctrl + b again.

bind -T popup-prefix Any switch-client -T popup

Running Commands from Neovim

The final piece of this puzzle was a small Lua function to run shell commands from Neovim inside of this popup. I spent some time trying to get this to take another table or a string in as that’s how my commands were being run before, but I failed to do so simple and decided that this was fine for now.

function run_in_popup ( cmd ) vim . system ( { "tmux-popup" , "-d" }, { }, function () vim . system ({ "tmux" , "send-keys" , "-t" , "popup" , cmd , "C-m" }) vim . system ({ "tmux" , "display-popup" , "-w" , "80%" , "-h" , "80%" , "-T" , "Shell" , "-E" , "tmux attach -t popup" }) end ) end

Outro

This is what it looks like with my full config setup.

If your browser doesn't support the video tag, you can download the video. A terminal running Neovim in tmux. A popup titled 'Shell' appears and a few commands are run including a nested Vim. Then the popup is dismissed and brought back multiple times.

I decided to write this blog post as I spent a fair bit of energy the past couple of days getting this working nicely. I thought it would be nice to give back to the community and document everything I figured out.