-
-
Notifications
You must be signed in to change notification settings - Fork 83
Expand file tree
/
Copy pathprofile.lua
More file actions
358 lines (315 loc) · 8.47 KB
/
profile.lua
File metadata and controls
358 lines (315 loc) · 8.47 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
local event = require('nui.utils.autocmd').event
local notify = require('java-core.utils.notify')
local profile_config = require('java.api.profile_config')
local class = require('java-core.utils.class')
local dap_api = require('java-dap')
local lsp_utils = require('java-core.utils.lsp')
local ui = require('java.ui.utils')
local Layout = require('nui.layout')
local Menu = require('nui.menu')
local Popup = require('nui.popup')
local DapSetup = require('java-dap.setup')
local new_profile = 'New Profile'
--- @param up_win number
--- @param down_win number
local function map_keys_for_profile_editor(popup, up_win, down_win)
local function go_up()
vim.api.nvim_set_current_win(up_win)
end
local function go_down()
vim.api.nvim_set_current_win(down_win)
end
popup:map('n', '<Tab>', go_down)
popup:map('n', 'k', go_up)
popup:map('n', 'j', go_down)
end
--- @param popup Popup
--- @return string
local function get_popup_value(popup)
local ok, value = pcall(vim.api.nvim_buf_get_lines, popup.bufnr, 0, -1, false)
if ok then
return value[1]
end
notify.error('Failed to get popup value for ' .. popup.name)
end
--- @param name string
--- @return boolean
local function is_contains_active_postfix(name)
return name:match('^(.*)(active)')
end
--- @param name string
--- @return string
local function clear_active_postfix(name)
local val, _ = name:gsub(' %(.+%)', '')
return val
end
--- @param popups table<string, Popup>
--- @param target_profile string
local function save_profile(popups, target_profile, main_class)
local vm_args = get_popup_value(popups.vm_args)
local prog_args = get_popup_value(popups.prog_args)
local name = get_popup_value(popups.name)
local profile = profile_config.Profile(vm_args, prog_args, name)
if profile.name == nil or profile.name == '' then
notify.warn('Profile name is required')
return false
end
if target_profile then
if is_contains_active_postfix(target_profile) then
target_profile = clear_active_postfix(target_profile)
end
else
if profile_config.get_profile(profile.name, main_class) then
notify.warn('Profile name already exists')
return false
end
end
profile_config.add_or_update_profile(profile, target_profile, main_class)
dap_api.config_dap()
return true
end
--- @class ProfileUI
--- @field win_options table
--- @field style string
--- @field focus_item NuiTree.Node
--- @field main_class string
local ProfileUI = class()
function ProfileUI:_init(main_class)
self.main_class = main_class
self.win_options = {
winhighlight = 'Normal:Normal,FloatBorder:Normal',
}
self.style = 'single'
self.keymap_style = 'rounded'
self.focus_item = nil
end
---@return NuiTree.Node[]
function ProfileUI:get_tree_node_list_for_menu()
local menu_nodes = {}
local profiles = profile_config.get_all_profiles(self.main_class)
local count = 1
for key, profile in pairs(profiles) do
if profile.is_active then
key = key .. ' (active)'
menu_nodes[1] = Menu.item(key)
else
count = count + 1
menu_nodes[count] = Menu.item(key)
end
end
table.insert(menu_nodes, Menu.separator())
table.insert(menu_nodes, Menu.item(new_profile))
return menu_nodes
end
--- @return Menu
function ProfileUI:get_menu()
local lines = self:get_tree_node_list_for_menu()
return Menu({
relative = 'editor',
position = '50%',
size = {
width = 40,
height = 8,
},
border = {
style = self.style,
text = {
top = '[Profiles]',
top_align = 'center',
bottom = '[a]ctivate [d]elete [b]ack [q]uit',
bottom_align = 'center',
},
},
win_options = self.win_options,
}, {
lines = lines,
max_width = 20,
keymap = {
focus_next = { 'j', '<Down>', '<Tab>' },
focus_prev = { 'k', '<Up>', '<S-Tab>' },
close = { '<Esc>', '<C-c>' },
submit = { '<CR>', '<Space>' },
},
on_submit = function(item)
if item.text == new_profile then
self:open_profile_editor()
else
local profile_name = clear_active_postfix(item.text)
self:open_profile_editor(profile_name)
end
end,
})
end
---@private
--- @param title string
--- @param key string
--- @param target_profile string
--- @param enter boolean|nil
--- @param keymaps boolean|nil
function ProfileUI:get_and_fill_popup(title, key, target_profile, enter, keymaps)
local style = self.style
local text = {
top = '[' .. title .. ']',
top_align = 'center',
}
if keymaps then
text.bottom = '[s]ave [b]ack [q]uit'
text.bottom_align = 'center'
end
local popup = Popup({
border = {
style = style,
text = text,
},
enter = enter or false,
win_options = self.win_options,
})
-- fill the popup with the config value
-- if target_profile is nil, it's a new profile
if target_profile then
vim.api.nvim_buf_set_lines(
popup.bufnr,
0,
-1,
false,
{ profile_config.get_profile(target_profile, self.main_class)[key] }
)
end
return popup
end
---@private
function ProfileUI:open_profile_editor(target_profile)
local popups = {
name = self:get_and_fill_popup('Name', 'name', target_profile, true, false),
vm_args = self:get_and_fill_popup('VM arguments', 'vm_args', target_profile, false, false),
prog_args = self:get_and_fill_popup('Program arguments', 'prog_args', target_profile, false, true),
}
local layout = Layout(
{
relative = 'editor',
position = '50%',
size = { height = 15, width = 60 },
},
Layout.Box({
Layout.Box(popups.name, { grow = 0.2 }),
Layout.Box(popups.vm_args, { grow = 0.4 }),
Layout.Box(popups.prog_args, { grow = 0.4 }),
}, { dir = 'col' })
)
layout:mount()
for _, popup in pairs(popups) do
-- go back
popup:map('n', 'b', function()
layout:unmount()
self:openMenu()
end)
-- quit
popup:map('n', 'q', function()
layout:unmount()
end)
-- save
popup:map('n', 's', function()
if save_profile(popups, target_profile, self.main_class) then
layout:unmount()
end
end)
end
map_keys_for_profile_editor(
popups.name, -- popup (first)
popups.prog_args.winid, -- up_win
popups.vm_args.winid -- down_win
)
map_keys_for_profile_editor(
popups.vm_args, -- popup (second)
popups.name.winid, -- up_win
popups.prog_args.winid -- down_win
)
map_keys_for_profile_editor(
popups.prog_args, -- popup (third)
popups.vm_args.winid, -- up_win
popups.name.winid -- down_win
)
end
---@private
--- @return boolean
function ProfileUI:is_selected_profile_modifiable()
if self.focus_item == nil or self.focus_item.text == nil or self.focus_item.text == new_profile then
return false
end
return true
end
---@private
function ProfileUI:set_active_profile()
if not self:is_selected_profile_modifiable() then
notify.error('Failed to set profile as active')
return
end
if is_contains_active_postfix(self.focus_item.text) then
return
end
profile_config.set_active_profile(self.focus_item.text, self.main_class)
dap_api.config_dap()
self.menu:unmount()
self:openMenu()
end
---@private
function ProfileUI:delete_profile()
if not self:is_selected_profile_modifiable() then
notify.error('Failed to delete profile')
return
end
if is_contains_active_postfix(self.focus_item.text) then
notify.warn('Cannot delete active profile')
return
end
profile_config.delete_profile(self.focus_item.text, self.main_class)
self.menu:unmount()
self:openMenu()
end
function ProfileUI:openMenu()
self.menu = self:get_menu()
self.menu:on(event.CursorMoved, function()
self.focus_item = self.menu.tree:get_node()
end)
self.menu:mount()
-- quit
self.menu:map('n', 'q', function()
self.menu:unmount()
end, { noremap = true, nowait = true })
-- back
self.menu:map('n', 'b', function()
self.menu:unmount()
end, { noremap = true, nowait = true })
self.menu:map('n', 'a', function()
self:set_active_profile()
end, { noremap = true, nowait = true })
-- delete
self.menu:map('n', 'd', function()
self:delete_profile()
end, { noremap = true, nowait = true })
end
local M = {}
local runner = require('async.runner')
local get_error_handler = require('java-core.utils.error_handler')
--- @type ProfileUI
M.ProfileUI = ProfileUI
function M.ui()
return runner(function()
local configs = DapSetup(lsp_utils.get_jdtls()):get_dap_config()
if not configs or #configs == 0 then
notify.error('No classes with main methods are found')
return
end
local selected_config = ui.select('Select the main class (module -> mainClass)', configs, function(config)
return config.name
end)
if not selected_config then
return
end
M.profile_ui = ProfileUI(selected_config.name)
return M.profile_ui:openMenu()
end)
.catch(get_error_handler('failed to run app'))
.run()
end
return M