nvim + pi / bi-directional communication
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273local M = {}
local config = require('pi-nvim.config')
vim.api.nvim_set_hl(0, 'PiNvimNormal', { link = 'Normal', default = true })
---@class pi.Terminal---@field buf number|nil Buffer number---@field win number|nil Window number---@field job number|nil Job ID
---@type pi.Terminal|nillocal current_terminal = nil
-- Track if we were in terminal insert mode before suspendlocal restore_insert_on_resume = false
---@return pi.Terminal|nilfunction M.get_current() return current_terminalend
---@return stringlocal function get_layout() local cfg = config.get() local layout = cfg.win.layout if layout == 'auto' then return vim.o.columns >= cfg.win.width_threshold and 'right' or 'bottom' end return layoutend
---@param buf number---@return numberlocal function open_win(buf) local cfg = config.get() local layout = get_layout() local opts = { win = -1, style = 'minimal' }
if layout == 'right' then opts.split = 'right' opts.width = cfg.win.width elseif layout == 'left' then opts.split = 'left' opts.width = cfg.win.width elseif layout == 'bottom' then opts.split = 'below' opts.height = cfg.win.height elseif layout == 'top' then opts.split = 'above' opts.height = cfg.win.height elseif layout == 'float' then opts.relative = 'editor' opts.width = math.floor(vim.o.columns * 0.9) opts.height = math.floor(vim.o.lines * 0.9) opts.row = math.floor(vim.o.lines * 0.05) opts.col = math.floor(vim.o.columns * 0.05) end
return vim.api.nvim_open_win(buf, true, opts)end
---@param win numberlocal function set_win_options(win) local wo = { winhighlight = 'Normal:PiNvimNormal,NormalNC:PiNvimNormal,EndOfBuffer:PiNvimNormal,SignColumn:PiNvimNormal', number = false, relativenumber = false, signcolumn = 'no', spell = false, wrap = false, sidescrolloff = 0, } for k, v in pairs(wo) do vim.api.nvim_set_option_value(k, v, { win = win }) end vim.wo[win].winfixwidth = trueend
---@param terminal pi.Terminallocal function setup_keymaps(terminal) if not terminal.buf then return end
local cfg = config.get() local keys = cfg.win.keys
if keys.close then local map = keys.close vim.keymap.set(map.mode or 'n', map[1], function() M.close(terminal) end, { buffer = terminal.buf, desc = map.desc }) end
if keys.stopinsert then local map = keys.stopinsert vim.keymap.set(map.mode or 't', map[1], function() vim.cmd.stopinsert() if cfg.win.focus_source_on_stopinsert then local source = require('pi-nvim.actions.source') local win = source.get_win() if win and vim.api.nvim_win_is_valid(win) then vim.api.nvim_set_current_win(win) end end end, { buffer = terminal.buf, desc = map.desc }) end
if keys.suspend then local map = keys.suspend vim.keymap.set(map.mode or 't', map[1], function() -- Remember we were in terminal insert mode restore_insert_on_resume = true -- Exit terminal mode and let Neovim handle suspend vim.cmd.stopinsert() vim.cmd.suspend() end, { buffer = terminal.buf, desc = map.desc }) end
if keys.picker then local map = keys.picker vim.keymap.set(map.mode or 't', map[1], function() require('pi-nvim.cli.picker').show(terminal) end, { buffer = terminal.buf, desc = map.desc }) endend
---@param cmd string[]---@return pi.Terminalfunction M.create(cmd) if current_terminal then M.close(current_terminal) end
local buf = vim.api.nvim_create_buf(false, true) vim.bo[buf].filetype = 'pi_nvim' vim.bo[buf].swapfile = false vim.b[buf].miniindentscope_disable = true
---@type pi.Terminal local terminal = { buf = buf } current_terminal = terminal
setup_keymaps(terminal)
local win = open_win(buf) terminal.win = win set_win_options(win)
vim.api.nvim_win_call(win, function() local env = vim.tbl_extend('force', {}, vim.uv.os_environ(), { NVIM = vim.v.servername, NVIM_LISTEN_ADDRESS = false, TERM = 'xterm-256color', }) for k, v in pairs(env) do if v == false then env[k] = nil end end
terminal.job = vim.fn.jobstart(cmd, { term = true, clear_env = true, env = next(env) and env or nil, on_exit = function() vim.schedule(function() if current_terminal == terminal then M.close(terminal) end end) end, })
if terminal.job <= 0 then vim.schedule(function() M.close(terminal) vim.notify('[pi-nvim] Failed to start Pi process', vim.log.levels.ERROR) end) return end end)
vim.api.nvim_set_current_win(win) vim.cmd.startinsert()
vim.api.nvim_create_autocmd('TermClose', { buffer = buf, callback = function() vim.schedule(function() M.close(terminal) end) end, })
vim.api.nvim_create_autocmd('VimResume', { buffer = buf, callback = function() if restore_insert_on_resume then restore_insert_on_resume = false -- Only restore if terminal is still open and focused if M.is_open(terminal) and vim.api.nvim_get_current_buf() == buf then vim.cmd.startinsert() end end end, })
vim.api.nvim_create_autocmd('BufEnter', { buffer = buf, callback = function() vim.cmd.startinsert() end, })
return terminalend
---@param terminal pi.Terminalfunction M.show(terminal) if not terminal.buf or not vim.api.nvim_buf_is_valid(terminal.buf) then return end if terminal.win and vim.api.nvim_win_is_valid(terminal.win) then vim.api.nvim_set_current_win(terminal.win) else terminal.win = open_win(terminal.buf) set_win_options(terminal.win) end vim.cmd.startinsert()end
---@param terminal pi.Terminalfunction M.close(terminal) if terminal.win and vim.api.nvim_win_is_valid(terminal.win) then vim.api.nvim_win_close(terminal.win, true) end if terminal.job then vim.fn.jobstop(terminal.job) end if terminal.buf and vim.api.nvim_buf_is_valid(terminal.buf) then vim.api.nvim_buf_delete(terminal.buf, { force = true }) end if current_terminal == terminal then current_terminal = nil endend
---@param terminal pi.Terminal---@return booleanfunction M.is_open(terminal) return terminal.win and vim.api.nvim_win_is_valid(terminal.win) or falseend
---@param terminal pi.Terminalfunction M.focus(terminal) if M.is_open(terminal) then vim.api.nvim_set_current_win(terminal.win) vim.cmd.startinsert() endend
---@param terminal pi.Terminal---@param text stringfunction M.send(terminal, text) if terminal.job then vim.api.nvim_chan_send(terminal.job, text) endend
return M