diff --git a/lua/lazyvim/config/keymaps.lua b/lua/lazyvim/config/keymaps.lua index 946fee30..6135e823 100644 --- a/lua/lazyvim/config/keymaps.lua +++ b/lua/lazyvim/config/keymaps.lua @@ -49,6 +49,7 @@ map("n", "bD", ":bd", { desc = "Delete Buffer and Window" }) map({ "i", "n", "s" }, "", function() vim.cmd("noh") LazyVim.cmp.actions.snippet_stop() + LazyVim.cmp.actions.ai_stop() return "" end, { expr = true, desc = "Escape and Clear hlsearch" }) diff --git a/lua/lazyvim/plugins/extras/ai/copilot-native.lua b/lua/lazyvim/plugins/extras/ai/copilot-native.lua new file mode 100644 index 00000000..b4a61b20 --- /dev/null +++ b/lua/lazyvim/plugins/extras/ai/copilot-native.lua @@ -0,0 +1,150 @@ +---@diagnostic disable: missing-fields +if lazyvim_docs then + -- Native inline completions don't support being shown as regular completions + vim.g.ai_cmp = false + + -- Set to `true` in your `options.lua` to enable experimental support for Next Edit Suggestions + vim.g.copilot_nes = false +end + +if LazyVim.has_extra("ai.copilot-native") then + if not vim.lsp.inline_completion then + LazyVim.error("You need Neovim >= 0.12 to use the `ai.copilot-native` extra.") + return {} + end + if LazyVim.has_extra("ai.copilot") then + LazyVim.error("Please disable the `ai.copilot` extra if you want to use `ai.copilot-native`") + return {} + end +end + +vim.g.ai_cmp = false +local status = {} ---@type table + +return { + desc = "Native Copilot LSP integration. Requires Neovim >= 0.12", + -- copilot-language-server + { + "neovim/nvim-lspconfig", + opts = { + servers = { + copilot = { + handlers = { + didChangeStatus = function(err, res, ctx) + if err then + return + end + status[ctx.client_id] = res.kind ~= "Normal" and "error" or res.busy and "pending" or "ok" + if res.status == "Error" then + LazyVim.error("Please use `:LspCopilotSignIn` to sign in to Copilot") + end + end, + }, + -- stylua: ignore + keys = { + { + "", + function() vim.lsp.inline_completion.select({ count = 1 }) end, + desc = "Next Copilot Suggestion", + mode = { "i", "n" }, + }, + { + "", + function() vim.lsp.inline_completion.select({ count = -1 }) end, + desc = "Next Copilot Suggestion", + mode = { "i", "n" }, + }, + }, + }, + }, + setup = { + copilot = function() + vim.lsp.inline_completion.enable() + + -- Only trigger NES updates: + -- * when leaving insert mode + -- * when text is changed (in normal mode) + -- * when accepting a next edit suggestion + local nes_update = Snacks.util.debounce(function() + return vim.g.copilot_nes and require("copilot-lsp.nes").request_nes("copilot") + end, { ms = 100 }) + + vim.api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, { + group = vim.api.nvim_create_augroup("lazyvim.copilot-native.complete", { clear = true }), + callback = nes_update, + }) + + -- Accept inline suggestions or next edits + LazyVim.cmp.actions.ai_accept = function() + if vim.b.nes_state then + local nes = require("copilot-lsp.nes") + + -- Try to jump to the start of the suggestion edit. + if nes.walk_cursor_start_edit() then + return true + end + + -- apply the pending suggestion and jump to the end of the edit. + if nes.apply_pending_nes() then + nes.walk_cursor_end_edit() + nes_update() -- trigger new nes update after accept + return true + end + end + if vim.lsp.inline_completion.get() then + -- nes_update() -- ensure nes update is triggered after inline completion + return true + end + end + end, + }, + }, + }, + + -- lualine + { + "nvim-lualine/lualine.nvim", + optional = true, + event = "VeryLazy", + opts = function(_, opts) + table.insert( + opts.sections.lualine_x, + 2, + LazyVim.lualine.status(LazyVim.config.icons.kinds.Copilot, function() + local clients = vim.lsp.get_clients({ name = "copilot", bufnr = 0 }) + return #clients > 0 and status[clients[1].id] or nil + end) + ) + end, + }, + + vim.g.copilot_nes + and { + "copilotlsp-nvim/copilot-lsp", + init = function() + vim.api.nvim_create_autocmd("BufEnter", { + callback = function(ev) + local buf = ev.buf + local client = vim.lsp.get_clients({ name = "copilot", bufnr = buf })[1] + if not client then + return + end + client:notify("textDocument/didFocus", { + textDocument = { + uri = vim.uri_from_bufnr(buf), + }, + }) + end, + }) + + LazyVim.cmp.actions.ai_stop = function() + require("copilot-lsp.nes").clear() + end + end, + keys = { + -- nes is also useful in normal mode + { "", LazyVim.cmp.map({ "ai_accept" }, ""), mode = { "n" }, expr = true }, + }, + } + or nil, +} diff --git a/lua/lazyvim/util/cmp.lua b/lua/lazyvim/util/cmp.lua index 0641c892..2637b0d3 100644 --- a/lua/lazyvim/util/cmp.lua +++ b/lua/lazyvim/util/cmp.lua @@ -18,6 +18,7 @@ M.actions = { vim.snippet.stop() end end, + ai_stop = function() end, } ---@param actions string[]