forked from harrisoncramer/gitlab.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit.lua
More file actions
514 lines (461 loc) · 18.3 KB
/
Copy pathinit.lua
File metadata and controls
514 lines (461 loc) · 18.3 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
-- This Module contains all of the reviewer code. This is the code
-- that parses or interacts with diffview directly, such as opening
-- and closing, getting metadata about the current view, and registering
-- callbacks for open/close actions.
local List = require("gitlab.utils.list")
local u = require("gitlab.utils")
local state = require("gitlab.state")
local hunks = require("gitlab.hunks")
local async = require("diffview.async")
local M = {
is_open = false,
bufnr = nil,
tabid = nil,
stored_win = nil,
buf_winids = {},
}
-- Checks for legacy installations, only Diffview is supported.
M.init = function()
if state.settings.reviewer ~= "diffview" then
vim.notify(
string.format("gitlab.nvim could not find reviewer %s, only diffview is supported", state.settings.reviewer),
vim.log.levels.ERROR
)
end
end
-- Opens the reviewer windows.
M.open = function()
require("gitlab.emoji").init() -- Read in emojis for lookup purposes
local diff_refs = state.INFO.diff_refs
if diff_refs == nil then
u.notify("Gitlab did not provide diff refs required to review this MR", vim.log.levels.ERROR)
return
end
if diff_refs.base_sha == "" or diff_refs.head_sha == "" then
u.notify("Merge request contains no changes", vim.log.levels.ERROR)
return
end
require("gitlab.git_async").check_current_branch_up_to_date_on_remote()
local git = require("gitlab.git")
local diffview_open_command = "DiffviewOpen"
if state.settings.reviewer_settings.diffview.imply_local then
local has_clean_tree, err = git.has_clean_tree()
if err ~= nil then
return
end
if has_clean_tree then
diffview_open_command = diffview_open_command .. " --imply-local"
else
u.notify("Working tree unclean. Stash or commit all changes to use 'imply_local'.", vim.log.levels.WARN)
state.settings.reviewer_settings.diffview.imply_local = false
end
end
vim.api.nvim_command(string.format("%s %s..%s", diffview_open_command, diff_refs.base_sha, diff_refs.head_sha))
M.is_open = true
M.diffview = require("diffview.lib").get_current_view()
if M.diffview == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
M.diffview_layout = M.diffview.cur_layout
M.tabid = vim.api.nvim_get_current_tabpage()
if state.settings.discussion_diagnostic ~= nil or state.settings.discussion_sign ~= nil then
u.notify(
"Diagnostics are now configured as settings.discussion_signs, see :h gitlab.nvim.signs-and-diagnostics",
vim.log.levels.WARN
)
end
-- Register Diffview hook for close event to set tab page # to nil
local on_diffview_closed = function(view)
if view.tabpage == M.tabid then
M.tabid = nil
require("gitlab.actions.discussions.winbar").cleanup_timer()
end
end
require("diffview.config").user_emitter:on("view_closed", function(_, args)
if M.tabid == args.tabpage then
M.is_open = false
on_diffview_closed(args)
end
end)
if state.settings.discussion_tree.auto_open then
local discussions = require("gitlab.actions.discussions")
discussions.close()
require("gitlab").toggle_discussions() -- Fetches data and opens discussions
end
git.check_mr_in_good_condition()
end
-- Closes the reviewer and cleans up
M.close = function()
if M.tabid ~= nil and vim.api.nvim_tabpage_is_valid(M.tabid) then
vim.cmd.tabclose(vim.api.nvim_tabpage_get_number(M.tabid))
end
local discussions = require("gitlab.actions.discussions")
discussions.close()
end
---Loads new INFO state from Gitlab, then if diffview.api is available applies the new diff refs to
---the existing diffview, otherwise closes and re-opens the reviewer.
M.reload = function()
state.load_new_state("info", function()
state.load_new_state("revisions", function()
local has_api, api = pcall(require, "diffview.api")
if has_api then
api.set_revs(
string.format("%s..%s", state.INFO.diff_refs.base_sha, state.INFO.diff_refs.head_sha),
{ view = M.diffview }
)
else
M.close()
M.open()
end
end)
end)
end
--- Jumps to the location provided in the reviewer window
---@param file_name string The file name after change.
---@param old_file_name string The file name before change (different from file_name for renamed/moved files).
---@param line_number number Line number from the discussion node.
---@param new_buffer boolean If true, jump to the NEW SHA.
M.jump = function(file_name, old_file_name, line_number, new_buffer)
-- Draft comments don't have `old_file_name` set
old_file_name = old_file_name or file_name
if M.tabid == nil then
u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
return
end
vim.api.nvim_set_current_tabpage(M.tabid)
if M.diffview == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
local files = M.diffview.panel:ordered_file_list()
local file = List.new(files):find(function(f)
local oldpath = f.oldpath ~= nil and f.oldpath or f.path
return new_buffer and f.path == file_name or oldpath == old_file_name
end)
if file == nil then
u.notify(
string.format("The file %s for which the comment was made doesn't exist in HEAD.", file_name),
vim.log.levels.WARN
)
return
end
async.await(M.diffview:set_file(file))
local layout = M.diffview.cur_layout
local number_of_lines
if new_buffer then
layout.b:focus()
number_of_lines = u.get_buffer_length(layout.b.file.bufnr)
else
layout.a:focus()
number_of_lines = u.get_buffer_length(layout.a.file.bufnr)
end
if line_number > number_of_lines then
u.notify("Diagnostic position outside buffer. Jumping to last line instead.", vim.log.levels.WARN)
line_number = number_of_lines
end
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
u.open_fold_under_cursor()
vim.cmd("normal! zz")
end
---Get the data from diffview, such as line information and file name. May be used by
---other modules such as the comment module to create line codes or set diagnostics
---@param current_win integer The ID of the currently focused window
---@return DiffviewInfo | nil
M.get_reviewer_data = function(current_win)
if M.diffview == nil then
return
end
local layout = M.diffview.cur_layout
local old_win = u.get_window_id_by_buffer_id(layout.a.file.bufnr)
local new_win = u.get_window_id_by_buffer_id(layout.b.file.bufnr)
if old_win == nil or new_win == nil then
u.notify("Error getting window IDs for current files", vim.log.levels.ERROR)
return
end
local current_file = M.get_current_file_path()
if current_file == nil then
u.notify("Error getting current file from Diffview", vim.log.levels.ERROR)
return
end
local new_line = vim.api.nvim_win_get_cursor(new_win)[1]
local old_line = vim.api.nvim_win_get_cursor(old_win)[1]
local new_sha_focused = M.is_new_sha_focused(current_win)
local modification_type = hunks.get_modification_type(old_line, new_line, new_sha_focused)
if modification_type == nil then
u.notify("Error getting modification type", vim.log.levels.ERROR)
return
end
if modification_type == "bad_file_unmodified" then
u.notify("Comments on unmodified lines will be placed in the old file", vim.log.levels.WARN)
end
local current_bufnr = new_sha_focused and layout.b.file.bufnr or layout.a.file.bufnr
local opposite_bufnr = new_sha_focused and layout.a.file.bufnr or layout.b.file.bufnr
return {
old_file_name = M.is_file_renamed() and layout.a.file.path or "",
file_name = layout.b.file.path,
old_line_from_buf = old_line,
new_line_from_buf = new_line,
modification_type = modification_type,
current_bufnr = current_bufnr,
opposite_bufnr = opposite_bufnr,
new_sha_focused = new_sha_focused,
current_win_id = current_win,
}
end
---Return whether user is focused on the new version of the file
---@param current_win integer The ID of the currently focused window
---@return boolean
M.is_new_sha_focused = function(current_win)
local layout = M.diffview.cur_layout
local b_win = u.get_window_id_by_buffer_id(layout.b.file.bufnr)
local a_win = u.get_window_id_by_buffer_id(layout.a.file.bufnr)
if a_win ~= current_win and b_win ~= current_win then
current_win = M.stored_win
M.stored_win = nil
end
return current_win == b_win
end
---Get currently shown file data
M.get_current_file_data = function()
return M.diffview and M.diffview.panel and M.diffview.panel.cur_file
end
---Get currently shown file path
---@return string|nil
M.get_current_file_path = function()
local file_data = M.get_current_file_data()
return file_data and file_data.path
end
---Get currently shown file's old path
---@return string|nil
M.get_current_file_oldpath = function()
local file_data = M.get_current_file_data()
return file_data and file_data.oldpath or file_data.path
end
---Tell whether current file is renamed or not
---@return boolean|nil
M.is_file_renamed = function()
local file_data = M.get_current_file_data()
return file_data and file_data.status == "R"
end
---Tell whether current file has changes or not
---@return boolean|nil
M.does_file_have_changes = function()
local file_data = M.get_current_file_data()
return file_data.stats.additions > 0 or file_data.stats.deletions > 0
end
---Run callback every time the buffer in one of the two reviewer windows changes.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_file_changed = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.file_changed", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewDiffBufWinEnter" },
group = group,
callback = function(...)
if M.tabid == vim.api.nvim_get_current_tabpage() then
callback(...)
end
end,
})
end
---Run callback the first time a new diff buffer is created and loaded into a window.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_buf_read = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.buf_read", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewDiffBufRead" },
group = group,
callback = function(...)
if vim.api.nvim_get_current_tabpage() == M.tabid then
callback(...)
end
end,
})
end
---Run callback when the reviewer is closed or the user switches to another tab.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_reviewer_leave = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.leave", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewLeave", "DiffviewViewClosed" },
group = group,
callback = function(...)
if vim.api.nvim_get_current_tabpage() == M.tabid then
callback(...)
end
end,
})
end
---Run callback when the reviewer is opened for the first time or the view is entered from another
---tab page.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_reviewer_enter = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.enter", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewEnter", "DiffviewViewOpened" },
group = group,
callback = function(...)
if vim.api.nvim_get_current_tabpage() == M.tabid then
callback(...)
end
end,
})
end
---Create the line-wise visual selection in the range of the motion (or on the [count] number of
---lines) and execute the gitlab.nvim API function. After that, restore the cursor position and the
---original operatorfunc.
---@param callback string Name of the gitlab.nvim API function to call
M.execute_callback = function(callback)
return function()
vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { "'[V']" } }, {})
local _, err = pcall(
vim.api.nvim_cmd,
{ cmd = "lua", args = { ("require'gitlab'.%s()"):format(callback) }, mods = { lockmarks = true } },
{}
)
vim.api.nvim_win_set_cursor(M.old_winnr, M.old_cursor_position)
vim.opt.operatorfunc = M.old_opfunc
if err ~= "" then
u.notify_vim_error(err, vim.log.levels.ERROR)
end
end
end
---Set the operatorfunc that will work on the lines defined by the motion that follows after the
---operator mapping, and enter the operator-pending mode.
---@param cb string Name of the gitlab.nvim API function to call, e.g., "create_multiline_comment".
local function execute_operatorfunc(cb)
M.old_opfunc = vim.opt.operatorfunc
M.old_winnr = vim.api.nvim_get_current_win()
M.old_cursor_position = vim.api.nvim_win_get_cursor(M.old_winnr)
vim.opt.operatorfunc = ("v:lua.require'gitlab.reviewer'.execute_callback'%s'"):format(cb)
-- Use the operator count before motion to allow, e.g., 2cc == c2c
local count = M.operator_count > 0 and tostring(M.operator_count) or ""
vim.api.nvim_feedkeys("g@" .. count, "n", false)
end
---Set keymaps for creating comments, suggestions and for jumping to discussion tree.
---@param bufnr integer Number of the buffer for which the keybindings will be created.
M.set_keymaps = function(bufnr)
if bufnr == nil or not vim.api.nvim_buf_is_loaded(bufnr) then
return
end
-- Require keymaps only after user settings have been merged with defaults
local keymaps = require("gitlab.state").settings.keymaps
if keymaps.disable_all or keymaps.reviewer.disable_all then
return
end
-- Set mappings for creating comments
if keymaps.reviewer.create_comment ~= false then
-- Set keymap for repeated operator keybinding
vim.keymap.set("o", keymaps.reviewer.create_comment, function()
-- The "V" in "V%d$" forces linewise motion, see `:h o_V`
vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { string.format("V%d$", vim.v.count1) } }, {})
end, {
buffer = bufnr,
desc = "Create comment for [count] lines",
nowait = keymaps.reviewer.create_comment_nowait,
})
-- Set operator keybinding
vim.keymap.set(
"n",
keymaps.reviewer.create_comment,
function()
M.operator_count = vim.v.count
execute_operatorfunc("create_multiline_comment")
end,
{ buffer = bufnr, desc = "Create comment for range of motion", nowait = keymaps.reviewer.create_comment_nowait }
)
vim.keymap.set("v", keymaps.reviewer.create_comment, function()
require("gitlab").create_multiline_comment()
end, {
buffer = bufnr,
desc = "Create comment for selected text",
nowait = keymaps.reviewer.create_comment_nowait,
})
end
-- Set mappings for creating suggestions
if keymaps.reviewer.create_suggestion ~= false then
-- Set keymap for repeated operator keybinding
vim.keymap.set("o", keymaps.reviewer.create_suggestion, function()
-- The "V" in "V%d$" forces linewise motion, see `:h o_V`
vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { string.format("V%d$", vim.v.count1) } }, {})
end, {
buffer = bufnr,
desc = "Create suggestion for [count] lines",
nowait = keymaps.reviewer.create_suggestion_nowait,
})
-- Set operator keybinding
vim.keymap.set("n", keymaps.reviewer.create_suggestion, function()
M.operator_count = vim.v.count
M.operator = keymaps.reviewer.create_suggestion
execute_operatorfunc("create_comment_suggestion")
end, {
buffer = bufnr,
desc = "Create suggestion for range of motion",
nowait = keymaps.reviewer.create_suggestion_nowait,
})
-- Set visual mode keybinding
vim.keymap.set("v", keymaps.reviewer.create_suggestion, function()
require("gitlab").create_comment_suggestion()
end, {
buffer = bufnr,
desc = "Create suggestion for selected text",
nowait = keymaps.reviewer.create_suggestion_nowait,
})
end
-- Set mapping for moving to discussion tree
if keymaps.reviewer.move_to_discussion_tree ~= false then
vim.keymap.set("n", keymaps.reviewer.move_to_discussion_tree, function()
require("gitlab").move_to_discussion_tree_from_diagnostic()
end, { buffer = bufnr, desc = "Move to discussion", nowait = keymaps.reviewer.move_to_discussion_tree_nowait })
end
end
---Delete keymaps from reviewer buffers.
---@param bufnr integer Number of the buffer from which the keybindings will be removed.
local del_keymaps = function(bufnr)
if bufnr == nil or not vim.api.nvim_buf_is_loaded(bufnr) then
return
end
-- Require keymaps only after user settings have been merged with defaults
local keymaps = require("gitlab.state").settings.keymaps
if keymaps.disable_all or keymaps.reviewer.disable_all then
return
end
for _, func in ipairs({ "create_comment", "create_suggestion" }) do
if keymaps.reviewer[func] ~= false then
for _, mode in ipairs({ "n", "o", "v" }) do
pcall(vim.api.nvim_buf_del_keymap, bufnr, mode, keymaps.reviewer[func])
end
end
end
if keymaps.reviewer.move_to_discussion_tree ~= false then
pcall(vim.api.nvim_buf_del_keymap, bufnr, "n", keymaps.reviewer.move_to_discussion_tree)
end
end
--- Set up autocaommands that will take care of setting and unsetting buffer-local options and keymaps
M.set_reviewer_autocommands = function(bufnr)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.win_enter." .. bufnr, {})
vim.api.nvim_create_autocmd({ "WinEnter", "BufWinEnter" }, {
group = group,
buffer = bufnr,
callback = function()
if vim.api.nvim_get_current_win() == M.buf_winids[bufnr] then
M.stored_win = vim.api.nvim_get_current_win()
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
M.set_keymaps(bufnr)
else
if M.diffview_layout.b.id == M.buf_winids[bufnr] then
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
end
del_keymaps(bufnr)
end
end,
})
end
--- Update the stored winid for a given reviewer buffer. This is necessary for the
--- M.set_reviewer_autocommands function to work correctly in cases like when the user closes one of
--- the original reviewer windows and Diffview automatically creates a new pair
--- of reviewer windows or the user wipes out a buffer and Diffview reloads it with a different ID.
M.update_winid_for_buffer = function(bufnr)
M.buf_winids[bufnr] = vim.fn.bufwinid(bufnr)
end
return M