Review and cleanup

This commit is contained in:
Jonas Widen 2025-03-18 18:17:52 +01:00
parent 81c200df66
commit 2c641faf11

View File

@ -14,6 +14,26 @@ local current_suggestion = {
max_context_lines = 10, -- Maximum number of context lines to send
}
-- Helper function to get visible lines around cursor
local function get_visible_lines()
local win = vim.api.nvim_get_current_win()
local cursor_line = vim.api.nvim_win_get_cursor(0)[1]
local top_line = vim.fn.line('w0')
local bottom_line = vim.fn.line('w$')
-- Get all visible lines
local lines = vim.api.nvim_buf_get_lines(0, top_line - 1, bottom_line, false)
local relative_cursor = cursor_line - top_line + 1
return lines, relative_cursor
end
-- Helper function to get current line indent
local function get_line_indent(line)
local indent = line:match("^%s+") or ""
return indent
end
-- Debug function
local function debug_print(...)
vim.notify(string.format(...), vim.log.levels.INFO)
@ -52,44 +72,27 @@ end
local function show_suggestion(suggestion, start_col)
clear_suggestion()
-- Split suggestion into lines
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1] - 1
local current_line = vim.api.nvim_get_current_line()
-- Get current line indent
local indent = get_line_indent(current_line)
-- Split suggestion into lines and apply indent to all lines except first
local suggestion_lines = vim.split(suggestion, "\n")
for i = 2, #suggestion_lines do
suggestion_lines[i] = indent .. suggestion_lines[i]
end
if #suggestion_lines == 0 then return end
-- Get current line info
local line = vim.api.nvim_win_get_cursor(0)[1] - 1
local line_text = vim.api.nvim_buf_get_lines(0, line, line + 1, true)[1]
-- Validate start_col
if not line_text then return end
start_col = math.min(start_col, #line_text)
if start_col < 0 then start_col = 0 end
-- Get text before cursor on current line
local input_before_cursor = string.sub(line_text, 1, start_col)
local first_line = suggestion_lines[1]
-- If the suggestion starts with what's already typed, remove that part
if vim.startswith(first_line, input_before_cursor) then
first_line = string.sub(first_line, #input_before_cursor + 1)
end
if first_line == "" and #suggestion_lines == 1 then return end
current_suggestion.text = suggestion
current_suggestion.start_col = start_col
-- Show first line as virtual text
if first_line ~= "" then
-- Ensure we're not exceeding line length
local safe_col = math.min(start_col, #line_text)
vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, safe_col, {
virt_text = {{first_line, 'GeminiSuggestion'}},
virt_text_pos = 'inline',
virt_text_hide = true,
-- Show ghost text
vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, start_col, {
virt_text = {{suggestion_lines[1], 'GeminiSuggestion'}},
virt_text_pos = 'overlay',
hl_mode = 'combine',
})
end
-- Show remaining lines as virtual lines
if #suggestion_lines > 1 then
@ -98,16 +101,14 @@ local function show_suggestion(suggestion, start_col)
table.insert(virt_lines, {{suggestion_lines[i], 'GeminiSuggestion'}})
end
-- Use safe column position for virtual lines
local safe_col = math.min(start_col, #line_text)
vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, safe_col, {
vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, start_col, {
virt_lines = virt_lines,
virt_lines_above = false,
})
end
debug_print("Showing suggestion: '%s'", first_line)
current_suggestion.text = table.concat(suggestion_lines, "\n")
current_suggestion.start_col = start_col
end
function M.accept_suggestion()
@ -206,66 +207,45 @@ local function get_context_around_cursor(lines, current_line, max_lines)
end
function M.trigger_completion()
debug_print("Triggering completion...")
-- Clear any existing timer
if current_suggestion.timer then
vim.fn.timer_stop(current_suggestion.timer)
end
-- Set up debounce timer
current_suggestion.timer = vim.fn.timer_start(150, function()
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1]
local line = cursor[1] - 1
local col = cursor[2]
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local current_line = lines[line]
-- Don't trigger if at end of file or empty line
-- Get visible context
local visible_lines, relative_cursor = get_visible_lines()
local current_line = visible_lines[relative_cursor]
if not current_line or current_line == "" then
clear_suggestion()
return
end
-- Get text before cursor
local prefix = string.sub(current_line, 1, col)
-- Don't trigger if we're in comments or strings
local syntax_group = vim.fn.synIDattr(vim.fn.synID(line, col, 1), "name")
-- Don't trigger in comments or strings
local syntax_group = vim.fn.synIDattr(vim.fn.synID(cursor[1], 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
-- 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 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")
-- Prepare context for AI
local context = table.concat(visible_lines, "\n")
local file_type = vim.bo.filetype
-- Improved prompt for better completions
local prompt = string.format([[In this %s file, complete the code at the cursor (^) position:
local prompt = string.format([[
In this %s file, complete the code at line %d, column %d:
%s
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.
Do not repeat any text that appears before the cursor.]], file_type, full_context)
Consider the visible context, syntax, and code style.
Do not repeat any text that appears before the cursor.]],
file_type, cursor[1], col + 1, context)
-- Check cache and rate limiting
local cache_key = get_cache_key(prompt)
@ -274,28 +254,19 @@ Do not repeat any text that appears before the cursor.]], file_type, full_contex
return
end
if should_rate_limit() then
debug_print("Rate limited")
return
end
if should_rate_limit() then return end
-- Get completion from Gemini
api.get_response(prompt, nil, function(response, error)
if error then
debug_print("Completion error: %s", error)
return
end
if error then return end
if type(response) == "string" and #response > 0 then
-- Clean up response and remove any leading whitespace/indentation
response = vim.trim(response)
response = response:gsub("^%s+", "")
current_suggestion.cache[cache_key] = response
vim.schedule(function()
-- Verify cursor position hasn't changed significantly
local new_cursor = vim.api.nvim_win_get_cursor(0)
if new_cursor[1] == line and math.abs(new_cursor[2] - col) <= 1 then
if new_cursor[1] == cursor[1] and math.abs(new_cursor[2] - col) <= 1 then
show_suggestion(response, col)
end
end)
@ -310,12 +281,12 @@ function M.setup()
vim.api.nvim_set_hl(0, 'GeminiSuggestion', {
fg = '#666666',
italic = true,
blend = 15 -- Makes the ghost text slightly transparent
})
-- Map Tab to accept suggestion or behave normally
-- Map Tab to accept suggestion
vim.keymap.set('i', '<Tab>', 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('<Tab>', true, true, true), 'n', true)
end
end, { expr = false, noremap = true })
@ -324,7 +295,6 @@ function M.setup()
vim.api.nvim_create_autocmd("TextChangedI", {
pattern = "*",
callback = function()
-- Only trigger if enabled
if vim.b.gemini_completion_enabled then
M.trigger_completion()
end
@ -338,8 +308,6 @@ function M.setup()
clear_suggestion()
end
})
debug_print("Gemini completion setup complete")
end
return M