Skip to content

Instantly share code, notes, and snippets.

@ilan-schemoul
Last active July 26, 2026 13:54
Show Gist options
  • Select an option

  • Save ilan-schemoul/8587bff1c90cf6dd0408a8a1a3fd6d41 to your computer and use it in GitHub Desktop.

Select an option

Save ilan-schemoul/8587bff1c90cf6dd0408a8a1a3fd6d41 to your computer and use it in GitHub Desktop.
Fish inside Nvim (how to and optimisations)
Rest of this file will take fish as the Shell, it works roughly the same with other shell though.
NOTE: if you add `set shell=fish` in nvim there's actually a third fish process. It becomes fish1->nvim(->starts its internal fish shell (fish2))->fish3. We are in fish 2 if $NVIM_LOG_FILE is defined but not $NVIM (from my limited tests).
# Fish sandwich
When I start a shell I want it to run inside Nvim, so at any time I can access
nvim (with houdini plugin I can just type hj in my terminal emulator to exit terminal mode, and be in normal nvim mode).
It is not possible, AFAIK, to directly start Nvim from Kitty (or other
emulator). So I start Fish, which starts Nvim, which starts fish again.
This is what I call:
- Fish stage 1
- Nvim
- Fish stage 2
# Code snippet
Kitty calls fish: `shell fish` in kitty.conf
Fish stage 1 calls nvim:
```fish
# Only put stuff you need in Nvim here (it will be executed twice so it's costly).
if status is-interactive
and test -z "$NVIM"
nvim -c ":term fish"
```
Nvim calls Fish stage 2: see snippet above (it's the `-c` part)
# Perf
As there is 3 steps involved it can be slow to startup. When CPU is busy,
without optimisations it can take multiple seconds.
## Benchmark
- Install hyperfine
- Run a fish terminal without Nvim
For fish stage 1 you need to add a exit 0 before the if block that starts nvim
(to emulate the fact stage 1 should never execute the rest of the file)
Fish stage 1: `hyperfine fish -c 'exit'` (as -c is non interactive command, nvim won't be started (see the code snippet))
Nvim: `hyperfine "NVIM='fake' fish -c 'exit'"` (add -u none to benchmark without config)
Fish stage 2: inside nvim `hyperfine "fish -c 'exit'"`
Results on my mac:
- Fish stage 1: 3ms
- Nvim: 70ms (100 plugins in total, but heavy lazy loading, see below)
- Fish stage 2: 35ms
To profile each step:
- Nvim, use lazy profiler if you use Lazy.
- Fish:
- Run fish with --profile-startup /tmp/startup.prof. As explained
previously, depending on if NVIM variable is set you are testing stage 1
or stage 2.
- sort -nk2 /tmp/startup.prof to read the data sorted. Left column is own time,
right column is own time + children time.
# Optim
## Fish stage 1
I added a check in each conf.d file if "$NVIM" is defined we `return` to
avoid reading the file for stage 1. Except the bits Nvim might need, which we put
above the guard clause (e.g.: add brew in path so nvim has access to binaries).
Same for config.fish, everything is put after exec nvim bloc except if needed by
nvim.
With these two things, we make fish stage 1 faster.
## Nvim
Another huge optim that almost doubled nvim startup time, is to have defined a
user event that only gets triggered once we have loaded our first file. Most
plugins use that event. It means that as long as we are in terminal mode and
haven't opened our first file yet most plugins are not loaded.
See https://github.com/ilan-schemoul/nvim-config/blob/f2014aef160dbef564b3828c743e2b722dde72d2/nvim/lua/config/lazy.lua#L17.
Then just use NormalBufferEnter as the `event` for each plugin you don't need in terminal mode.
## Fish (stage 1 and 2)
I have noticed various things that can be slow. For example, brew default
command to source is very slow (20ms). I executed it once manually, read the
variables it exports and manually have run fish commands (such as set -U or
fish_add_paths) that save permanently the variables.
fish_add_paths is very slow if we call it with a path not already in $PATH. If a
file does not exist, it means the path is never added to $PATH and we do the
check on disk everytime. It is horrible for the perfs.
I removed every call to fish_add_paths (as they are permanent you do not need
to execute them everytime). It means now I have on new PC to call
fish_add_paths manually once though.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment