Main page Rss feed

OSC7 in Neovim: third time's a charm

TL;DR

My personal history with OSC 7

For all the reasons I mentionned in my first article about OSC7 and Neovim, running all of your shells in Neovim's terminal is super nice. But as I mentionned in the same article, the shell's current directory not being synchronized with Neovim's was a bummer.

From 2017 to 2021, I've been solving this synchronization problem with a disgusting mix of python, shell and vimscript that ran each time I changed directory in the shell. In 2021, I got fed up with the jankyness of my solution and wrote a Neovim patch to get synchronization working directly in Neovim's terminal through the use of OSC 7 escape sequences.

Working through the various iterations of my patch, I got increasingly uncomfortable with how narrow it was and ended up drifting toward a generic autocommand named TermOSC that would enable handling any OSC event.

I eventually became happy enough with the patch that I started daily-driving my fork, opened a PR and wrote an article about it. I wasn't aware of it at the time, but as outlined in this comment, Neovim autocommands run asynchronously, so my perfectly-valid-for-OSC7 patch wasn't actually usable for OSC events that required more synchronization with the terminal's output. I recommended not merging my PR, but also said that I would not work on getting synchronous OSC support working in Neovim, as my patch was sufficient for my needs.

And so my PR died. I kept using my increasingly stale fork until a year later, when my hero @gpanders decided to revive it, renaming the TermOSC autocommand to TermRequest and performing a few fixes, and got it merged in less than a week 🤯. This TermRequest event still has the major synchronization issue my TermOSC event had, but I'm glad nonetheless: the PR getting merged means that I can run Neovim nightly again.

This means that, if you run Neovim nightly, you can now synchronize your shell's current working dir and Neovim's current working dir with just a tiny bit of shell and lua scripting!

Scripting OSC7 support

In your init.lua, add the following:

vim.api.nvim_create_autocmd({ 'TermRequest' }, {
  callback = function(e)
    if string.sub(vim.v.termrequest, 1, 4) == "\x1b]7;" then
      local dir = string.gsub(vim.v.termrequest, "\x1b]7;file://[^/]*", "")
      if vim.fn.isdirectory(dir) == 0 then
        return
      end
      vim.api.nvim_buf_set_var(e.buf, "last_osc7_payload", dir)
      if vim.o.autochdir and vim.api.nvim_get_current_buf() == e.buf then
        vim.cmd.cd(dir)
      end
    end
  end
})
vim.api.nvim_create_autocmd({ 'bufenter', 'winenter', 'dirchanged' }, {
  callback = function(e)
    if vim.b.last_osc7_payload ~= nil
      and vim.fn.isdirectory(vim.b.last_osc7_payload) == 1
    then
      vim.cmd.cd(vim.b.last_osc7_payload)
    end
  end
})

And then, just make sure your shell is emitting OSC 7 sequences. For bash, add the following to your .bashrc:

function print_osc7() {
  printf "\033]7;file://$HOSTNAME/$PWD\033\\"
}
PROMPT_COMMAND="print_osc7${PROMPT_COMMAND:+;$PROMPT_COMMAND}"

For zsh, add the following to your .zshrc:

function print_osc7() {
  printf "\033]7;file://$HOST/$PWD\033\\"
}
autoload -Uz add-zsh-hook
add-zsh-hook -Uz chpwd print_osc7

For fish, add the following to your fish config:

function print_osc7 --on-variable=PWD
  printf "\033]7;file://$HOSTNAME/$PWD\033\\"
end