diff --git a/lua/gemini/completion.lua b/lua/gemini/completion.lua index 2ab8214..a6bf187 100644 --- a/lua/gemini/completion.lua +++ b/lua/gemini/completion.lua @@ -10,6 +10,8 @@ local current_suggestion = { last_request = 0, cache = {}, debounce_ms = 1000, -- Wait 1 second between requests + min_chars = 3, -- Minimum characters before triggering completion + max_context_lines = 10, -- Maximum number of context lines to send } -- Debug function @@ -168,6 +170,20 @@ function M.accept_suggestion() return true end +-- Helper function to get relevant context around cursor +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)) + + 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])) + end + end + return context +end + function M.trigger_completion() debug_print("Triggering completion...") @@ -176,148 +192,62 @@ function M.trigger_completion() vim.fn.timer_stop(current_suggestion.timer) end - -- Set up debounce timer - current_suggestion.timer = vim.fn.timer_start(300, function() - debug_print("Timer fired, checking conditions...") - - -- Get current buffer and cursor info + -- Set up debounce timer with shorter delay + current_suggestion.timer = vim.fn.timer_start(150, function() local cursor = vim.api.nvim_win_get_cursor(0) local line = cursor[1] local col = cursor[2] - - -- Get entire buffer content local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) local current_line = lines[line] - debug_print("Current line: %s, col: %d", current_line or "nil", col) - - -- Don't trigger completion if line is empty - if not current_line or current_line:match("^%s*$") then - -- Instead of skipping, let's ask for what should come next - local context = {} - local cursor_line = line - - -- Add previous lines for context - for i = 1, line - 1 do - if lines[i] and #lines[i] > 0 then - table.insert(context, string.format("L%d: %s", i, lines[i])) - end - end - - -- Add empty current line with cursor position - table.insert(context, string.format("L%d (current): ", cursor_line)) - table.insert(context, "^") - - -- Add following lines for context - for i = line + 1, #lines do - if lines[i] and #lines[i] > 0 then - table.insert(context, string.format("L%d: %s", i, lines[i])) - end - end - - local full_context = table.concat(context, "\n") - if #full_context == 0 then - debug_print("Empty context, skipping completion request") - return - end - - -- Special prompt for empty line suggestions - local prompt = string.format([[I'm editing a %s file. Here's the context around line %d where my cursor (^) is on an empty line: - -%s - -Based on the surrounding code context, what would be the most appropriate code to add at the cursor position? -Return ONLY the raw code without any markdown formatting, language hints, or code blocks.]], vim.bo.filetype, cursor_line, full_context) - - -- Get completion from Gemini - api.get_response(prompt, nil, function(response, error) - if error then - debug_print("Completion error: %s", error) - return - end - - if type(response) == "string" and #response > 0 then - vim.schedule(function() - -- Check if cursor position is still valid - local new_cursor = vim.api.nvim_win_get_cursor(0) - if new_cursor[1] == line then - show_suggestion(response, 0) -- Start from column 0 since line is empty - end - end) - end - end) - return - end - - -- Get text before cursor on current line + -- Get text before cursor local prefix = string.sub(current_line, 1, col) - debug_print("Prefix: %s", prefix) - - -- Modified conditions: only skip if the line is completely empty - -- or if we're in the middle of whitespace - if prefix:match("^%s*$") then - debug_print("Skipping: empty prefix") + -- Don't trigger if we're in comments or strings + local syntax_group = vim.fn.synIDattr(vim.fn.synID(line, col, 1), "name") + if syntax_group:match("Comment") or syntax_group:match("String") then + debug_print("Skipping: in comment or string") clear_suggestion() return end - debug_print("Preparing context...") - -- Construct context with line numbers and cursor position - local context = {} - local cursor_line = line - - -- Add all previous lines for context - for i = 1, line - 1 do - if lines[i] and #lines[i] > 0 then - table.insert(context, string.format("L%d: %s", i, lines[i])) - end - end - - -- Add current line with cursor position marker (^) - if current_line and #current_line > 0 then - local cursor_marker = string.rep(" ", col) .. "^" - table.insert(context, string.format("L%d (current): %s", cursor_line, current_line)) - table.insert(context, cursor_marker) - end - - -- Add all remaining lines - for i = line + 1, #lines do - if lines[i] and #lines[i] > 0 then - table.insert(context, string.format("L%d: %s", i, lines[i])) - end - end - - -- Combine all lines and validate - local full_context = table.concat(context, "\n") - if #full_context == 0 then - debug_print("Empty context, skipping completion request") + -- Check minimum character threshold + local trimmed_prefix = vim.trim(prefix) + if #trimmed_prefix < current_suggestion.min_chars then + debug_print("Skipping: not enough characters") + clear_suggestion() return end - -- Get file type for better context + -- Get focused context around cursor + local context = get_context_around_cursor(lines, line, current_suggestion.max_context_lines) + + -- Add cursor position marker + local cursor_marker = string.rep(" ", col) .. "^" + table.insert(context, string.format("L%d (current): %s", line, current_line)) + table.insert(context, cursor_marker) + + local full_context = table.concat(context, "\n") local file_type = vim.bo.filetype - -- Construct prompt for Gemini - local prompt = string.format([[I'm editing a %s file. Here's the context around line %d where my cursor (^) is: + -- Improved prompt for better completions + local prompt = string.format([[In this %s file, complete the code at the cursor (^) position: %s -Complete the code at the cursor position. Consider the syntax and style of the surrounding code. -Return ONLY the raw completion text without any markdown formatting, language hints, or code blocks. -The completion can be multiple lines if appropriate for the context.]], file_type, cursor_line, full_context) +Return ONLY the completion text that would naturally continue from the cursor position. +Focus on completing the current statement or block. +Consider the syntax, style, and patterns in the surrounding code.]], file_type, full_context) - -- Check cache first + -- Check cache and rate limiting local cache_key = get_cache_key(prompt) if current_suggestion.cache[cache_key] then - debug_print("Using cached completion") show_suggestion(current_suggestion.cache[cache_key], col) return end - -- Check rate limiting if should_rate_limit() then - debug_print("Rate limited, skipping completion request") + debug_print("Rate limited") return end @@ -329,13 +259,14 @@ The completion can be multiple lines if appropriate for the context.]], file_typ end if type(response) == "string" and #response > 0 then - -- Cache the response + -- Clean up response + response = vim.trim(response) current_suggestion.cache[cache_key] = response vim.schedule(function() - -- Check if cursor position is still valid + -- Verify cursor position hasn't changed significantly local new_cursor = vim.api.nvim_win_get_cursor(0) - if new_cursor[1] == line and new_cursor[2] >= col then + if new_cursor[1] == line and math.abs(new_cursor[2] - col) <= 1 then show_suggestion(response, col) end end) @@ -382,4 +313,4 @@ function M.setup() debug_print("Gemini completion setup complete") end -return M \ No newline at end of file +return M