lua/llm/completion.lua (132 lines of code) (raw):

local api = vim.api local augroup = "llm.suggestion" local llm_ls = require("llm.language_server") local config = require("llm.config") local fn = vim.fn local utils = require("llm.utils") local M = { setup_done = false, hl_group = "LLMSuggestion", ns_id = api.nvim_create_namespace("llm.suggestion"), request_id = nil, shown_suggestion = nil, suggestion = nil, suggestions_enabled = true, timer = nil, } local function new_cursor_pos(lines, row) local lines_len = #lines local row_offset = row + lines_len - 1 local col_offset = string.len(lines[lines_len]) return row_offset, col_offset end local function stop_timer() if M.timer then fn.timer_stop(M.timer) M.timer = nil end end local function cancel_request() if M.request_id then llm_ls.cancel_request(M.request_id) M.request_id = nil end end local function clear_preview() api.nvim_buf_clear_namespace(0, M.ns_id, 0, -1) end function M.cancel() stop_timer() cancel_request() clear_preview() end function M.reject() M.cancel() if M.shown_suggestion ~= nil then llm_ls.reject_completion(M.shown_suggestion) M.shown_suggestion = nil end end function M.schedule() M.reject() M.timer = fn.timer_start(config.get().debounce_ms, function() if fn.mode() == "i" then M.lsp_suggest() end end) end function M.lsp_suggest() M.request_id = llm_ls.get_completions(function(err, result, context, _conf) if err ~= nil then vim.notify("[LLM] " .. err.message, vim.log.levels.ERROR) return end local completions = result.completions local generated_text = llm_ls.extract_generation(completions) local lines = utils.split_str(generated_text, "\n") if lines == nil then return end M.suggestion = lines local col = context.params.position.character local line = context.params.position.line local extmark = { virt_text_win_col = col, virt_text = { { lines[1], M.hl_group } }, } if #lines > 1 then extmark.virt_lines = {} for i = 2, #lines do extmark.virt_lines[i - 1] = { { lines[i], M.hl_group } } end end api.nvim_buf_set_extmark(0, M.ns_id, line, col, extmark) M.shown_suggestion = result end) end function M.complete() M.cancel() if M.suggestion ~= nil then local r, c = utils.get_cursor_pos() local line = api.nvim_buf_get_lines(0, r - 1, r, false)[1] M.suggestion[1] = utils.insert_at(line, c + 1, M.suggestion[1]) local row_offset, col_offset = new_cursor_pos(M.suggestion, r) api.nvim_buf_set_lines(0, r - 1, r, false, M.suggestion) api.nvim_win_set_cursor(0, { row_offset, col_offset }) llm_ls.accept_completion(M.shown_suggestion) M.shown_suggestion = nil M.suggestion = nil end end function M.should_complete() return M.suggestions_enabled end function M.toggle_suggestion() M.suggestions_enabled = not M.suggestions_enabled local state = M.suggestions_enabled and "on" or "off" vim.notify("[LLM] Auto suggestions are " .. state, vim.log.levels.INFO) end function M.create_autocmds() api.nvim_create_augroup(augroup, { clear = true }) api.nvim_create_autocmd("InsertLeave", { pattern = "*", callback = M.reject }) api.nvim_create_autocmd("CursorMovedI", { pattern = config.get().enable_suggestions_on_files, callback = function() if M.should_complete() then M.schedule() else M.reject() M.suggestion = nil end end, }) end function M.setup(suggestions_enabled) if M.setup_done then return end vim.api.nvim_command("highlight default link " .. M.hl_group .. " Comment") M.suggestions_enabled = suggestions_enabled M.setup_done = true end return M