From ed8b23b14e0aa3419c83598e297d35ddfbe7b846 Mon Sep 17 00:00:00 2001 From: Jonas Widen Date: Tue, 18 Mar 2025 18:41:18 +0100 Subject: [PATCH] Review and cleanup --- lua/gemini/completion.lua | 198 ++++++++++++++++++++++++++++++++------ 1 file changed, 170 insertions(+), 28 deletions(-) diff --git a/lua/gemini/completion.lua b/lua/gemini/completion.lua index 42cda6f..6744fd5 100644 --- a/lua/gemini/completion.lua +++ b/lua/gemini/completion.lua @@ -22,6 +22,109 @@ local completion_cache = { last_cleanup = 0 } +-- Add buffer context management +local buffer_context = { + content = nil, + last_update = 0, + bufnr = nil, + changetick = nil, + update_interval = 1000, -- Update buffer context at most every second +} + +-- Function to get relevant buffer content +local function get_buffer_context() + local current_buf = vim.api.nvim_get_current_buf() + local current_tick = vim.api.nvim_buf_get_changedtick(current_buf) + local now = vim.loop.now() + + -- Return cached context if buffer hasn't changed and cache is fresh + if buffer_context.content and + buffer_context.bufnr == current_buf and + buffer_context.changetick == current_tick and + (now - buffer_context.last_update) < buffer_context.update_interval then + return buffer_context.content + end + + -- Get buffer info + local filetype = vim.bo.filetype + local filename = vim.fn.expand('%:t') + local cursor_pos = vim.api.nvim_win_get_cursor(0) + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + + -- Extract imports/requires from the top of the file + local imports = {} + for _, line in ipairs(lines) do + if line:match("^%s*require") or + line:match("^%s*import") or + line:match("^%s*use") or + line:match("^%s*from") then + table.insert(imports, line) + end + end + + -- Get function/class definitions + local definitions = {} + for i, line in ipairs(lines) do + if line:match("^%s*function") or + line:match("^%s*local%s+function") or + line:match("^%s*class") or + line:match("^%s*struct") or + line:match("^%s*type") then + table.insert(definitions, string.format("L%d: %s", i, line)) + end + end + + -- Get current function context + local current_line = cursor_pos[1] + local function_context = {} + local in_function = false + local function_start = 0 + + for i = current_line, 1, -1 do + local line = lines[i] + if line:match("^%s*function") or line:match("^%s*local%s+function") then + function_start = i + in_function = true + break + end + end + + if in_function then + -- Get up to 10 lines of the current function + local context_start = math.max(function_start, current_line - 10) + for i = context_start, current_line - 1 do + table.insert(function_context, string.format("L%d: %s", i, lines[i])) + end + end + + -- Build context structure + local context = { + filetype = filetype, + filename = filename, + cursor = { + line = cursor_pos[1], + col = cursor_pos[2] + }, + imports = imports, + definitions = definitions, + current_function = function_context, + visible_lines = vim.api.nvim_buf_get_lines( + 0, + math.max(0, current_line - 10), + math.min(#lines, current_line + 10), + false + ) + } + + -- Update cache + buffer_context.content = context + buffer_context.bufnr = current_buf + buffer_context.changetick = current_tick + buffer_context.last_update = now + + return context +end + -- Add completion triggers detection local function should_trigger_completion(line_text, col) local config = require("gemini.config") @@ -62,12 +165,17 @@ end -- Improved cache key generation local function get_cache_key(context, line_text, col) + local buffer_ctx = get_buffer_context() local prefix = line_text:sub(1, col) - local filetype = vim.bo.filetype local key_parts = { - filetype, + buffer_ctx.filetype, prefix, - vim.inspect(context):sub(1, 100) -- Limited context hash + -- Include function context in cache key + table.concat(buffer_ctx.current_function, "\n"), + -- Include nearby definitions + table.concat(buffer_ctx.definitions, "\n"):sub(1, 200), + -- Include visible context hash + vim.fn.sha256(table.concat(buffer_ctx.visible_lines, "\n")) } return vim.fn.sha256(table.concat(key_parts, "||")) end @@ -312,24 +420,44 @@ function M.accept_suggestion() return true end --- Helper function to get relevant context around cursor +-- Modify get_context_around_cursor to use buffer context local function get_context_around_cursor(lines, current_line, max_lines) local context = {} - local start_line = math.max(1, current_line - math.floor(max_lines/2)) - local end_line = math.min(#lines, current_line + math.floor(max_lines/2)) + local buffer_ctx = get_buffer_context() - -- Add file type and cursor position information - local file_type = vim.bo.filetype - local cursor_pos = vim.api.nvim_win_get_cursor(0) + -- Add file and cursor information + table.insert(context, string.format("File: %s (%s)", buffer_ctx.filename, buffer_ctx.filetype)) + table.insert(context, string.format("Cursor: line %d, col %d", buffer_ctx.cursor.line, buffer_ctx.cursor.col)) - -- Add metadata to context - table.insert(context, string.format("File type: %s", file_type)) - table.insert(context, string.format("Cursor position: line %d, col %d", cursor_pos[1], cursor_pos[2])) + -- Add imports + if #buffer_ctx.imports > 0 then + table.insert(context, "\nRelevant imports:") + for _, import in ipairs(buffer_ctx.imports) do + table.insert(context, import) + end + end - -- Add visible context with line numbers - for i = start_line, end_line do - if lines[i] and #lines[i] > 0 then - table.insert(context, string.format("L%d: %s", i, lines[i])) + -- Add relevant definitions + if #buffer_ctx.definitions > 0 then + table.insert(context, "\nRelevant definitions:") + for _, def in ipairs(buffer_ctx.definitions) do + table.insert(context, def) + end + end + + -- Add current function context + if #buffer_ctx.current_function > 0 then + table.insert(context, "\nCurrent function context:") + for _, line in ipairs(buffer_ctx.current_function) do + table.insert(context, line) + end + end + + -- Add visible context + table.insert(context, "\nCurrent visible context:") + for i, line in ipairs(buffer_ctx.visible_lines) do + if #line > 0 then + table.insert(context, string.format("L%d: %s", current_line - 10 + i - 1, line)) end end @@ -374,9 +502,13 @@ function M.trigger_completion() -- Check rate limiting if should_rate_limit() then return end - -- Get context - local visible_lines = vim.api.nvim_buf_get_lines(0, math.max(0, line - 10), line + 10, false) - local context = get_context_around_cursor(visible_lines, 10, current_suggestion.max_context_lines) + -- Get enhanced context + local buffer_ctx = get_buffer_context() + local context = get_context_around_cursor( + buffer_ctx.visible_lines, + buffer_ctx.cursor.line, + current_suggestion.max_context_lines + ) -- Check cache first local cached_completion = get_cached_completion(context, current_line, col) @@ -387,21 +519,31 @@ function M.trigger_completion() return end - -- Create prompt for completion + -- Create more detailed prompt local prompt = string.format([[ You are an autocomplete engine. Respond ONLY with the direct completion text. -DO NOT include: -- Explanations -- Markdown formatting -- Code block markers -- Additional newlines -- Any other text +DO NOT include explanations, markdown, or additional formatting. Language: %s -Context: +File: %s +Imports: %s + +Relevant Definitions: +%s + +Current Function: +%s + Complete this line: -%s]], vim.bo.filetype, table.concat(context, "\n"), current_line) +%s]], + buffer_ctx.filetype, + buffer_ctx.filename, + table.concat(buffer_ctx.imports, "\n"), + table.concat(buffer_ctx.definitions, "\n"), + table.concat(buffer_ctx.current_function, "\n"), + current_line + ) -- Make API request api.get_response(prompt, nil, function(response, error)