Skip to content

Commit 97a9576

Browse files
committed
WIP
1 parent 81d289a commit 97a9576

File tree

7 files changed

+244
-12
lines changed

7 files changed

+244
-12
lines changed

README.md

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# copilot.lua
22

33
This plugin is the pure lua replacement for [github/copilot.vim](https://github.com/github/copilot.vim).
4+
A huge thank you to @tris203 for the code behind the nes functionality ([copilot-lsp](https://github.com/copilotlsp-nvim/copilot-lsp)).
45

56
<details>
67
<summary>Motivation behind `copilot.lua`</summary>
@@ -48,7 +49,11 @@ Install the plugin with your preferred plugin manager.
4849
For example, with [packer.nvim](https://github.com/wbthomason/packer.nvim):
4950

5051
```lua
51-
use { "zbirenbaum/copilot.lua" }
52+
use { "zbirenbaum/copilot.lua"
53+
requires = {
54+
"copilotlsp-nvim/copilot-lsp", -- (optional) for NES functionality
55+
},
56+
}
5257
```
5358

5459
### Authentication
@@ -93,6 +98,9 @@ For example:
9398
```lua
9499
use {
95100
"zbirenbaum/copilot.lua",
101+
requires = {
102+
"copilotlsp-nvim/copilot-lsp", -- (optional) for NES functionality
103+
},
96104
cmd = "Copilot",
97105
event = "InsertEnter",
98106
config = function()
@@ -136,16 +144,14 @@ require('copilot').setup({
136144
dismiss = "<C-]>",
137145
},
138146
},
139-
filetypes = {
140-
yaml = false,
141-
markdown = false,
142-
help = false,
143-
gitcommit = false,
144-
gitrebase = false,
145-
hgcommit = false,
146-
svn = false,
147-
cvs = false,
148-
["."] = false,
147+
nes = {
148+
enabled = false, -- requires copilot-lsp as a dependency
149+
auto_trigger = false,
150+
keymap = {
151+
accept_and_goto = false,
152+
accept = false,
153+
dismiss = false,
154+
},
149155
},
150156
auth_provider_url = nil, -- URL to authentication provider, if not "https://github.com/"
151157
logger = {
@@ -270,6 +276,43 @@ require("copilot.suggestion").toggle_auto_trigger()
270276
```
271277
These can also be accessed through the `:Copilot suggestion <function>` command (eg. `:Copilot suggestion accept`).
272278

279+
### nes (next edit suggestion)
280+
281+
When `enabled` is `true`, copilot will provide suggestions based on the next edit you are likely to make, through [copilot-lsp](https://github.com/copilotlsp-nvim/copilot-lsp).
282+
When `auto_trigger` is `true`, copilot will automatically provide suggestions as you type.
283+
When `auto_trigger` is `false`, you can use the `accept_and_goto` or `accept` keymaps to accept the suggestion and move to the next one.
284+
If there is no suggestion, the keymaps will pass through the original keymap.
285+
286+
`coilot-lsp` has a few configurations built-in as well, for additional configurations, please refer to the [copilot-lsp documentation](https://github.com/copilotlsp-nvim/copilot-lsp/blob/main/README.md).
287+
These configurations should be set in the `init` function of the `copilot-lsp` dependency.
288+
289+
```lua
290+
use {
291+
"zbirenbaum/copilot.lua",
292+
requires = {
293+
"copilotlsp-nvim/copilot-lsp",
294+
init = function()
295+
vim.g.copilot_nes_debounce = 500
296+
end,
297+
},
298+
cmd = "Copilot",
299+
event = "InsertEnter",
300+
config = function()
301+
require("copilot").setup({
302+
nes = {
303+
enabled = true,
304+
auto_trigger = false,
305+
keymap = {
306+
accept_and_goto = "<leader>p",
307+
accept = false,
308+
dismiss = "<Esc>",
309+
},
310+
},
311+
})
312+
end,
313+
}
314+
```
315+
273316
### filetypes
274317

275318
Specify filetypes for attaching copilot.

lua/copilot/client/config.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ function M.prepare_client_config(overrides, client)
122122
if token_env_set then
123123
require("copilot.auth").signin()
124124
end
125+
126+
require("copilot.nes").setup(lsp_client)
125127
end)
126128
end,
127129
on_exit = function(code, _, client_id)

lua/copilot/config/init.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ local logger = require("copilot.logger")
55
---@field suggestion SuggestionConfig
66
---@field logger LoggerConfig
77
---@field server ServerConfig
8+
---@field nes NesConfig
89
---@field filetypes table<string, boolean> Filetypes to enable Copilot for
910
---@field auth_provider_url string|nil URL for the authentication provider
1011
---@field workspace_folders string[] Workspace folders to enable Copilot for
@@ -23,6 +24,7 @@ local M = {
2324
suggestion = require("copilot.config.suggestion").default,
2425
logger = require("copilot.config.logger").default,
2526
server = require("copilot.config.server").default,
27+
nes = require("copilot.config.nes").default,
2628
root_dir = require("copilot.config.root_dir").default,
2729
should_attach = require("copilot.config.should_attach").default,
2830
filetypes = {},
@@ -59,6 +61,7 @@ end
5961
function M.validate(config)
6062
vim.validate("panel", config.panel, "table")
6163
vim.validate("suggestion", config.suggestion, "table")
64+
vim.validate("nes", config.nes, "table")
6265
vim.validate("logger", config.logger, "table")
6366
vim.validate("server", config.server, "table")
6467
vim.validate("filetypes", config.filetypes, "table")
@@ -76,6 +79,7 @@ function M.validate(config)
7679
require("copilot.config.server").validate(config.server)
7780
require("copilot.config.root_dir").validate(config.root_dir)
7881
require("copilot.config.should_attach").validate(config.should_attach)
82+
require("copilot.config.nes").validate(config.nes)
7983
end
8084

8185
return M

lua/copilot/config/nes.lua

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
local logger = require("copilot.logger")
2+
---@class NesKeymap
3+
---@field accept_and_goto string|false Keymap to accept the suggestion and go to the end of the suggestion
4+
---@field accept string|false Keymap to accept the suggestion
5+
---@field dismiss string|false Keymap to dismiss the suggestion
6+
7+
---@class NesConfig
8+
---@field enabled boolean Whether to enable nes (next edit suggestions)
9+
---@field auto_trigger boolean Whether to automatically trigger next edit suggestions
10+
---@field keymap NesKeymap Keymaps for nes actions
11+
12+
local M = {
13+
---@type NesConfig
14+
default = {
15+
enabled = false,
16+
auto_trigger = false,
17+
keymap = {
18+
accept_and_goto = false,
19+
accept = false,
20+
dismiss = false,
21+
},
22+
},
23+
}
24+
25+
---@type NesConfig
26+
M.config = vim.deepcopy(M.default)
27+
28+
---@param opts? NesConfig
29+
function M.setup(opts)
30+
opts = opts or {}
31+
M.config = vim.tbl_deep_extend("force", M.default, opts)
32+
end
33+
34+
---@param config NesConfig
35+
function M.validate(config)
36+
vim.validate("enabled", config.enabled, "boolean")
37+
vim.validate("auto_trigger", config.auto_trigger, "boolean")
38+
vim.validate("keymap", config.keymap, "table")
39+
vim.validate("keymap.accept_and_goto", config.keymap.accept_and_goto, { "string", "boolean" })
40+
vim.validate("keymap.accept", config.keymap.accept, { "string", "boolean" })
41+
vim.validate("keymap.dismiss", config.keymap.dismiss, { "string", "boolean" })
42+
43+
if config.enabled then
44+
local has_nes, _ = pcall(function()
45+
require("copilot-lsp.api")
46+
end)
47+
48+
if not has_nes then
49+
logger.error(
50+
"copilot-lsp is not available, disabling nes.\nPlease refer to the documentation and ensure it is installed."
51+
)
52+
config.enabled = false
53+
end
54+
end
55+
end
56+
57+
return M

lua/copilot/highlight.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
local logger = require("copilot.logger")
2+
local config = require("copilot.config")
3+
14
local M = {
25
group = {
36
CopilotAnnotation = "CopilotAnnotation",
@@ -19,6 +22,16 @@ function M.setup()
1922
vim.api.nvim_set_hl(0, from_group, { link = to_group })
2023
end
2124
end
25+
26+
if config.nes.enabled then
27+
local ok, err = pcall(function()
28+
require("copilot-lsp.api").set_hl()
29+
end)
30+
31+
if not ok then
32+
logger.error("Error setting copilot-lsp highlights: ", err)
33+
end
34+
end
2235
end)
2336
end
2437

lua/copilot/logger/init.lua

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ function M.handle_lsp_progress(_, result, _)
171171
M.trace(string.format("LSP progress - token %s", result.token), result.value)
172172
end
173173

174+
-- Known noisy errors that we do not want to show as they seem to be out of our control
175+
---@param msg string
176+
---@return boolean
177+
local function force_log_to_trace(msg)
178+
if
179+
msg:match(".*Request textDocument/copilotInlineEdit: AbortError: The operation was aborted.*")
180+
or msg:match(".*AsyncCompletionManager.*Request errored with AbortError: The operation was aborted.*")
181+
then
182+
return true
183+
end
184+
185+
return false
186+
end
187+
174188
function M.handle_log_lsp_messages(_, result, _)
175189
if not result then
176190
return
@@ -179,7 +193,7 @@ function M.handle_log_lsp_messages(_, result, _)
179193
local message = string.format("LSP message: %s", result.message)
180194
local message_type = result.type --[[@as integer]]
181195

182-
if message_type == 1 then
196+
if message_type == 1 and not force_log_to_trace(message) then
183197
M.error(message)
184198
elseif message_type == 2 then
185199
M.warn(message)

lua/copilot/nes/init.lua

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
local config = require("copilot.config")
2+
local logger = require("copilot.logger")
3+
local util = require("copilot.util")
4+
5+
local M = {
6+
initialized = false,
7+
}
8+
9+
---@param goto_end boolean Whether to move the cursor to the end of the accepted suggestion
10+
---@return boolean
11+
local function accept_suggestion(goto_end)
12+
local nes_api = require("copilot-lsp.api")
13+
local result = nes_api.nes_apply_pending_nes()
14+
15+
if goto_end then
16+
nes_api.nes_walk_cursor_end_edit()
17+
end
18+
19+
return result
20+
end
21+
22+
---@class NesKeymap
23+
local function set_keymap(keymap)
24+
if keymap.accept_and_goto then
25+
vim.keymap.set("n", keymap.accept_and_goto, function()
26+
if not accept_suggestion(true) then
27+
-- Pass through the key
28+
vim.api.nvim_feedkeys(keymap.accept_and_goto, "n", false)
29+
end
30+
end, {
31+
desc = "[copilot] (nes) accept suggestion and go to end",
32+
silent = true,
33+
})
34+
end
35+
36+
if keymap.accept then
37+
vim.keymap.set("n", keymap.accept, function()
38+
if not accept_suggestion(false) then
39+
-- Pass through the key
40+
vim.api.nvim_feedkeys(keymap.accept, "n", false)
41+
end
42+
end, {
43+
desc = "[copilot] (nes) accept suggestion",
44+
silent = true,
45+
})
46+
end
47+
48+
if keymap.dismiss then
49+
vim.keymap.set("n", keymap.dismiss, function()
50+
local cleared = require("copilot-lsp.api").nes_clear()
51+
-- Pass through the key
52+
if not cleared then
53+
vim.api.nvim_feedkeys(keymap.dismiss, "n", false)
54+
end
55+
end, {
56+
desc = "[copilot] (nes) dismiss suggestion",
57+
silent = true,
58+
})
59+
end
60+
end
61+
62+
---@param keymap NesKeymap
63+
local function unset_keymap(keymap)
64+
util.unset_keymap_if_exists("n", keymap.accept_and_goto)
65+
util.unset_keymap_if_exists("n", keymap.accept)
66+
util.unset_keymap_if_exists("n", keymap.dismiss)
67+
end
68+
69+
---@param lsp_client vim.lsp.Client
70+
function M.setup(lsp_client)
71+
if not config.nes.enabled then
72+
return
73+
end
74+
75+
local au = vim.api.nvim_create_augroup("copilotlsp.init", { clear = true })
76+
77+
local ok, err = pcall(function()
78+
require("copilot-lsp.api").nes_lsp_on_init(lsp_client, au)
79+
end)
80+
81+
if ok then
82+
logger.info("copilot-lsp nes loaded")
83+
else
84+
logger.error("copilot-lsp nes failed to load:", err)
85+
end
86+
87+
set_keymap(config.nes.keymap)
88+
M.initialized = true
89+
end
90+
91+
function M.teardown()
92+
if not M.initialized then
93+
return
94+
end
95+
96+
unset_keymap(config.nes.keymap)
97+
end
98+
99+
return M

0 commit comments

Comments
 (0)