diff --git a/README.md b/README.md index 2688eba..a10a4f4 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,49 @@ return { ## Lazy.nvim +### Basic setup + +```lua +{ + "devxplay/herdr.nvim", +} +``` + +### LazyVim / NvChad / Distro compatibility + +LazyVim and similar Neovim distributions set default `Ctrl+h/j/k/l` keymaps for window navigation that load after plugins. To ensure herdr.nvim's keymaps take precedence, use this configuration: + ```lua { "devxplay/herdr.nvim", + lazy = false, + priority = 1000, + config = function() + require("herdr").setup({ + set_keymaps = false, -- We'll set them after LazyVim loads + }) + + -- Set keymaps after LazyVim's defaults + vim.api.nvim_create_autocmd("VimEnter", { + callback = function() + vim.defer_fn(function() + vim.keymap.set("n", "", function() require("herdr.navigator").navigate("left") end, { desc = "Navigate left" }) + vim.keymap.set("n", "", function() require("herdr.navigator").navigate("down") end, { desc = "Navigate down" }) + vim.keymap.set("n", "", function() require("herdr.navigator").navigate("up") end, { desc = "Navigate up" }) + vim.keymap.set("n", "", function() require("herdr.navigator").navigate("right") end, { desc = "Navigate right" }) + end, 100) + end, + }) + end, } ``` +This configuration: +- Loads the plugin immediately (`lazy = false`) +- Loads early with high priority (`priority = 1000`) +- Waits for Neovim to finish starting, then sets the keymaps after a short delay +- Ensures herdr.nvim's navigation keymaps override the distribution's defaults + ## Herdr config Install `herdr-navigator` somewhere on your `PATH` for Herdr shell keybindings: diff --git a/lua/herdr/navigator.lua b/lua/herdr/navigator.lua index 6f05866..b57aad5 100644 --- a/lua/herdr/navigator.lua +++ b/lua/herdr/navigator.lua @@ -104,7 +104,21 @@ local function run_helper(args) end local function pane_id() - return vim.env.HERDR_ACTIVE_PANE_ID or vim.env.HERDR_PANE_ID + local id = vim.env.HERDR_ACTIVE_PANE_ID or vim.env.HERDR_PANE_ID + if id and id ~= "" then + return id + end + + local handle = io.popen("herdr pane current 2>/dev/null") + if handle then + local result = handle:read("*a") + handle:close() + local json = vim.json and vim.json.decode(result) or vim.fn.json_decode(result) + if json and json.result and json.result.pane then + return json.result.pane.pane_id + end + end + return nil end local function nvim_panes_dir() diff --git a/src/main.rs b/src/main.rs index 10d59b8..49abb07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -205,16 +205,21 @@ fn active_workspace_and_tab() -> Result<(Value, Value), String> { Ok((workspace, tab)) } -fn pane_public_mapping(workspace: &Value, tab: &Value) -> Result, String> { +fn pane_public_mapping(workspace: &Value, _tab: &Value) -> Result, String> { let workspace_id = workspace .get("id") .and_then(Value::as_str) .ok_or_else(|| "active Herdr workspace has no id".to_string())?; - let panes = tab + let panes = _tab .get("panes") .and_then(Value::as_object) .ok_or_else(|| "active Herdr tab has no panes object".to_string())?; + let public_pane_numbers = workspace + .get("public_pane_numbers") + .and_then(Value::as_object) + .ok_or_else(|| "active Herdr workspace has no public_pane_numbers".to_string())?; + let mut internal_ids = panes .keys() .filter_map(|pane_id| pane_id.parse::().ok()) @@ -223,8 +228,13 @@ fn pane_public_mapping(workspace: &Value, tab: &Value) -> Result Result<(), String> { + result( + "pane.focus_direction", + json!({ + "pane_id": pane_id, + "direction": direction, + }), + )?; + Ok(()) +} + fn focus_pane(pane_id: &str) -> Result<(), String> { let seq = now_ns(); let normalized = pane_id.replace(['-', ':'], "_"); @@ -578,6 +599,12 @@ fn focus_adjacent(direction: &str, current_pane_id: Option) -> Result Result { Ok(dir.join(&public_pane_id).exists()) } -fn ctrl_text(direction: &str) -> Option<&'static str> { +fn ctrl_key(direction: &str) -> Option<&'static str> { match direction { - "left" => Some("\u{0008}"), - "down" => Some("\u{000a}"), - "up" => Some("\u{000b}"), - "right" => Some("\u{000c}"), + "left" => Some("ctrl+h"), + "down" => Some("ctrl+j"), + "up" => Some("ctrl+k"), + "right" => Some("ctrl+l"), _ => None, } } fn send_ctrl_to_pane(pane_id: &str, direction: &str) -> Result<(), String> { - let Some(text) = ctrl_text(direction) else { + let Some(key) = ctrl_key(direction) else { return Err(format!("unsupported direction {direction}")); }; result( - "pane.send_text", + "pane.send_keys", json!({ "pane_id": pane_id, - "text": text, + "keys": [key], }), )?; Ok(()) @@ -825,13 +852,13 @@ fn run() -> Result { match (command, direction) { ("register", _) => register(), ("release", _) => release(), - ("focus", Some(direction)) if ctrl_text(direction).is_some() => { + ("focus", Some(direction)) if ctrl_key(direction).is_some() => { focus_adjacent(direction, None) } ("split", Some(direction)) if matches!(direction, "right" | "down") => { split_pane(direction) } - ("dispatch", Some(direction)) if ctrl_text(direction).is_some() => dispatch(direction), + ("dispatch", Some(direction)) if ctrl_key(direction).is_some() => dispatch(direction), _ => { usage(); Ok(2)