diff --git a/lua/gemini/completion.lua b/lua/gemini/completion.lua index b950b94..5fe6ba9 100644 --- a/lua/gemini/completion.lua +++ b/lua/gemini/completion.lua @@ -1,45 +1,53 @@ local api = require("gemini.api") local M = {} --- Cache for completion items -local completion_cache = {} +-- State for managing current suggestion +local current_suggestion = { + text = nil, + start_col = nil, + namespace_id = vim.api.nvim_create_namespace('gemini_suggestion') +} --- Helper function to create completion items from Gemini response -local function parse_completion_response(response) - if type(response) ~= "string" then - return {} +-- Helper function to clear current suggestion +local function clear_suggestion() + if current_suggestion.text then + vim.api.nvim_buf_clear_namespace(0, current_suggestion.namespace_id, 0, -1) + current_suggestion.text = nil + current_suggestion.start_col = nil end - - -- Split response into lines and filter out empty ones - local lines = vim.split(response, "\n") - local items = {} - - for _, line in ipairs(lines) do - if line and line:match("^%s*[^%s]") then -- Skip empty lines - table.insert(items, { - word = line:match("^%s*(.-)%s*$"), -- Trim whitespace - kind = vim.lsp.protocol.CompletionItemKind.Text, - menu = "[Gemini]", - info = line, - }) - end - end - - -- Always return at least one item - if #items == 0 then - items = { - { - word = "No completions found", - kind = vim.lsp.protocol.CompletionItemKind.Text, - menu = "[Gemini]" - } - } - end - - return items end -function M.get_completion(params, callback) +-- Helper function to show suggestion +local function show_suggestion(suggestion, start_col) + clear_suggestion() + + local line = vim.api.nvim_win_get_cursor(0)[1] - 1 + current_suggestion.text = suggestion + current_suggestion.start_col = start_col + + -- Show suggestion in a different color + vim.api.nvim_buf_add_highlight(0, current_suggestion.namespace_id, 'GeminiSuggestion', line, start_col, start_col + #suggestion) + vim.api.nvim_buf_set_text(0, line, start_col, line, start_col, {suggestion}) + + -- Make the text virtual (doesn't affect actual buffer content) + vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, start_col, { + virt_text = {{suggestion, 'GeminiSuggestion'}}, + virt_text_pos = 'overlay', + }) +end + +-- Function to accept current suggestion +function M.accept_suggestion() + if current_suggestion.text and current_suggestion.start_col then + local line = vim.api.nvim_win_get_cursor(0)[1] - 1 + vim.api.nvim_buf_set_text(0, line, current_suggestion.start_col, line, current_suggestion.start_col, {current_suggestion.text}) + clear_suggestion() + return true + end + return false +end + +function M.trigger_completion() local cursor = vim.api.nvim_win_get_cursor(0) local line = cursor[1] local col = cursor[2] @@ -62,19 +70,44 @@ function M.get_completion(params, callback) -- Get completion from Gemini api.get_response(prompt, nil, function(response, error) if error then - callback({ - { - word = "Error: " .. error, - kind = vim.lsp.protocol.CompletionItemKind.Text, - menu = "[Gemini]" - } - }) + vim.notify("Completion error: " .. error, vim.log.levels.ERROR) return end - local items = parse_completion_response(response) - callback(items) + if type(response) == "string" then + -- Get first non-empty line as suggestion + local suggestion = response:match("^%s*(.-)%s*$") + if suggestion and #suggestion > 0 then + vim.schedule(function() + show_suggestion(suggestion, col) + end) + end + end end) end +-- Setup function to create highlight group and keymaps +function M.setup() + -- Create highlight group for suggestions + vim.api.nvim_set_hl(0, 'GeminiSuggestion', { + fg = '#888888', + italic = true, + }) + + -- Map Tab to accept suggestion or behave normally + vim.keymap.set('i', '', function() + if not M.accept_suggestion() then + -- If no suggestion to accept, send regular Tab key + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'n', true) + end + end, { expr = false, noremap = true }) + + -- Clear suggestion on cursor move + vim.api.nvim_create_autocmd({'CursorMovedI', 'CursorMoved'}, { + callback = function() + clear_suggestion() + end + }) +end + return M \ No newline at end of file diff --git a/lua/gemini/init.lua b/lua/gemini/init.lua index b92e8ec..a044c54 100644 --- a/lua/gemini/init.lua +++ b/lua/gemini/init.lua @@ -43,12 +43,22 @@ function M.setup(opts) config.setup(opts) pcall(vim.treesitter.language.require_language, "markdown") - -- Set up omnifunc globally - vim.api.nvim_create_autocmd("FileType", { - pattern = "*", + -- Set up completion + require("gemini.completion").setup() + + -- Auto-trigger completion after a short delay when typing + vim.api.nvim_create_autocmd("TextChangedI", { callback = function() - vim.bo.omnifunc = "v:lua.require'gemini'.complete" - end, + -- Cancel any existing timer + if vim.b.completion_timer then + vim.fn.timer_stop(vim.b.completion_timer) + end + + -- Start new timer + vim.b.completion_timer = vim.fn.timer_start(500, function() + require("gemini.completion").trigger_completion() + end) + end }) vim.api.nvim_create_user_command("Gemini", function(opts)