lua/llm/language_server.lua (201 lines of code) (raw):

local api = vim.api local config = require("llm.config") local fn = vim.fn local loop = vim.loop local lsp = vim.lsp local utils = require("llm.utils") local M = { setup_done = false, client_id = nil, } local function build_binary_name() local os_uname = loop.os_uname() local arch = os_uname.machine local os = os_uname.sysname local arch_map = { x86_64 = "x86_64", i686 = "i686", arm64 = "aarch64", } local os_map = { Linux = "unknown-linux-gnu", Darwin = "apple-darwin", Windows = "pc-windows-msvc", } if os == "Linux" then local linux_distribution = utils.execute_command("cat /etc/os-release | grep '^ID=' | cut -d '=' -f 2") if linux_distribution == "alpine" then os_map.Linux = "unknown-linux-musl" elseif linux_distribution == "raspbian" then arch_map.armv7l = "arm" os_map.Linux = "unknown-linux-gnueabihf" -- else -- Add mappings for other distributions as needed end end local arch_prefix = arch_map[arch] local os_suffix = os_map[os] if not arch_prefix or not os_suffix then vim.notify("[LLM] Unsupported architecture or OS: " .. arch .. " " .. os, vim.log.levels.ERROR) return nil end return "llm-ls-" .. arch_prefix .. "-" .. os_suffix end local function build_url(bin_name) return "https://github.com/huggingface/llm-ls/releases/download/" .. config.get().lsp.version .. "/" .. bin_name .. ".gz" end local function download_and_unzip(url, path) local download_command = "curl -L -o " .. path .. ".gz " .. url local unzip_command = "gunzip -c " .. path .. ".gz > " .. path local chmod_command = "chmod +x " .. path local clean_zip_command = "rm " .. path .. ".gz" fn.system(download_command) fn.system(unzip_command) fn.system(chmod_command) fn.system(clean_zip_command) end local function download_llm_ls() local bin_dir = vim.api.nvim_call_function("stdpath", { "data" }) .. "/llm_nvim/bin" fn.system("mkdir -p " .. bin_dir) local bin_name = build_binary_name() if bin_name == nil then return nil end local full_path = bin_dir .. "/" .. bin_name .. "-" .. config.get().lsp.version if fn.filereadable(full_path) == 0 then local url = build_url(bin_name) download_and_unzip(url, full_path) vim.notify("[LLM] successfully downloaded llm-ls", vim.log.levels.INFO) end return full_path end function M.cancel_request(request_id) local client = lsp.get_client_by_id(M.client_id) if client ~= nil then client.cancel_request(request_id) end end function M.extract_generation(response) if #response == 0 then return "" end local raw_generated_text = response[1].generated_text return raw_generated_text end function M.get_completions(callback) if M.client_id == nil then return end if not lsp.buf_is_attached(0, M.client_id) then return end local params = lsp.util.make_position_params() params.model = utils.get_model() params.backend = config.get().backend params.url = utils.get_url() params.requestBody = config.get().request_body params.tokensToClear = config.get().tokens_to_clear params.apiToken = config.get().api_token params.fim = config.get().fim local tokenizerConfig = config.get().tokenizer if tokenizerConfig ~= nil and tokenizerConfig.repository ~= nil and tokenizerConfig.api_token == nil then tokenizerConfig.api_token = config.get_token() end params.tokenizerConfig = tokenizerConfig params.contextWindow = config.get().context_window params.tlsSkipVerifyInsecure = config.get().tls_skip_verify_insecure params.ide = "neovim" params.disableUrlPathCompletion = config.get().disable_url_path_completion local client = lsp.get_client_by_id(M.client_id) if client ~= nil then local status, request_id = client.request("llm-ls/getCompletions", params, callback, 0) if not status then vim.notify("[LLM] request 'llm-ls/getCompletions' failed", vim.log.levels.WARN) end return request_id else return nil end end function M.accept_completion(completion_result) local params = {} params.requestId = completion_result.request_id params.acceptedCompletion = 0 params.shownCompletions = { 0 } params.completions = completion_result.completions local client = lsp.get_client_by_id(M.client_id) if client ~= nil then local status, _ = client.request("llm-ls/acceptCompletion", params, function() end, 0) if not status then vim.notify("[LLM] request 'llm-ls/acceptCompletions' failed", vim.log.levels.WARN) end end end function M.reject_completion(completion_result) local params = {} params.requestId = completion_result.request_id params.shownCompletions = { 0 } local client = lsp.get_client_by_id(M.client_id) if client ~= nil then local status, _ = client.request("llm-ls/rejectCompletion", params, function() end, 0) if not status then vim.notify("[LLM] request 'llm-ls/rejectCompletions' failed", vim.log.levels.WARN) end end end function M.setup() if M.setup_done then return end local cmd local host = config.get().lsp.host local bin_path = config.get().lsp.bin_path or "llm-ls" if host == "localhost" then host = "127.0.0.1" end local port = config.get().lsp.port if host ~= nil and port ~= nil then cmd = lsp.rpc.connect(host, port) elseif fn.executable(bin_path) == 0 then local llm_ls_path = download_llm_ls() if llm_ls_path == nil then vim.notify("[LLM] failed to download llm-ls", vim.log.levels.ERROR) return end cmd = { llm_ls_path } else cmd = { bin_path } end local client_id = lsp.start_client({ name = "llm-ls", cmd = cmd, cmd_env = config.get().lsp.cmd_env, root_dir = vim.fs.dirname(vim.fs.find({ ".git" }, { upward = true })[1]), }) if client_id == nil then vim.notify("[LLM] Error starting llm-ls", vim.log.levels.ERROR) else local augroup = "llm.language_server" api.nvim_create_augroup(augroup, { clear = true }) api.nvim_create_autocmd("BufEnter", { group = augroup, pattern = config.get().enable_suggestions_on_files, callback = function(ev) if not lsp.buf_is_attached(ev.buf, client_id) then lsp.buf_attach_client(ev.buf, client_id) end end, }) M.client_id = client_id api.nvim_create_autocmd("VimLeavePre", { group = augroup, callback = function() lsp.stop_client(client_id) end, }) end M.setup_done = true end return M