mrjones2014 / smart-splits.nvim

🧠 Smart, seamless, directional navigation and resizing of Neovim + terminal multiplexer splits. Supports tmux, Wezterm, and Kitty. Think about splits in terms of "up/down/left/right".
MIT License
895 stars 37 forks source link

[Bug]: Wezterm navigation not working #127

Closed milch closed 12 months ago

milch commented 12 months ago

Similar Issues

Neovim Version

NVIM v0.9.1
Build type: Release
LuaJIT 2.1.0-beta3

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/opt/homebrew/Cellar/neovim/0.9.1/share/nvim"

Multiplexer Integration

Wezterm

Multiplexer Version

wezterm 20230712-072601-f4abf8fd

Steps to Reproduce

  1. Split pane in wezterm
  2. Open nvim in one of the panes with smart-splits installed
  3. Try to navigate out of the neovim split with the <C-h>, <C-j>, <C-l>, <C-k> keymaps

Expected Behavior

I can navigate between wezterm splits and neovim splits seamlessly

Actual Behavior

I can navigate between wezterm splits, into splits containing neovim, and within neovim between different neovim splits, but once I'm inside of neovim I stay "trapped" inside - I can't navigate "back out" to wezterm splits.

Minimal Configuration to Reproduce

I can reproduce it with the minimal init.lua template:

local root = vim.fn.fnamemodify('./.repro', ':p')

-- set stdpaths to use .repro
for _, name in ipairs({ 'config', 'data', 'state', 'cache' }) do
  vim.env[('XDG_%s_HOME'):format(name:upper())] = root .. '/' .. name
end

-- bootstrap lazy
local lazypath = root .. '/plugins/lazy.nvim'
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    'git',
    'clone',
    '--filter=blob:none',
    '--single-branch',
    'https://github.com/folke/lazy.nvim.git',
    lazypath,
  })
end
vim.opt.runtimepath:prepend(lazypath)

-- install plugins
local plugins = {
  -- do not remove the colorscheme! it makes testing nicer
  'folke/tokyonight.nvim',
  'mrjones2014/smart-splits.nvim',
  -- add any other pugins here
}

require('lazy').setup(plugins, {
  root = root .. '/plugins',
})

require('smart-splits').setup({
  -- add any options here
})

-- recommended mappings
-- resizing splits
-- these keymaps will also accept a range,
-- for example `10<A-h>` will `resize_left` by `(10 * config.default_amount)`
vim.keymap.set('n', '<A-h>', require('smart-splits').resize_left)
vim.keymap.set('n', '<A-j>', require('smart-splits').resize_down)
vim.keymap.set('n', '<A-k>', require('smart-splits').resize_up)
vim.keymap.set('n', '<A-l>', require('smart-splits').resize_right)
-- moving between splits
vim.keymap.set('n', '<C-h>', require('smart-splits').move_cursor_left)
vim.keymap.set('n', '<C-j>', require('smart-splits').move_cursor_down)
vim.keymap.set('n', '<C-k>', require('smart-splits').move_cursor_up)
vim.keymap.set('n', '<C-l>', require('smart-splits').move_cursor_right)
-- swapping buffers between windows
vim.keymap.set('n', '<leader><leader>h', require('smart-splits').swap_buf_left)
vim.keymap.set('n', '<leader><leader>j', require('smart-splits').swap_buf_down)
vim.keymap.set('n', '<leader><leader>k', require('smart-splits').swap_buf_up)
vim.keymap.set('n', '<leader><leader>l', require('smart-splits').swap_buf_right)

-- add anything else here
vim.opt.termguicolors = true
-- do not remove the colorscheme! it makes testing nicer
vim.cmd([[colorscheme tokyonight]])

My wezterm.lua is pretty minimal as well:

local w = require('wezterm')

-- if you are *NOT* lazy-loading smart-splits.nvim (recommended)
local function is_vim(pane)
  -- this is set by the plugin, and unset on ExitPre in Neovim
  return pane:get_user_vars().IS_NVIM == 'true'
end

local direction_keys = {
  Left = 'h',
  Down = 'j',
  Up = 'k',
  Right = 'l',
  -- reverse lookup
  h = 'Left',
  j = 'Down',
  k = 'Up',
  l = 'Right',
}

local function split_nav(resize_or_move, key)
  return {
    key = key,
    mods = resize_or_move == 'resize' and 'META' or 'CTRL',
    action = w.action_callback(function(win, pane)
      if is_vim(pane) then
        -- pass the keys through to vim/nvim
        win:perform_action({
          SendKey = { key = key, mods = resize_or_move == 'resize' and 'META' or 'CTRL' },
        }, pane)
      else
        if resize_or_move == 'resize' then
          win:perform_action({ AdjustPaneSize = { direction_keys[key], 3 } }, pane)
        else
          win:perform_action({ ActivatePaneDirection = direction_keys[key] }, pane)
        end
      end
    end),
  }
end

local config = {
  color_scheme = "catppuccin-latte",
  hide_tab_bar_if_only_one_tab = true,
  font = w.font('SF Mono', { weight = 'Medium' }),
  font_size = 14.0,
  freetype_load_target = "Light",
  freetype_load_flags = 'NO_HINTING',
  keys = {
    -- move between split panes
    split_nav('move', 'h'),
    split_nav('move', 'j'),
    split_nav('move', 'k'),
    split_nav('move', 'l'),
    -- resize panes
    split_nav('resize', 'h'),
    split_nav('resize', 'j'),
    split_nav('resize', 'k'),
    split_nav('resize', 'l'),
  },
  set_environment_variables = {
    -- Compatibility with `fish` cwd (OSC7) reporting
    VTE_VERSION = '6003',
  },
  inactive_pane_hsb = {
    saturation = 0.95,
    brightness = 0.95,
  },
}

return config

Additional Details and/or Screenshots

No response

milch commented 12 months ago

Whelp, this one's on me. wezterm needs to be on the PATH for this integration to work. Once I added it using set -gx /Applications/WezTerm.app/Contents/MacOS $PATH this plugin started working as well.

The weird thing is that the only thing I see in the logs using :SmartSplitsLog are lines like these:

[Mon Sep 11 16:04:08 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
mrjones2014 commented 12 months ago

No worries, I can add a sentence in the docs mentioning this.

milch commented 12 months ago

This is great, thank you!

JolliestJames commented 9 months ago

Hi, I seem to be having a problem similar to the OP, but in reverse. It seems that the IS_NVIM user var is not being set, and so commands are not being passed to nvim when inside of a pane with an active nvim instance. So I can switch into and out of wezterm panes no problem, with or without an active nvim instance, but when inside nvim, smart-splits isn't receiving any commands so I can't move between nvim panes. Interestingly, if I enable the lazy loaded is_vim implementation, I see behavior identical to the OP. It gives me the impression that smart-splits is being lazy loaded, even though I do not seem to have that option enabled. I have, however, added wezterm to my PATH. I've been driving myself crazy trying to solve, would really appreciate any help.

Neovim Version

NVIM v0.9.4
Build type: Release
LuaJIT 2.1.1700008891

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/usr/local/Cellar/neovim/0.9.4/share/nvim"

Multiplexer Version

wezterm 20230712-072601-f4abf8fd

Minimal Configuration to Reproduce

init.lua

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  {'VonHeikemen/lsp-zero.nvim', branch = 'v3.x'},
  {'mrjones2014/smart-splits.nvim'},
  {'williamboman/mason.nvim'},
  {'williamboman/mason-lspconfig.nvim'},
  {'neovim/nvim-lspconfig'},
  {'hrsh7th/cmp-nvim-lsp'},
  {'hrsh7th/nvim-cmp'},
  {'L3MON4D3/LuaSnip'},
  {'nvim-treesitter/nvim-treesitter'},
  {'nvim-treesitter/playground'},
  {'theprimeagen/harpoon'},
  {'mbbill/undotree'},
  {'tpope/vim-fugitive'},
  {'rose-pine/neovim', name = 'rose-pine'},
  {'nvim-telescope/telescope.nvim',
    tag = '0.1.5',
    dependencies = { 'nvim-lua/plenary.nvim' }
  }
})

require('smart-splits').setup({
  -- add any options here
})

-- IS_NVIM is not being set, so none of these mappings are working!
-- recommended mappings
-- resizing splits
-- these keymaps will also accept a range,
-- for example `10<A-h>` will `resize_left` by `(10 * config.default_amount)`
vim.keymap.set('n', '<A-h>', require('smart-splits').resize_left)
vim.keymap.set('n', '<A-j>', require('smart-splits').resize_down)
vim.keymap.set('n', '<A-k>', require('smart-splits').resize_up)
vim.keymap.set('n', '<A-l>', require('smart-splits').resize_right)
-- moving between splits
vim.keymap.set('n', '<C-h>', require('smart-splits').move_cursor_left)
vim.keymap.set('n', '<C-j>', require('smart-splits').move_cursor_down)
vim.keymap.set('n', '<C-k>', require('smart-splits').move_cursor_up)
vim.keymap.set('n', '<C-l>', require('smart-splits').move_cursor_right)
-- swapping buffers between windows
vim.keymap.set('n', '<leader><leader>h', require('smart-splits').swap_buf_left)
vim.keymap.set('n', '<leader><leader>j', require('smart-splits').swap_buf_down)
vim.keymap.set('n', '<leader><leader>k', require('smart-splits').swap_buf_up)
vim.keymap.set('n', '<leader><leader>l', require('smart-splits').swap_buf_right)

wezterm.lua

local w = require('wezterm')

-- https://wezfurlong.org/wezterm/config/lua/gui-events/gui-startup.html
local mux = w.mux

w.on('gui-startup', function(cmd)
  local tab, pane, window = mux.spawn_window(cmd or {})
  window:gui_window():maximize()
end)

-- This table will hold the configuration.
local config = {}

-- In newer versions of wezterm, use the config_builder which will
-- help provide clearer error messages
if w.config_builder then
  config = w.config_builder()
end

-- if you are *NOT* lazy-loading smart-splits.nvim (recommended)
local function is_vim(pane)
  -- this is set by the plugin, and unset on ExitPre in Neovim
  return pane:get_user_vars().IS_NVIM == 'true'
end

--local function is_vim(pane)
--  -- This gsub is equivalent to POSIX basename(3)
--  -- Given "/foo/bar" returns "bar"
--  -- Given "c:\\foo\\bar" returns "bar"
--  local process_name = string.gsub(pane:get_foreground_process_name(), '(.*[/\\])(.*)', '%2')
--  return process_name == 'nvim' or process_name == 'vim'
--end

local direction_keys = {
  Left = 'h',
  Down = 'j',
  Up = 'k',
  Right = 'l',
  -- reverse lookup
  h = 'Left',
  j = 'Down',
  k = 'Up',
  l = 'Right',
}

local function split_nav(resize_or_move, key)
  return {
    key = key,
    mods = resize_or_move == 'resize' and 'META' or 'CTRL',
    action = w.action_callback(function(win, pane)
      -- debug is_vim
      w.log_info(is_vim(pane))
      if is_vim(pane) then
        -- pass the keys through to vim/nvim
        win:perform_action({
          SendKey = { key = key, mods = resize_or_move == 'resize' and 'META' or 'CTRL' },
        }, pane)
      else
        if resize_or_move == 'resize' then
          win:perform_action({ AdjustPaneSize = { direction_keys[key], 3 } }, pane)
        else
          win:perform_action({ ActivatePaneDirection = direction_keys[key] }, pane)
        end
      end
    end),
  }
end

config.enable_tab_bar = false
config.keys = {
  {
    key = 't',
    mods = 'CMD|SHIFT',
    action = w.action.ShowTabNavigator,
  },
  -- move between split panes
  split_nav('move', 'h'),
  split_nav('move', 'j'),
  split_nav('move', 'k'),
  split_nav('move', 'l'),
  -- resize panes
  split_nav('resize', 'h'),
  split_nav('resize', 'j'),
  split_nav('resize', 'k'),
  split_nav('resize', 'l'),
}

return config
mrjones2014 commented 9 months ago

First thing to check would be, is the plugin detecting the mux environment correctly? :lua=require('smart-splits.config').multiplexer_integration should output wezterm.

JolliestJames commented 9 months ago

I do get nil instead of wezterm when I run :lua=require('smart-splits.config').multiplexer_integration, I forgot to mention that I tried printing it out in my init.lua and get nil there, too. I also tried manually setting the multiplexer to wezterm in the smart-splits setup config, but this didn't change the behavior. Is there something obvious I can try to help the plugin find wezterm? Sorry, I'm still pretty new to neovim.

mrjones2014 commented 9 months ago

Hmm, it looks like currently, the Wezterm user vars depend on being able to detect if Wezterm is the default, these lines of code in particular. I can put up a PR to always set it in Wezterm, which should solve your issue.

I'll also add some additional logging during startup, because I'd also like to diagnose why auto-detection isn't working for you.

mrjones2014 commented 9 months ago

@JolliestJames can you see if #139 fixes your issue? Can you also run that branch and then run :SmartSplitsLog and see if you have any log lines starting with Auto-detected multiplexer back-end:

JolliestJames commented 9 months ago

That did it, thanks! Interestingly, I have old logs that said wezterm was being detected at one point, not sure what happened there. But in swapping between the two branches, the fix is definitely working where master isn't. It's also possible I've discovered a different bug. When I'm inside of a file, and have configured at_edge = 'split', everything works as expected and a new pane is opened. But if I'm inside netrw and try to split, I can only split left and navigation does not work, I'm trapped inside of netrw. at_edge = 'wrap' works just fine, though.

[Thu Dec  7 12:58:02 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Thu Dec  7 13:00:40 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Thu Dec  7 13:00:40 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:49:24 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:50:05 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:51:22 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:53:18 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:56:27 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:59:11 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 09:59:47 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 10:01:53 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 10:03:06 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
[Mon Dec 11 10:03:45 2023][smart-splits.nvim] Auto-detected multiplexer back-end: wezterm
mrjones2014 commented 9 months ago

When I'm inside of a file, and have configured at_edge = 'split', everything works as expected and a new pane is opened. But if I'm inside netrw and try to split, I can only split left and navigation does not work, I'm trapped inside of netrw. at_edge = 'wrap' works just fine, though.

Are you sure that netrw isn't remapping one of your keymaps?

JolliestJames commented 9 months ago

That was it, setting a different binding solves it. Thanks again!

mrjones2014 commented 9 months ago

Glad we got it all worked out!