396 lines
13 KiB
Lua
396 lines
13 KiB
Lua
local M = {}
|
|
|
|
local progress_win = nil
|
|
local progress_buf = nil
|
|
local job_id = nil
|
|
|
|
function M.show_progress(msg)
|
|
if progress_win and vim.api.nvim_win_is_valid(progress_win) then
|
|
local lines = vim.split(msg, "\n")
|
|
vim.api.nvim_buf_set_lines(progress_buf, -1, -1, false, lines)
|
|
local line_count = vim.api.nvim_buf_line_count(progress_buf)
|
|
vim.api.nvim_win_set_cursor(progress_win, {line_count, 0})
|
|
return
|
|
end
|
|
|
|
progress_buf = vim.api.nvim_create_buf(false, true)
|
|
local lines = vim.split(msg, "\n")
|
|
vim.api.nvim_buf_set_lines(progress_buf, 0, -1, false, lines)
|
|
|
|
local width = math.min(math.floor(vim.o.columns * 0.4), 80)
|
|
local height = math.min(math.floor(vim.o.lines * 0.4), 20)
|
|
local row = 1
|
|
local col = vim.o.columns - width - 2
|
|
|
|
progress_win = vim.api.nvim_open_win(progress_buf, true, {
|
|
relative = 'editor',
|
|
width = width,
|
|
height = height,
|
|
row = row,
|
|
col = col,
|
|
style = 'minimal',
|
|
border = 'rounded',
|
|
title = ' Antigravity AI ',
|
|
title_pos = 'center'
|
|
})
|
|
|
|
local function close_win()
|
|
if job_id then
|
|
vim.fn.jobstop(job_id)
|
|
job_id = nil
|
|
end
|
|
if progress_win and vim.api.nvim_win_is_valid(progress_win) then
|
|
vim.api.nvim_win_close(progress_win, true)
|
|
progress_win = nil
|
|
end
|
|
end
|
|
vim.keymap.set('n', 'q', close_win, { buffer = progress_buf, noremap = true, silent = true })
|
|
vim.keymap.set('n', '<Esc>', close_win, { buffer = progress_buf, noremap = true, silent = true })
|
|
end
|
|
|
|
function M.close_progress()
|
|
if progress_win and vim.api.nvim_win_is_valid(progress_win) then
|
|
vim.api.nvim_win_close(progress_win, true)
|
|
progress_win = nil
|
|
end
|
|
end
|
|
|
|
local function send_udp(payload)
|
|
local uv = vim.uv or vim.loop
|
|
local client = uv.new_udp()
|
|
if client then
|
|
local ok, data = pcall(vim.fn.json_encode, payload)
|
|
if ok then
|
|
client:send(data, "127.0.0.1", 3002, function(err)
|
|
client:close()
|
|
end)
|
|
else
|
|
client:close()
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.prompt_user(prompt_text, options, request_id)
|
|
if options and #options > 0 then
|
|
vim.ui.select(options, { prompt = prompt_text }, function(choice, idx)
|
|
send_udp({
|
|
event = "agent_prompt_response",
|
|
request_id = request_id,
|
|
response = choice,
|
|
index = idx,
|
|
session_id = vim.v.servername
|
|
})
|
|
end)
|
|
else
|
|
vim.ui.input({ prompt = prompt_text }, function(input)
|
|
send_udp({
|
|
event = "agent_prompt_response",
|
|
request_id = request_id,
|
|
response = input,
|
|
session_id = vim.v.servername
|
|
})
|
|
end)
|
|
end
|
|
end
|
|
|
|
local ns_id = vim.api.nvim_create_namespace("gemini_ghost_text")
|
|
local knowledge_ns = vim.api.nvim_create_namespace("gemini_knowledge_projection")
|
|
local active_diffs = {}
|
|
local active_knowledge = {}
|
|
|
|
function M.project_knowledge(tech_debts, adrs, bufnr)
|
|
bufnr = (bufnr == 0 or bufnr == nil) and vim.api.nvim_get_current_buf() or bufnr
|
|
if not vim.api.nvim_buf_is_valid(bufnr) then return end
|
|
|
|
vim.api.nvim_buf_clear_namespace(bufnr, knowledge_ns, 0, -1)
|
|
active_knowledge[bufnr] = {
|
|
tech_debts = tech_debts or {},
|
|
adrs = adrs or {},
|
|
file = vim.api.nvim_buf_get_name(bufnr)
|
|
}
|
|
|
|
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
|
|
|
-- 1. Project Tech Debts
|
|
if tech_debts and #tech_debts > 0 then
|
|
for _, debt in ipairs(tech_debts) do
|
|
local target_line = 0
|
|
if debt.line_range then
|
|
local num = tonumber(string.match(debt.line_range, "%d+"))
|
|
if num and num > 0 then
|
|
target_line = num - 1
|
|
end
|
|
end
|
|
target_line = math.max(0, math.min(target_line, line_count - 1))
|
|
|
|
local sev = debt.severity or "debt"
|
|
local hl = (sev == "critical" or sev == "high") and "DiagnosticError" or "DiagnosticWarn"
|
|
vim.api.nvim_buf_set_extmark(bufnr, knowledge_ns, target_line, 0, {
|
|
virt_text = {{" ⚡ [" .. sev:upper() .. " DEBT] " .. debt.description, hl}},
|
|
virt_text_pos = "eol",
|
|
hl_mode = "combine",
|
|
})
|
|
end
|
|
end
|
|
|
|
-- 2. Project ADRs
|
|
if adrs and #adrs > 0 then
|
|
for _, adr in ipairs(adrs) do
|
|
vim.api.nvim_buf_set_extmark(bufnr, knowledge_ns, 0, 0, {
|
|
virt_text = {{" 📜 [" .. adr.id .. "] " .. adr.title, "DiagnosticInfo"}},
|
|
virt_text_pos = "eol",
|
|
hl_mode = "combine",
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.show_knowledge()
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
|
local data = active_knowledge[bufnr]
|
|
if not data or ((#data.tech_debts == 0) and (#data.adrs == 0)) then
|
|
print("No active Tech Debt or ADRs found for this file.")
|
|
return
|
|
end
|
|
|
|
local lines = {}
|
|
table.insert(lines, "# Antigravity Knowledge Graph Projection")
|
|
table.insert(lines, "File: " .. (data.file or vim.api.nvim_buf_get_name(bufnr)))
|
|
table.insert(lines, string.rep("=", 50))
|
|
table.insert(lines, "")
|
|
|
|
if #data.tech_debts > 0 then
|
|
table.insert(lines, "## Active Tech Debts (" .. #data.tech_debts .. ")")
|
|
for _, d in ipairs(data.tech_debts) do
|
|
table.insert(lines, string.format("- [%s] (%s): %s", d.id, d.severity or "unspecified", d.description))
|
|
if d.line_range then
|
|
table.insert(lines, " Lines: " .. d.line_range)
|
|
end
|
|
if d.ideal_solution and d.ideal_solution ~= "" then
|
|
table.insert(lines, " Ideal Solution: " .. d.ideal_solution)
|
|
end
|
|
end
|
|
table.insert(lines, "")
|
|
end
|
|
|
|
if #data.adrs > 0 then
|
|
table.insert(lines, "## Architectural Decision Records (ADRs) (" .. #data.adrs .. ")")
|
|
for _, a in ipairs(data.adrs) do
|
|
table.insert(lines, string.format("- [%s] %s (Status: %s)", a.id, a.title, a.status))
|
|
table.insert(lines, " Decision: " .. a.decision)
|
|
end
|
|
table.insert(lines, "")
|
|
end
|
|
|
|
local kbuf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_buf_set_lines(kbuf, 0, -1, false, lines)
|
|
vim.bo[kbuf].filetype = "markdown"
|
|
|
|
local width = math.min(math.floor(vim.o.columns * 0.7), 90)
|
|
local height = math.min(#lines + 2, math.floor(vim.o.lines * 0.6))
|
|
local row = math.floor((vim.o.lines - height) / 2)
|
|
local col = math.floor((vim.o.columns - width) / 2)
|
|
|
|
local kwin = vim.api.nvim_open_win(kbuf, true, {
|
|
relative = 'editor',
|
|
width = width,
|
|
height = height,
|
|
row = row,
|
|
col = col,
|
|
style = 'minimal',
|
|
border = 'rounded',
|
|
title = ' Antigravity Knowledge Projection ',
|
|
title_pos = 'center'
|
|
})
|
|
|
|
local function close_kwin()
|
|
if kwin and vim.api.nvim_win_is_valid(kwin) then
|
|
vim.api.nvim_win_close(kwin, true)
|
|
end
|
|
end
|
|
vim.keymap.set('n', 'q', close_kwin, { buffer = kbuf, noremap = true, silent = true })
|
|
vim.keymap.set('n', '<Esc>', close_kwin, { buffer = kbuf, noremap = true, silent = true })
|
|
end
|
|
|
|
function M.preview_diff(diff_id, bufnr, start_row, end_row, new_lines)
|
|
bufnr = (bufnr == 0 or bufnr == nil) and vim.api.nvim_get_current_buf() or bufnr
|
|
local virt_lines = {}
|
|
for _, line in ipairs(new_lines) do
|
|
table.insert(virt_lines, {{line, "DiffAdd"}})
|
|
end
|
|
|
|
local extmark_id = vim.api.nvim_buf_set_extmark(bufnr, ns_id, start_row, 0, {
|
|
virt_lines = virt_lines,
|
|
virt_lines_above = true,
|
|
})
|
|
|
|
active_diffs[diff_id] = {
|
|
bufnr = bufnr,
|
|
start_row = start_row,
|
|
end_row = end_row,
|
|
new_lines = new_lines,
|
|
extmark_id = extmark_id
|
|
}
|
|
|
|
if pcall(require, "notify") then
|
|
require("notify")("Ghost text diff added. Use <leader>aa to accept or <leader>ar to reject.", "info", { title = "Antigravity" })
|
|
else
|
|
print("Ghost text diff added. Use <leader>aa to accept or <leader>ar to reject.")
|
|
end
|
|
end
|
|
|
|
function M.accept_diff()
|
|
local diff_id, diff = next(active_diffs)
|
|
if not diff then
|
|
print("No active diffs to accept.")
|
|
return
|
|
end
|
|
|
|
vim.api.nvim_buf_set_lines(diff.bufnr, diff.start_row, diff.end_row, false, diff.new_lines)
|
|
vim.api.nvim_buf_del_extmark(diff.bufnr, ns_id, diff.extmark_id)
|
|
active_diffs[diff_id] = nil
|
|
|
|
send_udp({ event = "agent_diff_accepted", diff_id = diff_id, session_id = vim.v.servername })
|
|
print("Diff accepted.")
|
|
end
|
|
|
|
function M.reject_diff()
|
|
local diff_id, diff = next(active_diffs)
|
|
if not diff then
|
|
print("No active diffs to reject.")
|
|
return
|
|
end
|
|
|
|
vim.api.nvim_buf_del_extmark(diff.bufnr, ns_id, diff.extmark_id)
|
|
active_diffs[diff_id] = nil
|
|
|
|
send_udp({ event = "agent_diff_rejected", diff_id = diff_id, session_id = vim.v.servername })
|
|
print("Diff rejected.")
|
|
end
|
|
|
|
function M.ask_agent()
|
|
local file = vim.api.nvim_buf_get_name(0)
|
|
vim.ui.input({ prompt = 'Ask Agent: ' }, function(input)
|
|
if not input or input == '' then return end
|
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
|
send_udp({
|
|
event = "agent_ask",
|
|
prompt = input,
|
|
file = file,
|
|
line = cursor[1],
|
|
col = cursor[2],
|
|
session_id = vim.v.servername
|
|
})
|
|
M.show_progress("Sent to Antigravity via UDP...")
|
|
vim.defer_fn(M.close_progress, 1500)
|
|
end)
|
|
end
|
|
|
|
function M.setup()
|
|
vim.keymap.set('n', '<leader>ai', M.ask_agent, { desc = "Ask Antigravity Agent", noremap = true, silent = true })
|
|
vim.keymap.set('v', '<leader>ai', function()
|
|
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
|
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
|
|
|
vim.ui.input({ prompt = 'Ask Agent (about selection): ' }, function(input)
|
|
if not input or input == '' then return end
|
|
|
|
local file = vim.api.nvim_buf_get_name(0)
|
|
send_udp({
|
|
event = "agent_ask_selection",
|
|
prompt = input,
|
|
file = file,
|
|
start_row = csrow,
|
|
end_row = cerow,
|
|
session_id = vim.v.servername
|
|
})
|
|
M.show_progress("Sent selection to Antigravity via UDP...")
|
|
vim.defer_fn(M.close_progress, 1500)
|
|
end)
|
|
end, { desc = "Ask Antigravity Agent about selection", noremap = true, silent = true })
|
|
|
|
vim.keymap.set('n', '<leader>aa', M.accept_diff, { desc = "Accept Agent Diff", noremap = true, silent = true })
|
|
vim.keymap.set('n', '<leader>ar', M.reject_diff, { desc = "Reject Agent Diff", noremap = true, silent = true })
|
|
vim.keymap.set('n', '<leader>ak', M.show_knowledge, { desc = "Show Knowledge (Tech Debt & ADRs)", noremap = true, silent = true })
|
|
end
|
|
|
|
function M.send_to_terminal(command)
|
|
local term_buf = nil
|
|
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
|
if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == "terminal" then
|
|
term_buf = buf
|
|
break
|
|
end
|
|
end
|
|
|
|
if not term_buf then
|
|
vim.cmd("split | term")
|
|
term_buf = vim.api.nvim_get_current_buf()
|
|
else
|
|
local win_found = false
|
|
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
|
if vim.api.nvim_win_get_buf(win) == term_buf then
|
|
vim.api.nvim_set_current_win(win)
|
|
win_found = true
|
|
break
|
|
end
|
|
end
|
|
if not win_found then
|
|
vim.cmd("split")
|
|
vim.api.nvim_win_set_buf(0, term_buf)
|
|
end
|
|
end
|
|
|
|
local job_id = vim.b[term_buf].terminal_job_id
|
|
if job_id then
|
|
vim.api.nvim_chan_send(job_id, command .. "\r\n")
|
|
vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(term_buf), 0})
|
|
end
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Antigravity Statusline Integration (Push-driven / Zero-polling)
|
|
-- ============================================================================
|
|
local statusline_cache = {
|
|
text = ""
|
|
}
|
|
|
|
function M.update_statusline(active_task, active_gate)
|
|
local parts = {}
|
|
|
|
if active_task and active_task ~= vim.NIL and active_task ~= "" then
|
|
local title = tostring(active_task)
|
|
if #title > 24 then
|
|
title = title:sub(1, 22) .. ".."
|
|
end
|
|
table.insert(parts, "📋 " .. title)
|
|
end
|
|
|
|
if active_gate and active_gate ~= vim.NIL and active_gate ~= "" then
|
|
local status = tostring(active_gate):lower()
|
|
if status == "authorized" then
|
|
table.insert(parts, "🟢 PUSH:OK")
|
|
elseif status == "blocked" then
|
|
table.insert(parts, "🔴 PUSH:BLOCKED")
|
|
else
|
|
table.insert(parts, "🟡 PUSH:WAIT")
|
|
end
|
|
end
|
|
|
|
if #parts > 0 then
|
|
statusline_cache.text = " [AGY: " .. table.concat(parts, " | ") .. "] "
|
|
else
|
|
statusline_cache.text = ""
|
|
end
|
|
|
|
vim.schedule(function()
|
|
pcall(vim.cmd, "redrawstatus")
|
|
end)
|
|
end
|
|
|
|
function M.statusline()
|
|
return statusline_cache.text
|
|
end
|
|
|
|
return M
|