223 lines
8.0 KiB
Lua
223 lines
8.0 KiB
Lua
-- lua/gemini/init.lua
|
|
|
|
local api = require("gemini.api")
|
|
local M = {}
|
|
|
|
-- Store the buffer number of the chat window
|
|
local chat_bufnr = nil
|
|
local chat_winnr = nil
|
|
|
|
local function get_current_buffer_content()
|
|
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
local function setup_chat_highlighting(bufnr)
|
|
-- Enable treesitter for the buffer
|
|
vim.api.nvim_buf_set_option(bufnr, 'syntax', '')
|
|
local success = pcall(vim.treesitter.start, bufnr, "markdown")
|
|
if not success then
|
|
-- Fallback to basic markdown syntax if treesitter fails
|
|
vim.api.nvim_buf_set_option(bufnr, 'syntax', 'markdown')
|
|
end
|
|
|
|
-- Create syntax groups for user input and separators
|
|
vim.cmd([[
|
|
highlight GeminiUser guifg=#EBCB8B gui=bold
|
|
highlight GeminiSeparator guifg=#616E88 gui=bold
|
|
syntax match GeminiUser /^User:.*$/
|
|
syntax match GeminiSeparator /^━━━━━━━━━━━━━━━━━━━━━━━━━━$/
|
|
]])
|
|
end
|
|
|
|
local function update_chat_window(new_content)
|
|
if not chat_bufnr or not vim.api.nvim_buf_is_valid(chat_bufnr) then
|
|
-- Create a new buffer for chat
|
|
chat_bufnr = vim.api.nvim_create_buf(false, true)
|
|
|
|
-- Set buffer options to prevent file operations
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'buftype', 'nofile')
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'filetype', 'markdown')
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'buflisted', false)
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'swapfile', false)
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'bufhidden', 'wipe')
|
|
|
|
-- Create buffer-local autocmds to prevent file operations
|
|
local augroup = vim.api.nvim_create_augroup('GeminiChatBuffer', { clear = true })
|
|
vim.api.nvim_create_autocmd({'BufReadCmd', 'FileReadCmd', 'BufWriteCmd'}, {
|
|
group = augroup,
|
|
buffer = chat_bufnr,
|
|
callback = function()
|
|
vim.notify('File operations are not allowed in the chat window', vim.log.levels.WARN)
|
|
return true -- Prevent the default behavior
|
|
end
|
|
})
|
|
|
|
-- Instead of creating commands, we'll use buffer-local keymaps to disable operations
|
|
local operations = {
|
|
'e', 'edit', 'w', 'write', 'sp', 'split', 'vs', 'vsplit',
|
|
'new', 'vnew', 'read', 'update', 'saveas'
|
|
}
|
|
|
|
for _, op in ipairs(operations) do
|
|
-- Disable both normal and command-line operations
|
|
vim.keymap.set('n', ':' .. op, function()
|
|
vim.notify('Operation not allowed in chat window', vim.log.levels.WARN)
|
|
end, { buffer = chat_bufnr, nowait = true })
|
|
end
|
|
|
|
-- Disable entering command-line mode
|
|
vim.keymap.set('n', ':', function()
|
|
vim.notify('Command mode disabled in chat window', vim.log.levels.WARN)
|
|
end, { buffer = chat_bufnr, nowait = true })
|
|
end
|
|
|
|
-- Calculate dimensions
|
|
local width = math.floor(vim.o.columns / 3)
|
|
local height = vim.o.lines - 2
|
|
|
|
if not chat_winnr or not vim.api.nvim_win_is_valid(chat_winnr) then
|
|
-- Create the window
|
|
chat_winnr = vim.api.nvim_open_win(chat_bufnr, true, {
|
|
relative = "editor",
|
|
width = width,
|
|
height = height,
|
|
row = 0,
|
|
col = vim.o.columns - width,
|
|
border = "rounded",
|
|
title = "Gemini Chat Session",
|
|
title_pos = "center",
|
|
style = "minimal"
|
|
})
|
|
|
|
-- Set window-local options
|
|
vim.api.nvim_win_set_option(chat_winnr, 'wrap', true)
|
|
vim.api.nvim_win_set_option(chat_winnr, 'linebreak', true)
|
|
vim.api.nvim_win_set_option(chat_winnr, 'breakindent', true)
|
|
|
|
-- Setup custom highlighting
|
|
setup_chat_highlighting(chat_bufnr)
|
|
|
|
-- Set window-local keymaps
|
|
vim.keymap.set('n', 'q', function()
|
|
vim.api.nvim_win_close(chat_winnr, true)
|
|
chat_winnr = nil
|
|
end, { buffer = chat_bufnr, nowait = true })
|
|
|
|
-- Add input mapping
|
|
vim.keymap.set('n', 'i', function()
|
|
vim.ui.input({ prompt = "Gemini: " }, function(input)
|
|
if input then
|
|
M.gemini_query(input)
|
|
end
|
|
end)
|
|
end, { buffer = chat_bufnr, nowait = true })
|
|
end
|
|
|
|
-- Make buffer modifiable
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', true)
|
|
|
|
-- Initialize with content (without separator)
|
|
vim.api.nvim_buf_set_lines(chat_bufnr, 0, -1, false, vim.split(new_content, "\n"))
|
|
|
|
-- Make buffer unmodifiable
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', false)
|
|
else
|
|
-- Make buffer modifiable
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', true)
|
|
|
|
-- Add separator before new content (only for subsequent messages)
|
|
local separator = "━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
vim.api.nvim_buf_set_lines(chat_bufnr, -1, -1, false, {separator})
|
|
|
|
-- Update content
|
|
vim.api.nvim_buf_set_lines(chat_bufnr, -1, -1, false, vim.split(new_content, "\n"))
|
|
|
|
-- Make buffer unmodifiable
|
|
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', false)
|
|
end
|
|
|
|
-- Scroll to bottom
|
|
local line_count = vim.api.nvim_buf_line_count(chat_bufnr)
|
|
vim.api.nvim_win_set_cursor(chat_winnr, {line_count, 0})
|
|
|
|
-- Ensure the last line is visible
|
|
vim.api.nvim_command('normal! zz')
|
|
|
|
-- Return focus to the previous window
|
|
vim.cmd('wincmd p')
|
|
end
|
|
|
|
local function gemini_query(prompt, context)
|
|
local response = api.get_response(prompt, context)
|
|
if response then
|
|
local formatted_content = "\nUser: " .. prompt .. "\n\nAssistant: " .. response
|
|
update_chat_window(formatted_content)
|
|
else
|
|
vim.notify("Failed to get a response from Gemini API", vim.log.levels.ERROR)
|
|
end
|
|
end
|
|
|
|
-- Make gemini_query available in M so it can be used by the setup function
|
|
M.gemini_query = gemini_query
|
|
|
|
function M.setup()
|
|
-- Ensure markdown parser is installed
|
|
local parser_installed = pcall(vim.treesitter.language.require_language, "markdown")
|
|
if not parser_installed then
|
|
vim.notify("Installing markdown parser for treesitter...", vim.log.levels.INFO)
|
|
vim.fn.system({
|
|
"nvim",
|
|
"--headless",
|
|
"-c", "TSInstall markdown",
|
|
"-c", "q"
|
|
})
|
|
end
|
|
|
|
-- Create the user command
|
|
vim.api.nvim_create_user_command("Gemini", function(opts)
|
|
local prompt = opts.args
|
|
if prompt == "" then
|
|
vim.notify("Please provide a prompt for Gemini.", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
M.gemini_query(prompt)
|
|
end, {
|
|
desc = "Query Google AI",
|
|
nargs = "+",
|
|
complete = "shellcmd",
|
|
})
|
|
|
|
-- Add command to clear chat history
|
|
vim.api.nvim_create_user_command("GeminiClearChat", function()
|
|
api.clear_conversation()
|
|
if chat_bufnr and vim.api.nvim_buf_is_valid(chat_bufnr) then
|
|
vim.api.nvim_buf_set_lines(chat_bufnr, 0, -1, false, {})
|
|
end
|
|
vim.notify("Chat history cleared", vim.log.levels.INFO)
|
|
end, {
|
|
desc = "Clear Gemini chat history"
|
|
})
|
|
|
|
-- Set up keymapping with 'gc' for 'gemini chat'
|
|
vim.keymap.set("n", "<leader>gc", function()
|
|
vim.ui.input({ prompt = "Gemini: " }, function(input)
|
|
if input then
|
|
M.gemini_query(input)
|
|
end
|
|
end)
|
|
end, { desc = "Chat with Gemini AI" })
|
|
|
|
-- Set up keymapping with 'gs' for 'gemini sync'
|
|
vim.keymap.set("n", "<leader>gs", function()
|
|
vim.ui.input({ prompt = "Gemini (with buffer context): " }, function(input)
|
|
if input then
|
|
local buffer_content = get_current_buffer_content()
|
|
M.gemini_query(input, buffer_content)
|
|
end
|
|
end)
|
|
end, { desc = "Chat with Gemini AI (with buffer context)" })
|
|
end
|
|
|
|
return M
|