Skip to content

Commit 82f4efe

Browse files
committed
WIP
1 parent 81d289a commit 82f4efe

File tree

7 files changed

+231
-12
lines changed

7 files changed

+231
-12
lines changed

README.md

Lines changed: 35 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,8 @@ 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
149149
},
150150
auth_provider_url = nil, -- URL to authentication provider, if not "https://github.com/"
151151
logger = {
@@ -270,6 +270,30 @@ require("copilot.suggestion").toggle_auto_trigger()
270270
```
271271
These can also be accessed through the `:Copilot suggestion <function>` command (eg. `:Copilot suggestion accept`).
272272

273+
### nes (next edit suggestion)
274+
275+
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).
276+
For additional configurations, please refer to the [copilot-lsp documentation](https://github.com/copilotlsp-nvim/copilot-lsp/blob/main/README.md).
277+
278+
Configurations are better placed inside the require/dependency definition as such:
279+
280+
```lua
281+
use {
282+
"zbirenbaum/copilot.lua",
283+
requires = {
284+
"copilotlsp-nvim/copilot-lsp",
285+
init = function()
286+
vim.g.copilot_nes_debounce = 500
287+
end,
288+
},
289+
cmd = "Copilot",
290+
event = "InsertEnter",
291+
config = function()
292+
require("copilot").setup({})
293+
end,
294+
}
295+
```
296+
273297
### filetypes
274298

275299
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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
local function accept_suggestion(goto_end)
11+
local nes_api = require("copilot-lsp.api")
12+
nes_api.nes_apply_pending_nes()
13+
14+
if goto_end then
15+
nes_api.nes_walk_cursor_end_edit()
16+
end
17+
end
18+
19+
local previous_keymaps = {}
20+
---@class NesKeymap
21+
local function set_keymap(keymap)
22+
if keymap.accept_and_goto then
23+
vim.keymap.set("n", keymap.accept_and_goto, function()
24+
accept_suggestion(true)
25+
end, {
26+
desc = "[copilot] (nes) accept suggestion and go to end",
27+
silent = true,
28+
})
29+
end
30+
31+
if keymap.accept then
32+
vim.keymap.set("n", keymap.accept, function()
33+
accept_suggestion(false)
34+
end, {
35+
desc = "[copilot] (nes) accept suggestion",
36+
silent = true,
37+
})
38+
end
39+
40+
-- This is to get cancellation keys to work like <Esc> when there is nothing to cancel
41+
if keymap.dismiss then
42+
-- Save previous mapping
43+
local rhs = vim.fn.maparg(keymap.dismiss, "n", false, true)
44+
if rhs and rhs.lhs ~= "" then
45+
previous_keymaps[keymap.dismiss] = rhs
46+
else
47+
previous_keymaps[keymap.dismiss] = nil
48+
end
49+
50+
vim.keymap.set("n", keymap.dismiss, function()
51+
local cleared = require("copilot-lsp.api").nes_clear()
52+
if not cleared and previous_keymaps[keymap.dismiss] then
53+
-- Call the previous mapping
54+
local prev = previous_keymaps[keymap.dismiss]
55+
if prev.callback then
56+
prev.callback()
57+
elseif prev.rhs and prev.rhs ~= "" then
58+
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(prev.rhs, true, false, true), "n", false)
59+
end
60+
end
61+
end, {
62+
desc = "[copilot] (nes) dismiss suggestion",
63+
silent = true,
64+
})
65+
end
66+
end
67+
68+
---@param keymap NesKeymap
69+
local function unset_keymap(keymap)
70+
util.unset_keymap_if_exists("n", keymap.accept_and_goto)
71+
util.unset_keymap_if_exists("n", keymap.accept)
72+
util.unset_keymap_if_exists("n", keymap.dismiss)
73+
end
74+
75+
---@param lsp_client vim.lsp.Client
76+
function M.setup(lsp_client)
77+
if not config.nes.enabled then
78+
return
79+
end
80+
81+
local au = vim.api.nvim_create_augroup("copilotlsp.init", { clear = true })
82+
83+
local ok, err = pcall(function()
84+
require("copilot-lsp.api").nes_lsp_on_init(lsp_client, au)
85+
end)
86+
87+
if ok then
88+
logger.info("copilot-lsp nes loaded")
89+
else
90+
logger.error("copilot-lsp nes failed to load:", err)
91+
end
92+
93+
set_keymap(config.nes.keymap)
94+
M.initialized = true
95+
end
96+
97+
function M.teardown()
98+
if not M.initialized then
99+
return
100+
end
101+
102+
unset_keymap(config.nes.keymap)
103+
end
104+
105+
return M

0 commit comments

Comments
 (0)