From 9dc03ad047b2bde328e7791af4f9c85105084ae3 Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 16 Dec 2025 21:51:10 +0100 Subject: [PATCH] powerline? --- lua/zterm-navigator/init.lua | 265 +++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) diff --git a/lua/zterm-navigator/init.lua b/lua/zterm-navigator/init.lua index 78d4c3c..5280b45 100644 --- a/lua/zterm-navigator/init.lua +++ b/lua/zterm-navigator/init.lua @@ -3,9 +3,17 @@ -- 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 +-- +-- Also provides vim-tpipeline-like statusline integration: +-- - Sends neovim's statusline to ZTerm's terminal statusline +-- - Updates on mode change, buffer change, cursor move, etc. local M = {} +-- Track if statusline integration is active +M._statusline_enabled = false +M._statusline_augroup = nil + -- Default configuration M.config = { -- Key mappings for navigation @@ -14,6 +22,17 @@ M.config = { right = "", up = "", down = "", + + -- Statusline integration (vim-tpipeline style) + statusline = { + -- Enable statusline integration (auto-detected if nil) + enabled = nil, + -- Use neovim's statusline setting, or provide a custom one + -- If nil, uses vim.o.statusline + statusline = nil, + -- Disable neovim's builtin statusline when sending to ZTerm + hide_nvim_statusline = true, + }, } -- Send OSC 51 command to ZTerm for pane navigation @@ -87,6 +106,238 @@ function M.navigate_down() navigate("down") end +-- ============================================================================ +-- Statusline Integration (vim-tpipeline style) +-- ============================================================================ + +-- Check if we're running inside ZTerm +local function is_zterm() + local term = vim.env.TERM or "" + local term_program = vim.env.TERM_PROGRAM or "" + -- ZTerm sets TERM to "zterm" or similar + return term:match("zterm") or term_program:match("[Zz]term") +end + +-- Send statusline content to ZTerm via OSC 51 +local function send_statusline(content) + -- OSC 51;statusline; ST + local osc = string.format("\027]51;statusline;%s\007", content or "") + io.write(osc) + io.flush() +end + +-- Clear the ZTerm statusline (restore default) +local function clear_statusline() + send_statusline("") +end + +-- Get the rendered statusline with ANSI escape codes +local function get_rendered_statusline() + -- Get the statusline string to evaluate + local stl = M.config.statusline.statusline or vim.o.statusline + + -- If empty, try to get a sensible default + if not stl or stl == "" then + -- Build a simple default statusline + stl = " %f %m%r%h%w %= %y [%l,%c] %P " + end + + -- Use nvim_eval_statusline to get the rendered content with highlights + local ok, result = pcall(vim.api.nvim_eval_statusline, stl, { + winid = vim.api.nvim_get_current_win(), + highlights = true, + fillchar = " ", + }) + + if not ok then + return nil + end + + -- Convert highlights to ANSI escape codes + local output = {} + local str = result.str + local highlights = result.highlights or {} + + -- Sort highlights by start position + table.sort(highlights, function(a, b) return a.start < b.start end) + + local pos = 1 + for i, hl in ipairs(highlights) do + -- Add any text before this highlight + if hl.start > pos then + -- No highlight for this segment, use reset + table.insert(output, "\027[0m") + table.insert(output, str:sub(pos, hl.start)) + end + + -- Determine the end of this highlight + local hl_end + if i < #highlights then + hl_end = highlights[i + 1].start + else + hl_end = #str + end + + -- Get the highlight colors + local hl_info = vim.api.nvim_get_hl(0, { name = hl.group, link = false }) + local ansi = {} + + -- Foreground color + if hl_info.fg then + local r = bit.rshift(bit.band(hl_info.fg, 0xFF0000), 16) + local g = bit.rshift(bit.band(hl_info.fg, 0x00FF00), 8) + local b = bit.band(hl_info.fg, 0x0000FF) + table.insert(ansi, string.format("38;2;%d;%d;%d", r, g, b)) + end + + -- Background color + if hl_info.bg then + local r = bit.rshift(bit.band(hl_info.bg, 0xFF0000), 16) + local g = bit.rshift(bit.band(hl_info.bg, 0x00FF00), 8) + local b = bit.band(hl_info.bg, 0x0000FF) + table.insert(ansi, string.format("48;2;%d;%d;%d", r, g, b)) + end + + -- Bold + if hl_info.bold then + table.insert(ansi, "1") + end + + -- Italic + if hl_info.italic then + table.insert(ansi, "3") + end + + -- Underline + if hl_info.underline then + table.insert(ansi, "4") + end + + -- Build the ANSI sequence + if #ansi > 0 then + table.insert(output, "\027[" .. table.concat(ansi, ";") .. "m") + else + table.insert(output, "\027[0m") + end + + -- Add the highlighted text + table.insert(output, str:sub(hl.start + 1, hl_end)) + pos = hl_end + 1 + end + + -- Add any remaining text + if pos <= #str then + table.insert(output, "\027[0m") + table.insert(output, str:sub(pos)) + end + + -- Reset at the end + table.insert(output, "\027[0m") + + return table.concat(output) +end + +-- Update the ZTerm statusline +local function update_statusline() + if not M._statusline_enabled then + return + end + + local content = get_rendered_statusline() + if content then + send_statusline(content) + end +end + +-- Saved laststatus value to restore on disable +M._saved_laststatus = nil + +-- Enable statusline integration +function M.enable_statusline() + if M._statusline_enabled then + return + end + + M._statusline_enabled = true + + -- Hide neovim's statusline if configured + if M.config.statusline.hide_nvim_statusline then + M._saved_laststatus = vim.o.laststatus + vim.o.laststatus = 0 + end + + -- Create autocommands for statusline updates + M._statusline_augroup = vim.api.nvim_create_augroup("ZTermStatusline", { clear = true }) + + -- Events that should trigger a statusline update + local events = { + "ModeChanged", -- Mode changes (normal, insert, visual, etc.) + "BufEnter", -- Entering a buffer + "BufWritePost", -- After writing a file + "FileChangedShellPost", + "WinEnter", -- Entering a window + "CursorMoved", -- Cursor moved (for position info) + "CursorMovedI", -- Cursor moved in insert mode + "DiagnosticChanged", -- LSP diagnostics changed + } + + vim.api.nvim_create_autocmd(events, { + group = M._statusline_augroup, + callback = function() + -- Defer slightly to batch rapid updates + vim.defer_fn(update_statusline, 10) + end, + }) + + -- Clear statusline on exit + vim.api.nvim_create_autocmd("VimLeavePre", { + group = M._statusline_augroup, + callback = function() + clear_statusline() + end, + }) + + -- Initial update + update_statusline() +end + +-- Disable statusline integration +function M.disable_statusline() + if not M._statusline_enabled then + return + end + + M._statusline_enabled = false + + -- Remove autocommands + if M._statusline_augroup then + vim.api.nvim_del_augroup_by_id(M._statusline_augroup) + M._statusline_augroup = nil + end + + -- Restore neovim's statusline + if M._saved_laststatus then + vim.o.laststatus = M._saved_laststatus + M._saved_laststatus = nil + end + + -- Clear the ZTerm statusline + clear_statusline() +end + +-- Toggle statusline integration +function M.toggle_statusline() + if M._statusline_enabled then + M.disable_statusline() + else + M.enable_statusline() + end +end + +-- ============================================================================ +-- Setup +-- ============================================================================ + -- Setup function to configure keymaps function M.setup(opts) -- Merge user options with defaults @@ -115,6 +366,20 @@ function M.setup(opts) vim.keymap.set({"n", "t"}, M.config.down, M.navigate_down, vim.tbl_extend("force", keymap_opts, { desc = "Navigate down (window/pane)" })) end + + -- Set up statusline integration + local statusline_enabled = M.config.statusline.enabled + if statusline_enabled == nil then + -- Auto-detect: enable if running in ZTerm + statusline_enabled = is_zterm() + end + + if statusline_enabled then + -- Defer to ensure neovim is fully loaded + vim.defer_fn(function() + M.enable_statusline() + end, 100) + end end return M