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 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 -- Debug function
local function debug_print(...) local function debug_print(...)
vim.notify(string.format(...), vim.log.levels.INFO) vim.notify(string.format(...), vim.log.levels.INFO)
@ -52,44 +72,27 @@ end
local function show_suggestion(suggestion, start_col) local function show_suggestion(suggestion, start_col)
clear_suggestion() 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") 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 if #suggestion_lines == 0 then return end
-- Get current line info -- Show ghost text
local line = vim.api.nvim_win_get_cursor(0)[1] - 1 vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, start_col, {
local line_text = vim.api.nvim_buf_get_lines(0, line, line + 1, true)[1] virt_text = {{suggestion_lines[1], 'GeminiSuggestion'}},
virt_text_pos = 'overlay',
-- Validate start_col hl_mode = 'combine',
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,
}) })
end
-- Show remaining lines as virtual lines -- Show remaining lines as virtual lines
if #suggestion_lines > 1 then if #suggestion_lines > 1 then
@ -98,16 +101,14 @@ local function show_suggestion(suggestion, start_col)
table.insert(virt_lines, {{suggestion_lines[i], 'GeminiSuggestion'}}) table.insert(virt_lines, {{suggestion_lines[i], 'GeminiSuggestion'}})
end end
-- Use safe column position for virtual lines vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, start_col, {
local safe_col = math.min(start_col, #line_text)
vim.api.nvim_buf_set_extmark(0, current_suggestion.namespace_id, line, safe_col, {
virt_lines = virt_lines, virt_lines = virt_lines,
virt_lines_above = false, virt_lines_above = false,
}) })
end end
debug_print("Showing suggestion: '%s'", first_line) current_suggestion.text = table.concat(suggestion_lines, "\n")
current_suggestion.start_col = start_col
end end
function M.accept_suggestion() function M.accept_suggestion()
@ -206,66 +207,45 @@ local function get_context_around_cursor(lines, current_line, max_lines)
end end
function M.trigger_completion() function M.trigger_completion()
debug_print("Triggering completion...")
-- Clear any existing timer
if current_suggestion.timer then if current_suggestion.timer then
vim.fn.timer_stop(current_suggestion.timer) vim.fn.timer_stop(current_suggestion.timer)
end end
-- Set up debounce timer
current_suggestion.timer = vim.fn.timer_start(150, function() current_suggestion.timer = vim.fn.timer_start(150, function()
local cursor = vim.api.nvim_win_get_cursor(0) local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1] local line = cursor[1] - 1
local col = cursor[2] 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 if not current_line or current_line == "" then
clear_suggestion() clear_suggestion()
return return
end end
-- Get text before cursor -- Don't trigger in comments or strings
local prefix = string.sub(current_line, 1, col) local syntax_group = vim.fn.synIDattr(vim.fn.synID(cursor[1], col, 1), "name")
-- 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 if syntax_group:match("Comment") or syntax_group:match("String") then
debug_print("Skipping: in comment or string")
clear_suggestion() clear_suggestion()
return return
end end
-- Check minimum character threshold -- Prepare context for AI
local trimmed_prefix = vim.trim(prefix) local context = table.concat(visible_lines, "\n")
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")
local file_type = vim.bo.filetype local file_type = vim.bo.filetype
-- Improved prompt for better completions local prompt = string.format([[
local prompt = string.format([[In this %s file, complete the code at the cursor (^) position: In this %s file, complete the code at line %d, column %d:
%s %s
Return ONLY the completion text that would naturally continue from the cursor position. Return ONLY the completion text that would naturally continue from the cursor position.
Focus on completing the current statement or block. Focus on completing the current statement or block.
Consider the syntax, style, and patterns in the surrounding code. Consider the visible context, syntax, and code style.
Do not repeat any text that appears before the cursor.]], file_type, full_context) Do not repeat any text that appears before the cursor.]],
file_type, cursor[1], col + 1, context)
-- Check cache and rate limiting -- Check cache and rate limiting
local cache_key = get_cache_key(prompt) 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 return
end end
if should_rate_limit() then if should_rate_limit() then return end
debug_print("Rate limited")
return
end
-- Get completion from Gemini -- Get completion from Gemini
api.get_response(prompt, nil, function(response, error) api.get_response(prompt, nil, function(response, error)
if error then if error then return end
debug_print("Completion error: %s", error)
return
end
if type(response) == "string" and #response > 0 then if type(response) == "string" and #response > 0 then
-- Clean up response and remove any leading whitespace/indentation
response = vim.trim(response) response = vim.trim(response)
response = response:gsub("^%s+", "")
current_suggestion.cache[cache_key] = response current_suggestion.cache[cache_key] = response
vim.schedule(function() vim.schedule(function()
-- Verify cursor position hasn't changed significantly
local new_cursor = vim.api.nvim_win_get_cursor(0) 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) show_suggestion(response, col)
end end
end) end)
@ -310,12 +281,12 @@ function M.setup()
vim.api.nvim_set_hl(0, 'GeminiSuggestion', { vim.api.nvim_set_hl(0, 'GeminiSuggestion', {
fg = '#666666', fg = '#666666',
italic = true, 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() vim.keymap.set('i', '<Tab>', function()
if not M.accept_suggestion() then 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) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Tab>', true, true, true), 'n', true)
end end
end, { expr = false, noremap = true }) end, { expr = false, noremap = true })
@ -324,7 +295,6 @@ function M.setup()
vim.api.nvim_create_autocmd("TextChangedI", { vim.api.nvim_create_autocmd("TextChangedI", {
pattern = "*", pattern = "*",
callback = function() callback = function()
-- Only trigger if enabled
if vim.b.gemini_completion_enabled then if vim.b.gemini_completion_enabled then
M.trigger_completion() M.trigger_completion()
end end
@ -338,8 +308,6 @@ function M.setup()
clear_suggestion() clear_suggestion()
end end
}) })
debug_print("Gemini completion setup complete")
end end
return M return M