commit f7ffd807349efa0948af491936924c53149ed957 Author: Zacharias-Brohn Date: Mon Dec 15 22:53:52 2025 +0100 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..19cd86f --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# zterm-navigator + +Seamless navigation between Neovim windows and ZTerm terminal panes. + +## Features + +- Navigate between Neovim splits using `Alt+Arrow` keys +- When there's no Neovim window in that direction, automatically navigate to the next ZTerm pane +- Works bidirectionally - you can navigate from shell to Neovim and back + +## Requirements + +- [ZTerm](https://github.com/yourusername/zterm) terminal emulator +- Neovim 0.7+ + +## Installation + +### Using lazy.nvim + +```lua +{ + "yourusername/zterm-navigator", + config = function() + require("zterm-navigator").setup() + end, +} +``` + +### Using packer.nvim + +```lua +use { + "yourusername/zterm-navigator", + config = function() + require("zterm-navigator").setup() + end, +} +``` + +## Configuration + +```lua +require("zterm-navigator").setup({ + -- Default keybindings (set to false to disable) + left = "", + right = "", + up = "", + down = "", +}) +``` + +## ZTerm Configuration + +Make sure your ZTerm config (`~/.config/zterm/config.json`) has matching keybindings: + +```json +{ + "keybindings": { + "focus_pane_up": "alt+up", + "focus_pane_down": "alt+down", + "focus_pane_left": "alt+left", + "focus_pane_right": "alt+right" + }, + "pass_keys_to_programs": ["nvim", "vim"] +} +``` + +## How It Works + +1. When you press `Alt+Arrow` in Neovim: + - The plugin checks if there's a Neovim window in that direction + - If yes, it navigates to that window using `wincmd` + - If no, it sends an OSC 51 escape sequence to ZTerm + +2. ZTerm receives the OSC sequence and navigates to the neighboring pane + +3. When you press `Alt+Arrow` in a shell (non-Neovim): + - ZTerm checks `pass_keys_to_programs` - since it's not Neovim, ZTerm handles navigation directly + +## Protocol + +The plugin communicates with ZTerm using OSC (Operating System Command) sequences: + +``` +OSC 51 ; navigate ; BEL +``` + +Where `` is one of: `up`, `down`, `left`, `right`. + +## License + +MIT diff --git a/lua/zterm-navigator/init.lua b/lua/zterm-navigator/init.lua new file mode 100644 index 0000000..78d4c3c --- /dev/null +++ b/lua/zterm-navigator/init.lua @@ -0,0 +1,120 @@ +-- zterm-navigator: Seamless navigation between Neovim windows and ZTerm panes +-- +-- When you press the navigation key (e.g., Alt+Arrow): +-- - If there's a Neovim window in that direction, navigate to it +-- - If not, send an OSC sequence to ZTerm to navigate to the next pane + +local M = {} + +-- Default configuration +M.config = { + -- Key mappings for navigation + -- Set to false to disable a direction + left = "", + right = "", + up = "", + down = "", +} + +-- Send OSC 51 command to ZTerm for pane navigation +local function zterm_navigate(direction) + -- OSC 51;navigate; ST + -- Using BEL (\007) as string terminator for better compatibility + local osc = string.format("\027]51;navigate;%s\007", direction) + io.write(osc) + io.flush() +end + +-- Get the window number in a given direction, or 0 if none exists +local function get_window_in_direction(direction) + local dir_char = ({ + left = "h", + right = "l", + up = "k", + down = "j", + })[direction] + + if not dir_char then + return 0 + end + + -- Get current window number + local current_winnr = vim.fn.winnr() + -- Get window number in the specified direction + local target_winnr = vim.fn.winnr(dir_char) + + -- If winnr() returns the same window, there's no window in that direction + if target_winnr == current_winnr then + return 0 + end + + return target_winnr +end + +-- Navigate in the given direction +-- First tries to move within Neovim, falls back to ZTerm pane navigation +local function navigate(direction) + local target_win = get_window_in_direction(direction) + + if target_win ~= 0 then + -- There's a Neovim window in that direction, navigate to it + vim.cmd("wincmd " .. ({ + left = "h", + right = "l", + up = "k", + down = "j", + })[direction]) + else + -- No Neovim window, ask ZTerm to navigate + zterm_navigate(direction) + end +end + +-- Public navigation functions +function M.navigate_left() + navigate("left") +end + +function M.navigate_right() + navigate("right") +end + +function M.navigate_up() + navigate("up") +end + +function M.navigate_down() + navigate("down") +end + +-- Setup function to configure keymaps +function M.setup(opts) + -- Merge user options with defaults + opts = opts or {} + M.config = vim.tbl_deep_extend("force", M.config, opts) + + -- Set up keymaps + local keymap_opts = { noremap = true, silent = true } + + if M.config.left then + vim.keymap.set({"n", "t"}, M.config.left, M.navigate_left, + vim.tbl_extend("force", keymap_opts, { desc = "Navigate left (window/pane)" })) + end + + if M.config.right then + vim.keymap.set({"n", "t"}, M.config.right, M.navigate_right, + vim.tbl_extend("force", keymap_opts, { desc = "Navigate right (window/pane)" })) + end + + if M.config.up then + vim.keymap.set({"n", "t"}, M.config.up, M.navigate_up, + vim.tbl_extend("force", keymap_opts, { desc = "Navigate up (window/pane)" })) + end + + if M.config.down then + vim.keymap.set({"n", "t"}, M.config.down, M.navigate_down, + vim.tbl_extend("force", keymap_opts, { desc = "Navigate down (window/pane)" })) + end +end + +return M