diff options
| author | Pablu <me@pablu.de> | 2026-07-29 08:36:58 +0200 |
|---|---|---|
| committer | Pablu <me@pablu.de> | 2026-07-29 08:36:58 +0200 |
| commit | 75f2079ddf60f8901bd436e1cb908c189a8a9037 (patch) | |
| tree | 114877450da032bddfc648a044a0af64e9b58449 /dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/lua/herdr-splits/nav.lua | |
| parent | 3f66debcc79bd995797c029338de36a0b463f20f (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.lua | 206 |
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 |
