-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathactions.lua
More file actions
178 lines (164 loc) · 6.17 KB
/
Copy pathactions.lua
File metadata and controls
178 lines (164 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
local M = {}
local ns = require("coderabbit.diagnostics").ns
--- Apply a suggestion to a buffer and remove the corresponding diagnostic.
--- @param bufnr number Buffer number
--- @param lnum number 0-indexed start line
--- @param end_lnum number|nil 0-indexed end line (nil = single line)
--- @param suggestion string Replacement text (may contain newlines)
--- @param message string Diagnostic message (used to identify which diagnostic to remove)
function M.apply(bufnr, lnum, end_lnum, suggestion, message)
if not vim.api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
local end_line = (end_lnum or lnum) + 1
local new_lines = vim.split(suggestion, "\n", { plain = true })
vim.api.nvim_buf_set_lines(bufnr, lnum, end_line, false, new_lines)
local old_count = end_line - lnum
local delta = #new_lines - old_count
M.cleanup(bufnr, lnum, end_lnum, message, delta)
end
--- Remove a diagnostic and shift subsequent ones after an edit has been applied.
--- @param bufnr number Buffer number
--- @param lnum number 0-indexed start line of the applied edit
--- @param end_lnum number|nil 0-indexed end line (nil = single line)
--- @param message string Diagnostic message (used to identify which diagnostic to remove)
--- @param delta number Line count change from the edit (positive = lines added, negative = removed)
function M.cleanup(bufnr, lnum, end_lnum, message, delta)
local existing = vim.diagnostic.get(bufnr, { namespace = ns })
local remaining = {}
local removed = false
local end_line = (end_lnum or lnum) + 1
for _, d in ipairs(existing) do
if not removed and d.lnum == lnum and d.end_lnum == (end_lnum or lnum) and d.message == message then
removed = true
else
if d.lnum >= end_line then
d.lnum = d.lnum + delta
if d.end_lnum then
d.end_lnum = d.end_lnum + delta
end
end
table.insert(remaining, d)
end
end
vim.diagnostic.set(ns, bufnr, remaining)
end
--- Build code actions for coderabbit diagnostics overlapping the given range.
--- @param bufnr number Buffer number
--- @param range table LSP-style range { start = { line = N }, ["end"] = { line = N } }
--- @return table[] LSP code actions
function M.get_actions(bufnr, range)
local start_line = range.start.line
local end_line = range["end"].line
local uri = vim.uri_from_bufnr(bufnr)
local diags = vim.diagnostic.get(bufnr, { namespace = ns })
local actions = {}
for _, diag in ipairs(diags) do
local diag_end = diag.end_lnum or diag.lnum
-- Check if diagnostic range overlaps the requested range
if diag.lnum <= end_line and diag_end >= start_line then
local suggestions = diag.user_data and diag.user_data.suggestions or {}
for i, suggestion in ipairs(suggestions) do
local title = #suggestions > 1 and string.format("CodeRabbit: Apply fix (%d/%d)", i, #suggestions)
or "CodeRabbit: Apply fix"
local edit_end_line = (diag.end_lnum or diag.lnum) + 1
local new_lines = vim.split(suggestion, "\n", { plain = true })
local delta = #new_lines - (edit_end_line - diag.lnum)
table.insert(actions, {
title = title,
kind = "quickfix",
edit = {
changes = {
[uri] = {
{
range = {
start = { line = diag.lnum, character = 0 },
["end"] = { line = edit_end_line, character = 0 },
},
newText = suggestion:gsub("\n*$", "\n"),
},
},
},
},
command = {
title = title,
command = "coderabbit.cleanup",
arguments = {
{
bufnr = bufnr,
lnum = diag.lnum,
end_lnum = diag.end_lnum,
message = diag.message,
delta = delta,
},
},
},
})
end
end
end
return actions
end
--- Attach the code-action virtual LSP client to a buffer.
--- Creates the client on first call; reuses and attaches on subsequent calls.
--- @param bufnr number Buffer number
function M.attach(bufnr)
vim.lsp.start({
name = "coderabbit",
cmd = function(_dispatchers)
local closing = false
return {
request = function(method, params, callback)
if method == "initialize" then
callback(nil, {
capabilities = {
codeActionProvider = true,
executeCommandProvider = {
commands = { "coderabbit.apply", "coderabbit.cleanup" },
},
},
})
elseif method == "shutdown" then
callback(nil, nil)
elseif method == "textDocument/codeAction" then
local uri = params.textDocument.uri
local buf = vim.uri_to_bufnr(uri)
local result = M.get_actions(buf, params.range)
callback(nil, result)
elseif method == "workspace/executeCommand" then
local args = type(params.arguments) == "table" and params.arguments[1]
if params.command == "coderabbit.apply" then
if type(args) == "table" and args.bufnr and args.lnum and args.suggestion and args.message then
vim.schedule(function()
M.apply(args.bufnr, args.lnum, args.end_lnum, args.suggestion, args.message)
end)
end
elseif params.command == "coderabbit.cleanup" then
if type(args) == "table" and args.bufnr and args.message and args.delta then
vim.schedule(function()
M.cleanup(args.bufnr, args.lnum, args.end_lnum, args.message, args.delta)
end)
end
end
callback(nil, nil)
else
callback(nil, nil)
end
end,
notify = function(method)
if method == "exit" then
closing = true
end
end,
is_closing = function()
return closing
end,
terminate = function()
closing = true
end,
}
end,
root_dir = vim.fn.getcwd(),
}, { bufnr = bufnr })
end
return M