aboutsummaryrefslogtreecommitdiff
path: root/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
diff options
context:
space:
mode:
authorPablu <me@pablu.de>2026-07-29 08:36:58 +0200
committerPablu <me@pablu.de>2026-07-29 08:36:58 +0200
commit75f2079ddf60f8901bd436e1cb908c189a8a9037 (patch)
tree114877450da032bddfc648a044a0af64e9b58449 /dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
parent3f66debcc79bd995797c029338de36a0b463f20f (diff)
Add jj and herdr
Diffstat (limited to 'dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua')
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua206
1 files changed, 206 insertions, 0 deletions
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
new file mode 100644
index 0000000..56ecca3
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua
@@ -0,0 +1,206 @@
+---Navigation logic: seamless movement between Neovim splits and Herdr panes.
+---@class HerdrSplitsNav
+local M = {}
+
+local config = require('herdr-splits.config')
+local herdr = require('herdr-splits.herdr')
+local win = require('herdr-splits.win')
+
+---Treat the current window as a sidebar that should not be navigated through.
+---Combines the configured ignore lists with the embedded-float heuristic.
+---@return boolean
+local function is_sidebar()
+ return win.is_ignored_or_preview() or win.is_embedded_floating_window()
+end
+
+---Split a new Neovim window using the user's placement preferences.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+local function split_edge(direction)
+ if direction == 'left' or direction == 'right' then
+ vim.cmd('vsp')
+ else
+ vim.cmd('sp')
+ end
+end
+
+---Move cursor between Neovim splits, falling through to Herdr at edges.
+---This is the core navigation function.
+---@param direction '"left"'|'"right"'|'"up"'|'"down"'
+---@param opts table|nil { same_row: boolean|nil, at_edge: string|function|nil }
+function M.move_cursor(direction, opts)
+ local same_row = config.move_cursor_same_row
+ local at_edge_behavior = config.at_edge
+
+ if type(opts) == 'table' then
+ if opts.same_row ~= nil then
+ same_row = opts.same_row
+ end
+ if opts.at_edge ~= nil then
+ at_edge_behavior = opts.at_edge
+ end
+ end
+
+ -- Handle floating windows: just forward to Herdr,
+ -- EXCEPT for embedded sidebars (snacks/neo-tree float/aerial float):
+ -- those should stay in Neovim so the user can keep interacting
+ -- with the picker.
+ if win.is_floating() and not win.is_embedded_floating_window() then
+ herdr.focus_pane(direction)
+ return
+ end
+
+ local dir_key = win.dir_keys[direction]
+ local offset = vim.fn.winline() + vim.api.nvim_win_get_position(0)[1]
+
+ -- Save current window to detect if wincmd changes it
+ local prev_win = vim.api.nvim_get_current_win()
+
+ -- Try moving within Neovim first
+ local will_wrap = false
+ local count = vim.v.count1
+ local target_winnr = vim.fn.winnr(count .. dir_key)
+ if count > 1 then
+ local prev_winnr = vim.fn.winnr((count - 1) .. dir_key)
+ will_wrap = target_winnr == prev_winnr
+ else
+ will_wrap = target_winnr == vim.fn.winnr()
+ end
+
+ -- Command-line window (q:, q/, q?): Neovim forbids all window commands
+ -- (E11). Never wincmd; at a Neovim screen edge, delegate to Herdr
+ -- (subprocess-safe, does not close the cmdwin); otherwise silent no-op.
+ -- Mirrors smart-splits.nvim PR #464.
+ if win.is_command_line_window() then
+ if will_wrap and herdr.is_in_session() then
+ local at_herdr_edge = herdr.current_pane_at_edge(direction)
+ if at_herdr_edge == false then
+ herdr.focus_pane(direction)
+ elseif at_herdr_edge == true and herdr.nav_at_edge() ~= 'stop' then
+ herdr.focus_pane(win.reverse_direction[direction])
+ end
+ end
+ return
+ end
+
+ -- Execute the wincmd
+ if will_wrap and count == 1 then
+ vim.cmd('wincmd ' .. dir_key)
+ else
+ vim.cmd(count .. 'wincmd ' .. dir_key)
+ end
+
+ if vim.api.nvim_get_current_win() ~= prev_win then
+ -- Moved within Neovim. Restore same-row if configured.
+ if (direction == 'left' or direction == 'right') and same_row then
+ local row = offset - vim.api.nvim_win_get_position(0)[1]
+ if row > 0 then
+ vim.cmd('normal! ' .. row .. 'H')
+ end
+ end
+ return
+ end
+
+ -- We're at a Neovim edge. Try to cross into Herdr.
+ if not herdr.is_in_session() then
+ if will_wrap and count == 1 then
+ local sidebar = is_sidebar()
+ if type(at_edge_behavior) == 'function' then
+ at_edge_behavior({
+ direction = direction,
+ split = function() split_edge(direction) end,
+ is_sidebar = sidebar,
+ wrap = function()
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end,
+ })
+ elseif at_edge_behavior == 'stop' then
+ return
+ elseif at_edge_behavior == 'split' then
+ if not sidebar then
+ split_edge(direction)
+ end
+ else -- 'wrap' (default)
+ -- Wrap is allowed even from filetype-based sidebars (dbui, neo-tree,
+ -- ...) so the user can leave them; only embedded floats stay gated.
+ if not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ end
+ end
+ return
+ end
+
+ -- Check zoom state: unzoom first, then retry Neovim navigation.
+ -- Must happen BEFORE the at_herdr_edge check — when zoomed, the pane fills
+ -- the screen so herdr reports it as being at every edge, making
+ -- at_herdr_edge useless until we unzoom.
+ if herdr.unzoom_enabled() and herdr.current_pane_is_zoomed() then
+ herdr.unzoom()
+ -- Retry wincmd — other Neovim splits may now be visible
+ vim.cmd('wincmd ' .. dir_key)
+ if vim.api.nvim_get_current_win() ~= prev_win then
+ if (direction == 'left' or direction == 'right') and same_row then
+ local row = offset - vim.api.nvim_win_get_position(0)[1]
+ if row > 0 then
+ vim.cmd('normal! ' .. row .. 'H')
+ end
+ end
+ return
+ end
+ -- Still at edge after unzoom; fall through to Herdr edge check.
+ end
+
+ -- Check if we're at the Herdr edge too
+ local at_herdr_edge = herdr.current_pane_at_edge(direction)
+ if at_herdr_edge == nil then
+ if will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ return
+ end
+
+ if not at_herdr_edge then
+ -- There's a Herdr pane in this direction. Cross the boundary.
+ local moved = herdr.focus_pane(direction)
+ if not moved and will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ return
+ end
+
+ -- At both Neovim AND Herdr edges (no herdr pane to cross into).
+ -- Apply at_edge behavior. (Any needed unzoom already happened above.)
+ if type(at_edge_behavior) == 'function' then
+ at_edge_behavior({
+ direction = direction,
+ split = function() split_edge(direction) end,
+ is_sidebar = is_sidebar(),
+ wrap = function()
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end,
+ })
+ elseif at_edge_behavior == 'stop' then
+ return
+ elseif at_edge_behavior == 'split' then
+ if not is_sidebar() then
+ split_edge(direction)
+ end
+ else -- 'wrap' (default)
+ -- Wrap is allowed from filetype-based sidebars (dbui, neo-tree, ...) so
+ -- you can leave them at an edge; only embedded floating overlays stay gated.
+ if will_wrap and count == 1 and not win.is_embedded_floating_window() then
+ -- Wrap to the opposite side. If a Herdr pane exists there AND nav_at_edge
+ -- allows wrap-across-boundary (the default), cross into it; otherwise
+ -- wrap within Neovim. nav_at_edge=stop keeps the wrap inside Neovim.
+ if herdr.nav_at_edge() ~= 'stop'
+ and herdr.current_pane_at_edge(win.reverse_direction[direction]) == false
+ then
+ herdr.focus_pane(win.reverse_direction[direction])
+ else
+ vim.cmd('wincmd ' .. win.dir_keys_reverse[direction])
+ end
+ end
+ end
+end
+
+return M