Skip to content

Commit c4a3229

Browse files
Feat: Adds More Information to Summary Panel (#100)
This MR adds more information into the summary view, including the MR author, created at date, merge status, draft status, conflicts, and pipeline status, among other things. This is configurable via the setup function.
1 parent 88b9196 commit c4a3229

File tree

7 files changed

+333
-55
lines changed

7 files changed

+333
-55
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@ require("gitlab").setup({
135135
resolved = '', -- Symbol to show next to resolved discussions
136136
unresolved = '', -- Symbol to show next to unresolved discussions
137137
},
138+
info = { -- Show additional fields in the summary pane
139+
enabled = true,
140+
horizontal = false, -- Display metadata to the left of the summary rather than underneath
141+
fields = { -- The fields listed here will be displayed, in whatever order you choose
142+
"author",
143+
"created_at",
144+
"updated_at",
145+
"merge_status",
146+
"draft",
147+
"conflicts",
148+
"assignees",
149+
"branch",
150+
"pipeline",
151+
},
152+
},
138153
discussion_sign_and_diagnostic = {
139154
skip_resolved_discussion = false,
140155
skip_old_revision_discussion = true,
@@ -206,6 +221,8 @@ require("gitlab").summary()
206221

207222
After editing the description or title, you may save your changes via the `settings.popup.perform_action` keybinding.
208223

224+
By default this plugin will also show additional metadata about the MR in a separate pane underneath the description. This can be disabled, and these fields can be reordered or removed. Please see the `settings.info` section of the configuration.
225+
209226
### Reviewing Diffs
210227

211228
The `review` action will open a diff of the changes. You can leave comments using the `create_comment` action. In visual mode, add multiline comments with the `create_multiline_comment` command, and add suggested changes with the `create_comment_suggestion` command.

example.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
local Layout = require("nui.layout")
2+
local Popup = require("nui.popup")
3+
4+
local opts = {
5+
buf_options = {
6+
filetype = "markdown",
7+
},
8+
focusable = true,
9+
border = {
10+
style = "rounded",
11+
},
12+
}
13+
14+
local title_popup = Popup(opts)
15+
local description_popup = Popup(opts)
16+
local info_popup = Popup(opts)
17+
18+
local layout = Layout(
19+
{
20+
position = "50%",
21+
relative = "editor",
22+
size = {
23+
width = "95%",
24+
height = "95%",
25+
},
26+
},
27+
Layout.Box({
28+
Layout.Box(title_popup, { size = { height = 3 } }),
29+
Layout.Box({
30+
Layout.Box(description_popup, { grow = 1 }),
31+
Layout.Box(info_popup, { size = { height = 15 } }),
32+
}, { dir = "col", size = "100%" }),
33+
}, { dir = "col" })
34+
)
35+
36+
layout:mount()

lua/gitlab/actions/pipeline.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ local function get_pipeline()
2020
return pipeline
2121
end
2222

23+
M.get_pipeline_status = function()
24+
local pipeline = get_pipeline()
25+
if pipeline == nil then
26+
return nil
27+
end
28+
return string.format("%s (%s)", state.settings.pipeline[pipeline.status], pipeline.status)
29+
end
30+
2331
-- The function will render the Pipeline state in a popup
2432
M.open = function()
2533
local pipeline = get_pipeline()
@@ -44,7 +52,7 @@ M.open = function()
4452
local lines = {}
4553

4654
u.switch_can_edit_buf(bufnr, true)
47-
table.insert(lines, string.format("Status: %s (%s)", state.settings.pipeline[pipeline.status], pipeline.status))
55+
table.insert(lines, "Status: " .. M.get_pipeline_status())
4856
table.insert(lines, "")
4957
table.insert(lines, string.format("Last Run: %s", u.time_since(pipeline.created_at)))
5058
table.insert(lines, string.format("Url: %s", pipeline.web_url))

lua/gitlab/actions/summary.lua

Lines changed: 161 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ local job = require("gitlab.job")
77
local u = require("gitlab.utils")
88
local state = require("gitlab.state")
99
local miscellaneous = require("gitlab.actions.miscellaneous")
10+
local pipeline = require("gitlab.actions.pipeline")
11+
1012
local M = {
1113
layout_visible = false,
1214
layout = nil,
@@ -15,15 +17,58 @@ local M = {
1517
description_bufnr = nil,
1618
}
1719

18-
-- The function will render the MR description in a popup
20+
local title_popup_settings = {
21+
buf_options = {
22+
filetype = "markdown",
23+
},
24+
focusable = true,
25+
border = {
26+
style = "rounded",
27+
},
28+
}
29+
30+
local details_popup_settings = {
31+
buf_options = {
32+
filetype = "markdown",
33+
},
34+
focusable = true,
35+
border = {
36+
style = "rounded",
37+
text = {
38+
top = "Details",
39+
},
40+
},
41+
}
42+
43+
local description_popup_settings = {
44+
buf_options = {
45+
filetype = "markdown",
46+
},
47+
enter = true,
48+
focusable = true,
49+
border = {
50+
style = "rounded",
51+
text = {
52+
top = "Description",
53+
},
54+
},
55+
}
56+
57+
-- The function will render a popup containing the MR title and MR description, and optionally,
58+
-- any additional metadata that the user wants. The title and description are editable and
59+
-- can be changed via the local action keybinding, which also closes the popup
1960
M.summary = function()
2061
if M.layout_visible then
2162
M.layout:unmount()
2263
M.layout_visible = false
2364
return
2465
end
2566

26-
local layout, title_popup, description_popup = M.create_layout()
67+
local title = state.INFO.title
68+
local description_lines = M.build_description_lines()
69+
local info_lines = state.settings.info.enabled and M.build_info_lines() or nil
70+
71+
local layout, title_popup, description_popup, info_popup = M.create_layout(info_lines)
2772

2873
M.layout = layout
2974
M.layout_buf = layout.bufnr
@@ -34,29 +79,93 @@ M.summary = function()
3479
M.layout_visible = false
3580
end
3681

37-
local currentBuffer = vim.api.nvim_get_current_buf()
38-
local title = state.INFO.title
39-
local description = state.INFO.description
40-
local lines = {}
41-
42-
for line in description:gmatch("[^\n]+") do
43-
table.insert(lines, line)
44-
table.insert(lines, "")
45-
end
46-
4782
vim.schedule(function()
48-
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
83+
vim.api.nvim_buf_set_lines(description_popup.bufnr, 0, -1, false, description_lines)
4984
vim.api.nvim_buf_set_lines(title_popup.bufnr, 0, -1, false, { title })
85+
86+
if info_popup then
87+
vim.api.nvim_buf_set_lines(info_popup.bufnr, 0, -1, false, info_lines)
88+
vim.api.nvim_set_option_value("modifiable", false, { buf = info_popup.bufnr })
89+
vim.api.nvim_set_option_value("readonly", false, { buf = info_popup.bufnr })
90+
end
91+
5092
state.set_popup_keymaps(
5193
description_popup,
5294
M.edit_summary,
5395
miscellaneous.attach_file,
5496
{ cb = exit, action_before_close = true }
5597
)
5698
state.set_popup_keymaps(title_popup, M.edit_summary, nil, { cb = exit, action_before_close = true })
99+
100+
vim.api.nvim_set_current_buf(description_popup.bufnr)
57101
end)
58102
end
59103

104+
-- Builds a lua list of strings that contain the MR description
105+
M.build_description_lines = function()
106+
local description_lines = {}
107+
108+
local description = state.INFO.description
109+
for line in description:gmatch("[^\n]+") do
110+
table.insert(description_lines, line)
111+
table.insert(description_lines, "")
112+
end
113+
114+
return description_lines
115+
end
116+
117+
-- Builds a lua list of strings that contain metadata about the current MR. Only builds the
118+
-- lines that users include in their state.settings.info.fields list.
119+
M.build_info_lines = function()
120+
local info = state.INFO
121+
local options = {
122+
author = { title = "Author", content = "@" .. info.author.username .. " (" .. info.author.name .. ")" },
123+
created_at = { title = "Created", content = u.format_to_local(info.created_at, vim.fn.strftime("%z")) },
124+
updated_at = { title = "Updated", content = u.format_to_local(info.updated_at, vim.fn.strftime("%z")) },
125+
merge_status = { title = "Status", content = info.detailed_merge_status },
126+
draft = { title = "Draft", content = (info.draft and "Yes" or "No") },
127+
conflicts = { title = "Merge Conflicts", content = (info.has_conflicts and "Yes" or "No") },
128+
assignees = { title = "Assignees", content = u.make_readable_list(info.assignees, "name") },
129+
branch = { title = "Branch", content = info.source_branch },
130+
pipeline = {
131+
title = "Pipeline Status:",
132+
content = function()
133+
return pipeline.get_pipeline_status()
134+
end,
135+
},
136+
}
137+
138+
local longest_used = ""
139+
for _, v in ipairs(state.settings.info.fields) do
140+
local title = options[v].title
141+
if string.len(title) > string.len(longest_used) then
142+
longest_used = title
143+
end
144+
end
145+
146+
local function row_offset(row)
147+
local offset = string.len(longest_used) - string.len(row)
148+
return string.rep(" ", offset + 3)
149+
end
150+
151+
local lines = {}
152+
for _, v in ipairs(state.settings.info.fields) do
153+
local row = options[v]
154+
local line = "* " .. row.title .. row_offset(row.title)
155+
if type(row.content) == "function" then
156+
local content = row.content()
157+
if content ~= nil then
158+
line = line .. row.content()
159+
end
160+
else
161+
line = line .. row.content
162+
end
163+
table.insert(lines, line)
164+
end
165+
166+
return lines
167+
end
168+
60169
-- This function will PUT the new description to the Go server
61170
M.edit_summary = function()
62171
local description = u.get_buffer_text(M.description_bufnr)
@@ -71,54 +180,52 @@ M.edit_summary = function()
71180
end)
72181
end
73182

74-
local top_popup = {
75-
buf_options = {
76-
filetype = "markdown",
77-
},
78-
focusable = true,
79-
border = {
80-
style = "rounded",
81-
text = {
82-
top = "Merge Request",
83-
},
84-
},
85-
}
86-
87-
local bottom_popup = {
88-
buf_options = {
89-
filetype = "markdown",
90-
},
91-
enter = true,
92-
focusable = true,
93-
border = {
94-
style = "rounded",
95-
},
96-
}
97-
98-
M.create_layout = function()
99-
local title_popup = Popup(top_popup)
183+
M.create_layout = function(info_lines)
184+
local title_popup = Popup(title_popup_settings)
100185
M.title_bufnr = title_popup.bufnr
101-
local description_popup = Popup(bottom_popup)
186+
local description_popup = Popup(description_popup_settings)
102187
M.description_bufnr = description_popup.bufnr
188+
local details_popup
103189

104-
local layout = Layout(
105-
{
106-
position = "50%",
107-
relative = "editor",
108-
size = {
109-
width = "90%",
110-
height = "70%",
111-
},
112-
},
113-
Layout.Box({
114-
Layout.Box(title_popup, { size = { height = 3 } }),
115-
Layout.Box(description_popup, { size = "100%" }),
190+
local internal_layout
191+
if state.settings.info.enabled then
192+
details_popup = Popup(details_popup_settings)
193+
if state.settings.info.horizontal then
194+
local longest_line = u.get_longest_string(info_lines)
195+
print(longest_line)
196+
internal_layout = Layout.Box({
197+
Layout.Box(title_popup, { size = 3 }),
198+
Layout.Box({
199+
Layout.Box(details_popup, { size = longest_line + 3 }),
200+
Layout.Box(description_popup, { grow = 1 }),
201+
}, { dir = "row", size = "100%" }),
202+
}, { dir = "col" })
203+
else
204+
internal_layout = Layout.Box({
205+
Layout.Box(title_popup, { size = 3 }),
206+
Layout.Box(description_popup, { grow = 1 }),
207+
Layout.Box(details_popup, { size = #info_lines + 3 }),
208+
}, { dir = "col" })
209+
end
210+
else
211+
internal_layout = Layout.Box({
212+
Layout.Box(title_popup, { size = 3 }),
213+
Layout.Box(description_popup, { grow = 1 }),
116214
}, { dir = "col" })
117-
)
215+
end
216+
217+
local layout = Layout({
218+
position = "50%",
219+
relative = "editor",
220+
size = {
221+
width = "95%",
222+
height = "95%",
223+
},
224+
}, internal_layout)
118225

119226
layout:mount()
120227

121-
return layout, title_popup, description_popup
228+
return layout, title_popup, description_popup, details_popup
122229
end
123230

124231
return M

lua/gitlab/spec/util_spec.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,38 @@ describe("utils/init.lua", function()
178178
assert.are.same(got, want)
179179
end)
180180
end)
181+
182+
describe("offset_to_seconds", function()
183+
local tests = {
184+
est = { "-0500", -18000 },
185+
pst = { "-0800", -28800 },
186+
gmt = { "+0000", 0 },
187+
cet = { "+0100", 360 },
188+
jst = { "+0900", 32400 },
189+
ist = { "+0530", 19800 },
190+
art = { "-0300", -10800 },
191+
aest = { "+1100", 39600 },
192+
mmt = { "+0630", 23400 },
193+
}
194+
195+
for _, val in ipairs(tests) do
196+
local got = u.offset_to_seconds(val[1])
197+
local want = val[2]
198+
assert.are.same(got, want)
199+
end
200+
end)
201+
202+
describe("format_to_local", function()
203+
local tests = {
204+
{ "2023-10-28T16:25:09.482Z", "-0500", "10/28/2023 at 11:25" },
205+
{ "2016-11-22T1:25:09.482Z", "-0500", "11/21/2016 at 20:25" },
206+
{ "2016-11-22T1:25:09.482Z", "-0000", "11/22/2016 at 01:25" },
207+
{ "2017-3-22T13:25:09.482Z", "+0700", "03/22/2017 at 20:25" },
208+
}
209+
for _, val in ipairs(tests) do
210+
local got = u.format_to_local(val[1], val[2])
211+
local want = val[3]
212+
assert.are.same(got, want)
213+
end
214+
end)
181215
end)

0 commit comments

Comments
 (0)