More refactoring
This commit is contained in:
parent
9e2bc5e1e4
commit
ec2f8862d9
@ -1,114 +1,17 @@
|
||||
-- lua/gemini/api.lua
|
||||
|
||||
local config = require("gemini.config")
|
||||
local http = require("gemini.http")
|
||||
local M = {}
|
||||
|
||||
-- Store conversation history
|
||||
local conversation_history = {}
|
||||
|
||||
-- Helper function to get API key
|
||||
local function get_api_key()
|
||||
return vim.g.gemini_api_key or os.getenv("GEMINI_API_KEY")
|
||||
end
|
||||
|
||||
-- Create a Request class to handle HTTP requests
|
||||
local Request = {}
|
||||
Request.__index = Request
|
||||
|
||||
function Request.new(url, payload)
|
||||
local self = setmetatable({}, Request)
|
||||
self.url = url
|
||||
self.payload = payload
|
||||
self.response = ""
|
||||
self.error_msg = ""
|
||||
self.stdout = vim.loop.new_pipe()
|
||||
self.stderr = vim.loop.new_pipe()
|
||||
self.handle = nil
|
||||
return self
|
||||
end
|
||||
|
||||
function Request:cleanup()
|
||||
if self.stdout then self.stdout:close() end
|
||||
if self.stderr then self.stderr:close() end
|
||||
if self.handle then self.handle:close() end
|
||||
end
|
||||
|
||||
function Request:handle_error(callback, msg)
|
||||
self:cleanup()
|
||||
vim.schedule(function()
|
||||
callback(nil, msg)
|
||||
end)
|
||||
end
|
||||
|
||||
function Request:setup_pipes(callback)
|
||||
-- Handle stdout data
|
||||
self.stdout:read_start(function(err, chunk)
|
||||
if err then
|
||||
self:handle_error(callback, "Read error: " .. err)
|
||||
return
|
||||
end
|
||||
if chunk then
|
||||
self.response = self.response .. chunk
|
||||
end
|
||||
end)
|
||||
|
||||
-- Handle stderr data
|
||||
self.stderr:read_start(function(err, chunk)
|
||||
if err then
|
||||
self:handle_error(callback, "Error reading stderr: " .. err)
|
||||
return
|
||||
end
|
||||
if chunk then
|
||||
self.error_msg = self.error_msg .. chunk
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Request:execute(callback)
|
||||
self.handle = vim.loop.spawn('curl', {
|
||||
args = {
|
||||
'-s',
|
||||
'-X', 'POST',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', self.payload,
|
||||
self.url
|
||||
},
|
||||
stdio = {nil, self.stdout, self.stderr}
|
||||
}, function(exit_code)
|
||||
self:cleanup()
|
||||
|
||||
if exit_code ~= 0 then
|
||||
vim.schedule(function()
|
||||
callback(nil, "Curl failed with code " .. exit_code .. ": " .. self.error_msg)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- Try to parse the response
|
||||
local success, decoded = pcall(vim.json.decode, self.response)
|
||||
vim.schedule(function()
|
||||
if success then
|
||||
callback(decoded)
|
||||
else
|
||||
callback(nil, "JSON decode error: " .. self.response)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
if not self.handle then
|
||||
self:handle_error(callback, "Failed to start curl")
|
||||
return
|
||||
end
|
||||
|
||||
self:setup_pipes(callback)
|
||||
end
|
||||
|
||||
-- Message handling functions
|
||||
local Message = {}
|
||||
|
||||
function Message.create_contents(prompt, context)
|
||||
local function create_contents(prompt, context)
|
||||
local contents = {}
|
||||
|
||||
-- Add conversation history
|
||||
for _, message in ipairs(conversation_history) do
|
||||
table.insert(contents, {
|
||||
role = message.role,
|
||||
@ -116,7 +19,6 @@ function Message.create_contents(prompt, context)
|
||||
})
|
||||
end
|
||||
|
||||
-- Add current prompt
|
||||
if context then
|
||||
table.insert(contents, {
|
||||
role = "user",
|
||||
@ -132,11 +34,11 @@ function Message.create_contents(prompt, context)
|
||||
return contents
|
||||
end
|
||||
|
||||
function Message.store_message(role, content)
|
||||
local function store_message(role, content)
|
||||
table.insert(conversation_history, {role = role, content = content})
|
||||
end
|
||||
|
||||
function Message.handle_response(result, callback)
|
||||
local function handle_response(result, callback)
|
||||
if result.error then
|
||||
callback(nil, "API Error: " .. vim.inspect(result.error))
|
||||
return
|
||||
@ -150,14 +52,13 @@ function Message.handle_response(result, callback)
|
||||
result.candidates[1].content.parts[1].text then
|
||||
|
||||
local response_text = result.candidates[1].content.parts[1].text
|
||||
Message.store_message("model", response_text)
|
||||
store_message("model", response_text)
|
||||
callback(response_text)
|
||||
else
|
||||
callback(nil, "Unexpected response structure")
|
||||
end
|
||||
end
|
||||
|
||||
-- Main API functions
|
||||
function M.get_response(prompt, context, callback)
|
||||
local api_key = get_api_key()
|
||||
|
||||
@ -168,24 +69,23 @@ function M.get_response(prompt, context, callback)
|
||||
return
|
||||
end
|
||||
|
||||
-- Store prompt in history
|
||||
Message.store_message("user", prompt)
|
||||
store_message("user", prompt)
|
||||
|
||||
local contents = Message.create_contents(prompt, context)
|
||||
local contents = create_contents(prompt, context)
|
||||
local payload = vim.json.encode({contents = contents})
|
||||
local url = string.format(
|
||||
"https://generativelanguage.googleapis.com/v1/models/%s:generateContent?key=%s",
|
||||
"gemini-2.0-flash",
|
||||
config.options.api_url .. "?key=%s",
|
||||
config.options.model,
|
||||
api_key
|
||||
)
|
||||
|
||||
local request = Request.new(url, payload)
|
||||
local request = http.Request.new(url, payload)
|
||||
request:execute(function(result, error)
|
||||
if error then
|
||||
callback(nil, error)
|
||||
return
|
||||
end
|
||||
Message.handle_response(result, callback)
|
||||
handle_response(result, callback)
|
||||
end)
|
||||
end
|
||||
|
||||
|
145
lua/gemini/chat.lua
Normal file
145
lua/gemini/chat.lua
Normal file
@ -0,0 +1,145 @@
|
||||
local config = require("gemini.config")
|
||||
local M = {}
|
||||
|
||||
-- Store chat state
|
||||
local state = {
|
||||
bufnr = nil,
|
||||
winnr = nil,
|
||||
current_context = nil,
|
||||
}
|
||||
|
||||
local function setup_chat_highlighting(bufnr)
|
||||
vim.api.nvim_buf_set_option(bufnr, 'syntax', '')
|
||||
pcall(vim.treesitter.start, bufnr, "markdown")
|
||||
|
||||
vim.cmd([[
|
||||
highlight default GeminiUser guifg=#EBCB8B gui=bold
|
||||
highlight default GeminiSeparator guifg=#616E88 gui=bold
|
||||
syntax match GeminiUser /^User:.*$/
|
||||
syntax match GeminiSeparator /^━━━━━━━━━━━━━━━━━━━━━━━━━━$/
|
||||
]])
|
||||
end
|
||||
|
||||
local function setup_buffer_options(bufnr)
|
||||
local options = {
|
||||
buftype = 'nofile',
|
||||
filetype = 'markdown',
|
||||
buflisted = false,
|
||||
swapfile = false,
|
||||
bufhidden = 'wipe',
|
||||
modifiable = false,
|
||||
}
|
||||
|
||||
for option, value in pairs(options) do
|
||||
vim.api.nvim_buf_set_option(bufnr, option, value)
|
||||
end
|
||||
end
|
||||
|
||||
local function setup_buffer_autocmds(bufnr)
|
||||
local augroup = vim.api.nvim_create_augroup('GeminiChatBuffer', { clear = true })
|
||||
vim.api.nvim_create_autocmd({'BufReadCmd', 'FileReadCmd', 'BufWriteCmd'}, {
|
||||
group = augroup,
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
vim.notify('File operations are not allowed in the chat window', vim.log.levels.WARN)
|
||||
return true
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function setup_buffer_keymaps(bufnr)
|
||||
local opts = { buffer = bufnr, nowait = true }
|
||||
|
||||
-- Disable file operations
|
||||
local operations = {'e', 'edit', 'w', 'write', 'sp', 'split', 'vs', 'vsplit',
|
||||
'new', 'vnew', 'read', 'update', 'saveas'}
|
||||
for _, op in ipairs(operations) do
|
||||
vim.keymap.set('n', ':' .. op, function()
|
||||
vim.notify('Operation not allowed in chat window', vim.log.levels.WARN)
|
||||
end, opts)
|
||||
end
|
||||
|
||||
-- Disable command mode
|
||||
vim.keymap.set('n', ':', function()
|
||||
vim.notify('Command mode disabled in chat window', vim.log.levels.WARN)
|
||||
end, opts)
|
||||
|
||||
-- Set chat-specific keymaps
|
||||
local mappings = config.options.mappings
|
||||
vim.keymap.set('n', mappings.close, function()
|
||||
vim.api.nvim_win_close(state.winnr, true)
|
||||
state.winnr = nil
|
||||
end, opts)
|
||||
|
||||
vim.keymap.set('n', mappings.return_focus, function()
|
||||
vim.cmd('wincmd p')
|
||||
end, opts)
|
||||
|
||||
vim.keymap.set('n', mappings.new_query, function()
|
||||
require("gemini").prompt_query(state.current_context)
|
||||
end, opts)
|
||||
end
|
||||
|
||||
function M.create_window()
|
||||
if not state.bufnr or not vim.api.nvim_buf_is_valid(state.bufnr) then
|
||||
state.bufnr = vim.api.nvim_create_buf(false, true)
|
||||
setup_buffer_options(state.bufnr)
|
||||
setup_buffer_autocmds(state.bufnr)
|
||||
setup_buffer_keymaps(state.bufnr)
|
||||
end
|
||||
|
||||
local win_opts = config.options.window
|
||||
if not state.winnr or not vim.api.nvim_win_is_valid(state.winnr) then
|
||||
state.winnr = vim.api.nvim_open_win(state.bufnr, true, {
|
||||
relative = "editor",
|
||||
width = win_opts.width(),
|
||||
height = win_opts.height(),
|
||||
row = 0,
|
||||
col = vim.o.columns - win_opts.width(),
|
||||
border = win_opts.border,
|
||||
title = win_opts.title,
|
||||
title_pos = win_opts.title_pos,
|
||||
style = "minimal"
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_option(state.winnr, 'wrap', true)
|
||||
vim.api.nvim_win_set_option(state.winnr, 'linebreak', true)
|
||||
vim.api.nvim_win_set_option(state.winnr, 'breakindent', true)
|
||||
|
||||
setup_chat_highlighting(state.bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
function M.update_content(content, is_new_chat)
|
||||
vim.api.nvim_buf_set_option(state.bufnr, 'modifiable', true)
|
||||
|
||||
if is_new_chat then
|
||||
vim.api.nvim_buf_set_lines(state.bufnr, 0, -1, false, vim.split(content, "\n"))
|
||||
else
|
||||
local separator = "━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
vim.api.nvim_buf_set_lines(state.bufnr, -1, -1, false, {separator})
|
||||
vim.api.nvim_buf_set_lines(state.bufnr, -1, -1, false, vim.split(content, "\n"))
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_option(state.bufnr, 'modifiable', false)
|
||||
|
||||
-- Scroll to show new content
|
||||
local line_count = vim.api.nvim_buf_line_count(state.bufnr)
|
||||
local content_lines = #vim.split(content, "\n")
|
||||
local start_line = line_count - content_lines + 1
|
||||
vim.api.nvim_win_set_cursor(state.winnr, {start_line, 0})
|
||||
vim.cmd('normal! zt')
|
||||
vim.cmd('wincmd p')
|
||||
end
|
||||
|
||||
function M.set_context(context)
|
||||
state.current_context = context
|
||||
end
|
||||
|
||||
function M.clear()
|
||||
if state.bufnr and vim.api.nvim_buf_is_valid(state.bufnr) then
|
||||
vim.api.nvim_buf_set_lines(state.bufnr, 0, -1, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
30
lua/gemini/config.lua
Normal file
30
lua/gemini/config.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local M = {}
|
||||
|
||||
M.defaults = {
|
||||
model = "gemini-2.0-flash",
|
||||
api_url = "https://generativelanguage.googleapis.com/v1/models/%s:generateContent",
|
||||
window = {
|
||||
width = function() return math.floor(vim.o.columns / 3) end,
|
||||
height = function() return vim.o.lines - 2 end,
|
||||
border = "rounded",
|
||||
title = "Gemini Chat Session",
|
||||
title_pos = "center",
|
||||
},
|
||||
mappings = {
|
||||
close = 'q',
|
||||
return_focus = '<Esc>',
|
||||
new_query = 'i',
|
||||
},
|
||||
highlights = {
|
||||
user = "GeminiUser",
|
||||
separator = "GeminiSeparator",
|
||||
},
|
||||
}
|
||||
|
||||
M.options = {}
|
||||
|
||||
function M.setup(opts)
|
||||
M.options = vim.tbl_deep_extend("force", {}, M.defaults, opts or {})
|
||||
end
|
||||
|
||||
return M
|
93
lua/gemini/http.lua
Normal file
93
lua/gemini/http.lua
Normal file
@ -0,0 +1,93 @@
|
||||
local M = {}
|
||||
|
||||
-- HTTP Request class
|
||||
local Request = {}
|
||||
Request.__index = Request
|
||||
|
||||
function Request.new(url, payload)
|
||||
local self = setmetatable({}, Request)
|
||||
self.url = url
|
||||
self.payload = payload
|
||||
self.response = ""
|
||||
self.error_msg = ""
|
||||
self.stdout = vim.loop.new_pipe()
|
||||
self.stderr = vim.loop.new_pipe()
|
||||
self.handle = nil
|
||||
return self
|
||||
end
|
||||
|
||||
function Request:cleanup()
|
||||
if self.stdout then self.stdout:close() end
|
||||
if self.stderr then self.stderr:close() end
|
||||
if self.handle then self.handle:close() end
|
||||
end
|
||||
|
||||
function Request:handle_error(callback, msg)
|
||||
self:cleanup()
|
||||
vim.schedule(function()
|
||||
callback(nil, msg)
|
||||
end)
|
||||
end
|
||||
|
||||
function Request:setup_pipes(callback)
|
||||
self.stdout:read_start(function(err, chunk)
|
||||
if err then
|
||||
self:handle_error(callback, "Read error: " .. err)
|
||||
return
|
||||
end
|
||||
if chunk then
|
||||
self.response = self.response .. chunk
|
||||
end
|
||||
end)
|
||||
|
||||
self.stderr:read_start(function(err, chunk)
|
||||
if err then
|
||||
self:handle_error(callback, "Error reading stderr: " .. err)
|
||||
return
|
||||
end
|
||||
if chunk then
|
||||
self.error_msg = self.error_msg .. chunk
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Request:execute(callback)
|
||||
self.handle = vim.loop.spawn('curl', {
|
||||
args = {
|
||||
'-s',
|
||||
'-X', 'POST',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', self.payload,
|
||||
self.url
|
||||
},
|
||||
stdio = {nil, self.stdout, self.stderr}
|
||||
}, function(exit_code)
|
||||
self:cleanup()
|
||||
|
||||
if exit_code ~= 0 then
|
||||
vim.schedule(function()
|
||||
callback(nil, "Curl failed with code " .. exit_code .. ": " .. self.error_msg)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
local success, decoded = pcall(vim.json.decode, self.response)
|
||||
vim.schedule(function()
|
||||
if success then
|
||||
callback(decoded)
|
||||
else
|
||||
callback(nil, "JSON decode error: " .. self.response)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
if not self.handle then
|
||||
self:handle_error(callback, "Failed to start curl")
|
||||
return
|
||||
end
|
||||
|
||||
self:setup_pipes(callback)
|
||||
end
|
||||
|
||||
M.Request = Request
|
||||
return M
|
@ -1,294 +1,84 @@
|
||||
-- lua/gemini/init.lua
|
||||
|
||||
local api = require("gemini.api")
|
||||
local M = {}
|
||||
local chat = require("gemini.chat")
|
||||
local config = require("gemini.config")
|
||||
|
||||
-- Store the buffer number of the chat window
|
||||
local chat_bufnr = nil
|
||||
local chat_winnr = nil
|
||||
local current_context = nil -- Store the current context
|
||||
local M = {}
|
||||
|
||||
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 })
|
||||
|
||||
-- Make Esc return focus to previous window
|
||||
vim.keymap.set('n', '<Esc>', function()
|
||||
vim.cmd('wincmd p')
|
||||
end, { buffer = chat_bufnr, nowait = true })
|
||||
|
||||
-- Add input mapping with context awareness
|
||||
vim.keymap.set('n', 'i', function()
|
||||
vim.ui.input({ prompt = "Gemini: " }, function(input)
|
||||
function M.prompt_query(context)
|
||||
local prompt = context and "Gemini (with buffer context): " or "Gemini: "
|
||||
vim.ui.input({ prompt = prompt }, function(input)
|
||||
if input then
|
||||
M.gemini_query(input, current_context)
|
||||
M.query(input, context)
|
||||
end
|
||||
end)
|
||||
end, { buffer = chat_bufnr, nowait = true })
|
||||
|
||||
-- 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
|
||||
|
||||
-- Find the start of the current query (last line count - number of lines in new_content + 1)
|
||||
local line_count = vim.api.nvim_buf_line_count(chat_bufnr)
|
||||
local new_content_lines = #vim.split(new_content, "\n")
|
||||
local query_start_line = line_count - new_content_lines + 1
|
||||
|
||||
-- Set cursor to the start of current query
|
||||
vim.api.nvim_win_set_cursor(chat_winnr, {query_start_line, 0})
|
||||
|
||||
-- Scroll the window to show the query at the top
|
||||
vim.cmd('normal! zt')
|
||||
|
||||
-- Return focus to the previous window
|
||||
vim.cmd('wincmd p')
|
||||
end
|
||||
|
||||
local function gemini_query(prompt, context)
|
||||
-- Store the context for subsequent queries
|
||||
current_context = context
|
||||
function M.query(prompt, context)
|
||||
chat.set_context(context)
|
||||
chat.create_window()
|
||||
|
||||
-- Show initial message in chat window
|
||||
local initial_content = "User: " .. prompt .. "\n\nAssistant: Thinking..."
|
||||
update_chat_window(initial_content)
|
||||
|
||||
-- Force Neovim to update the screen
|
||||
chat.update_content(initial_content, true)
|
||||
vim.cmd('redraw')
|
||||
|
||||
-- Make async request
|
||||
api.get_response(prompt, context, function(response, error)
|
||||
if error then
|
||||
-- Replace "Thinking..." with error message
|
||||
local lines = vim.api.nvim_buf_get_lines(chat_bufnr, 0, -1, false)
|
||||
for i = 1, #lines do
|
||||
if lines[i] == "Assistant: Thinking..." then
|
||||
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', true)
|
||||
vim.api.nvim_buf_set_lines(chat_bufnr, i, i + 1, false,
|
||||
{"Assistant: Error - " .. error})
|
||||
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', false)
|
||||
break
|
||||
end
|
||||
end
|
||||
vim.notify("Failed to get response: " .. error, vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Make buffer modifiable
|
||||
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', true)
|
||||
|
||||
-- Get all lines
|
||||
local lines = vim.api.nvim_buf_get_lines(chat_bufnr, 0, -1, false)
|
||||
|
||||
-- Find and remove the "Thinking..." line
|
||||
for i = 1, #lines do
|
||||
if lines[i] == "Assistant: Thinking..." then
|
||||
-- Remove the "Thinking..." line
|
||||
table.remove(lines, i)
|
||||
-- Insert response lines
|
||||
local response_lines = vim.split(response, "\n")
|
||||
response_lines[1] = "Assistant: " .. response_lines[1]
|
||||
for j = #response_lines, 1, -1 do
|
||||
table.insert(lines, i, response_lines[j])
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Update buffer with modified lines
|
||||
vim.api.nvim_buf_set_lines(chat_bufnr, 0, -1, false, lines)
|
||||
|
||||
-- Make buffer unmodifiable
|
||||
vim.api.nvim_buf_set_option(chat_bufnr, 'modifiable', false)
|
||||
|
||||
-- Move focus to chat window
|
||||
if chat_winnr and vim.api.nvim_win_is_valid(chat_winnr) then
|
||||
vim.api.nvim_set_current_win(chat_winnr)
|
||||
end
|
||||
|
||||
-- Clear the command line
|
||||
vim.cmd('echo ""')
|
||||
local content = "User: " .. prompt .. "\n\nAssistant: " .. response
|
||||
chat.update_content(content, true)
|
||||
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(opts)
|
||||
-- Configure the plugin
|
||||
config.setup(opts)
|
||||
|
||||
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
|
||||
pcall(vim.treesitter.language.require_language, "markdown")
|
||||
|
||||
-- Create the user command
|
||||
-- Create commands
|
||||
vim.api.nvim_create_user_command("Gemini", function(opts)
|
||||
local prompt = opts.args
|
||||
if prompt == "" then
|
||||
if opts.args == "" then
|
||||
vim.notify("Please provide a prompt for Gemini.", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
M.gemini_query(prompt)
|
||||
M.query(opts.args)
|
||||
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
|
||||
chat.clear()
|
||||
vim.notify("Chat history cleared", vim.log.levels.INFO)
|
||||
end, {
|
||||
desc = "Clear Gemini chat history"
|
||||
})
|
||||
|
||||
-- Set up keymapping with 'gc' for 'gemini chat'
|
||||
-- Set up default keymaps
|
||||
vim.keymap.set("n", "<leader>gc", function()
|
||||
vim.ui.input({ prompt = "Gemini: " }, function(input)
|
||||
if input then
|
||||
M.gemini_query(input)
|
||||
end
|
||||
end)
|
||||
M.prompt_query()
|
||||
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)
|
||||
M.prompt_query(get_current_buffer_content())
|
||||
end, { desc = "Chat with Gemini AI (with buffer context)" })
|
||||
|
||||
-- Set up keymapping with 'gq' for 'gemini quit/clear'
|
||||
vim.keymap.set("n", "<leader>gq", 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
|
||||
chat.clear()
|
||||
vim.notify("Chat history cleared", vim.log.levels.INFO)
|
||||
end, { desc = "Clear Gemini chat history" })
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user