Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 29 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,63 +19,52 @@

## Usage

To use the plugin, add the following to your Neovim configuration:

```lua
return {
{
"copilotlsp-nvim/copilot-lsp",
init = function()
vim.g.copilot_nes_debounce = 500
vim.lsp.enable("copilot_ls")
vim.keymap.set("n", "<tab>", function()
local bufnr = vim.api.nvim_get_current_buf()
local state = vim.b[bufnr].nes_state
if state then
-- Try to jump to the start of the suggestion edit.
-- If already at the start, then apply the pending suggestion and jump to the end of the edit.
local _ = require("copilot-lsp.nes").walk_cursor_start_edit()
or (
require("copilot-lsp.nes").apply_pending_nes()
and require("copilot-lsp.nes").walk_cursor_end_edit()
)
return nil
else
-- Resolving the terminal's inability to distinguish between `TAB` and `<C-i>` in normal mode
return "<C-i>"
end
end, { desc = "Accept Copilot NES suggestion", expr = true })
end,
opts = {},
}
```

#### Clearing suggestions with Escape
## Default configuration

You can map the `<Esc>` key to clear suggestions while preserving its other functionality:
You don't need to configure anything, but you can customize the defaults: `move_count_threshold` is the most important setting - it controls how many cursor moves happen before suggestions are cleared. Higher values make suggestions persist longer.

```lua
-- Clear copilot suggestion with Esc if visible, otherwise preserve default Esc behavior
vim.keymap.set("n", "<esc>", function()
if not require("copilot-lsp.nes").clear() then
-- fallback to other functionality
end
end, { desc = "Clear Copilot suggestion or fallback" })
require('copilot-lsp').setup({
nes = {
auto_trigger = false,
debounce = 500,
move_count_threshold = 3,
distance_threshold = 40,
clear_on_large_distance = true,
count_horizontal_moves = true,
reset_on_approaching = true,
}
keymaps = {
request_nes = nil,
accept_nes = nil,
clear_nes = nil,
},
})
```

## Default Configuration

### NES (Next Edit Suggestion) Smart Clearing
### Keymap Configuration

You don’t need to configure anything, but you can customize the defaults:
`move_count_threshold` is the most important. It controls how many cursor moves happen before suggestions are cleared. Higher = slower to clear.
**By default, no keymaps are set** to avoid breaking existing configurations, you need to explicitly set them:

```lua
require('copilot-lsp').setup({
nes = {
move_count_threshold = 3, -- Clear after 3 cursor movements
}
keymaps = {
request_nes = "<leader>re",
accept_nes = "<tab>",
clear_nes = "<esc>",
},
})
```

When `accept_nes` is set to `<tab>`, it will fallback to `<C-i>` in normal mode when no suggestion is active (resolving the terminal's inability to distinguish between Tab and Ctrl-I).

### Blink Integration

```lua
Expand Down
41 changes: 0 additions & 41 deletions lsp/copilot_ls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,45 +29,4 @@ return {
end,
}),
root_dir = vim.uv.cwd(),
on_init = function(client)
local au = vim.api.nvim_create_augroup("copilotlsp.init", { clear = true })
--NOTE: Inline Completions
--TODO: We dont currently use this code path, so comment for now until a UI is built
-- vim.api.nvim_create_autocmd("TextChangedI", {
-- callback = function()
-- inline_completion.request_inline_completion(2)
-- end,
-- group = au,
-- })

-- TODO: make this configurable for key maps, or just expose commands to map in config
-- vim.keymap.set("i", "<c-i>", function()
-- inline_completion.request_inline_completion(1)
-- end)

--NOTE: NES Completions
local debounced_request = require("copilot-lsp.util").debounce(
require("copilot-lsp.nes").request_nes,
vim.g.copilot_nes_debounce or 500
)
vim.api.nvim_create_autocmd({ "TextChangedI", "TextChanged" }, {
callback = function()
debounced_request(client)
end,
group = au,
})

--NOTE: didFocus
vim.api.nvim_create_autocmd("BufEnter", {
callback = function()
local td_params = vim.lsp.util.make_text_document_params()
client:notify("textDocument/didFocus", {
textDocument = {
uri = td_params.uri,
},
})
end,
group = au,
})
end,
}
15 changes: 15 additions & 0 deletions lua/copilot-lsp/config.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
---@class copilotlsp.config.keymaps
---@field request_nes? string? Keymap to request a suggestion
---@field accept_nes? string? Keymap to accept a suggestion
---@field clear_nes? string? Keymap to clear a suggestion

---@class copilotlsp.config.nes
---@field auto_trigger boolean Whether to automatically trigger suggestions
---@field debounce integer Debounce time in milliseconds
---@field move_count_threshold integer Number of cursor movements before clearing suggestion
---@field distance_threshold integer Maximum line distance before clearing suggestion
---@field clear_on_large_distance boolean Whether to clear suggestion when cursor is far away
Expand All @@ -9,14 +16,22 @@ local M = {}

---@class copilotlsp.config
---@field nes copilotlsp.config.nes
---@field keymaps copilotlsp.config.keymaps
M.defaults = {
nes = {
auto_trigger = false,
debounce = 500,
move_count_threshold = 3,
distance_threshold = 40,
clear_on_large_distance = true,
count_horizontal_moves = true,
reset_on_approaching = true,
},
keymaps = {
request_nes = nil,
accept_nes = nil,
clear_nes = nil,
},
}

---@type copilotlsp.config
Expand Down
73 changes: 73 additions & 0 deletions lua/copilot-lsp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,79 @@ M.config = config.config
function M.setup(opts)
config.setup(opts)
M.config = config.config

vim.lsp.config("copilot_ls", {
on_init = function(client)
vim.api.nvim_create_autocmd("BufEnter", {
callback = function()
local td_params = vim.lsp.util.make_text_document_params()
client:notify("textDocument/didFocus", {
textDocument = {
uri = td_params.uri,
},
})
end,
group = vim.api.nvim_create_augroup("copilot_ls", { clear = true }),
desc = "Trigger when entering a buffer",
})

if M.config.nes.auto_trigger then
local debounced_request =
require("copilot-lsp.util").debounce(require("copilot-lsp.nes").request_nes, M.config.nes.debounce)
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
group = vim.api.nvim_create_augroup("copilot-lsp", { clear = true }),
callback = function()
debounced_request(client)
end,
desc = "Debounced request for Copilot NES",
})
end

if M.config.keymaps.request_nes then
vim.keymap.set("n", M.config.keymaps.request_nes, function()
require("copilot-lsp.nes").request_nes(client)
end, { desc = "Request Copilot NES" })
end

if M.config.keymaps.accept_nes then
local is_tab = M.config.keymaps.accept_nes:lower() == "<tab>"
vim.keymap.set("n", M.config.keymaps.accept_nes, function()
local bufnr = vim.api.nvim_get_current_buf()
local state = vim.b[bufnr].nes_state
if state then
local _ = require("copilot-lsp.nes").walk_cursor_start_edit()
or (
require("copilot-lsp.nes").apply_pending_nes()
and require("copilot-lsp.nes").walk_cursor_end_edit()
)
return nil
else
-- Only fallback to <C-i> if the mapped key is <tab>
-- This resolves terminal's inability to distinguish between TAB and <C-i>
if is_tab then
return "<C-i>"
end
return nil
end
end, {
desc = "Accept Copilot NES",
expr = is_tab,
})
end

if M.config.keymaps.clear_nes then
vim.keymap.set("n", M.config.keymaps.clear_nes, function()
if not require("copilot-lsp.nes").clear() then
return M.config.keymaps.clear_nes
end

return nil
end, { desc = "Clear Copilot NES", expr = true })
end
end,
})

vim.lsp.enable("copilot_ls")
end

return M
Loading