Skip to content

Commit 38f19c8

Browse files
committed
Improve the stack trace picker and get it to work even when inside a non file buffer (like dap console output)
2 parents b2f0408 + 4a0e49b commit 38f19c8

3 files changed

Lines changed: 199 additions & 101 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ Demonstation of the ":JavaHelpersDeobfuscate" command:
6060

6161
### Create a new Java class in same package as current Java file
6262

63-
Demonstation of the ":JavaHelpersNewFile Class"
63+
Demonstation of the ":JavaHelpersNewFile Class" command:
6464

6565
![New Class Creation](https://github.com/NickJAllen/resources/blob/main/java-helpers/new-class-creation.gif)
6666

6767
### Create a new type in the same package as current Java file
6868

69-
Demonstation of the ":JavaHelpersNewFile"
69+
Demonstation of the ":JavaHelpersNewFile" command:
7070

7171
![Java Stack Picker](https://github.com/NickJAllen/resources/blob/main/java-helpers/new-type-creation.gif)
7272

lua/java-helpers/stack-trace.lua

Lines changed: 189 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -923,20 +923,54 @@ local function java_stack_trace_to_snacks_items(stack_trace)
923923
return nil
924924
end
925925

926+
---@param cols table
927+
---@param element JavaHelpers.StackTraceElement
928+
---@param max_class_and_method_length integer
929+
local function add_snacks_picker_columns_for_element(cols, element, max_class_and_method_length)
930+
assert(element)
931+
assert(max_class_and_method_length > 0)
932+
933+
local class_name = element.class_name
934+
local method_name = element.method_name
935+
local width = #class_name + 1 + #method_name
936+
937+
assert(width <= max_class_and_method_length)
938+
939+
table.insert(cols, { class_name, "SnacksLabel" })
940+
table.insert(cols, { ".", "SnacksPickerSpecial" })
941+
table.insert(cols, { method_name, "SnacksPickerSpecial" })
942+
table.insert(cols, { string.rep(" ", max_class_and_method_length - width + 2), "SnacksLabel" })
943+
944+
local file_name = element.file_name
945+
946+
if file_name then
947+
table.insert(cols, { file_name, "SnacksPickerFile" })
948+
table.insert(cols, { ":", "SnacksLabel" })
949+
end
950+
951+
table.insert(cols, { tostring(element.line_number), "SnacksPickerRow" })
952+
end
953+
926954
---@param stack_trace JavaHelpers.StackTraceElement[]
927955
---@param initially_selected integer
928-
local function pick_java_stack_trace_line(stack_trace, initially_selected)
956+
---@return integer? selected_index
957+
local function await_pick_java_stack_trace_line(stack_trace, initially_selected)
958+
local co = coroutine.running()
959+
960+
assert(co, "await_pick_java_stack_trace_line must be called within a coroutine")
961+
929962
if #stack_trace == 1 then
930-
go_to_java_stack_trace_element(stack_trace[1])
931-
return
963+
return 1
932964
end
933965

934966
local items = java_stack_trace_to_snacks_items(stack_trace)
935967

936968
if not items then
937-
return
969+
return nil
938970
end
939971

972+
assert(#items > 0)
973+
940974
local max_class_and_method_length = 1
941975

942976
for _, item in ipairs(items) do
@@ -960,43 +994,23 @@ local function pick_java_stack_trace_line(stack_trace, initially_selected)
960994
local cols = {}
961995
local element = item.java_stack_trace_element
962996

963-
assert(element)
964-
965-
local class_name = element.class_name
966-
local method_name = element.method_name
967-
local width = #class_name + 1 + #method_name
968-
969-
table.insert(cols, { class_name, "SnacksLabel" })
970-
table.insert(cols, { ".", "SnacksPickerSpecial" })
971-
table.insert(cols, { method_name, "SnacksPickerSpecial" })
972-
table.insert(cols, { string.rep(" ", max_class_and_method_length - width + 2), "SnacksLabel" })
973-
974-
local file_name = element.file_name
975-
976-
if file_name then
977-
table.insert(cols, { file_name, "SnacksPickerFile" })
978-
table.insert(cols, { ":", "SnacksLabel" })
979-
end
980-
981-
table.insert(cols, { tostring(element.line_number), "SnacksPickerRow" })
997+
add_snacks_picker_columns_for_element(cols, element, max_class_and_method_length)
982998

983999
return cols
9841000
end,
9851001
confirm = function(p, item)
9861002
p:close()
987-
if item then
988-
utils.go_to_file_and_line_number(item.file, item.pos[1])
989-
990-
if stack_trace == current_loaded_stack_trace then
991-
local index = item.java_stack_trace_index
992-
993-
if index then
994-
current_loaded_stack_trace_index = index
995-
end
1003+
vim.schedule(function()
1004+
if item then
1005+
coroutine.resume(co, item.java_stack_trace_index)
1006+
else
1007+
coroutine.resume(co, nil)
9961008
end
997-
end
1009+
end)
9981010
end,
9991011
})
1012+
1013+
return coroutine.yield()
10001014
end
10011015

10021016
---@param register_name_or_text_to_parse string? If a single character then defines the register name, if multiple characters then will parse trhe supplied text, if nil or empty then uses the text around the current cursor position
@@ -1009,7 +1023,18 @@ function M.pick_java_stack_trace_line(register_name_or_text_to_parse)
10091023
return
10101024
end
10111025

1012-
pick_java_stack_trace_line(current_loaded_stack_trace, current_loaded_stack_trace_index)
1026+
local stack_trace = current_loaded_stack_trace
1027+
1028+
local index = await_pick_java_stack_trace_line(stack_trace, current_loaded_stack_trace_index)
1029+
1030+
if index then
1031+
local element = stack_trace[index]
1032+
1033+
go_to_java_stack_trace_element(element)
1034+
1035+
current_loaded_stack_trace = stack_trace
1036+
current_loaded_stack_trace_index = index
1037+
end
10131038
end)
10141039
end
10151040

@@ -1042,7 +1067,7 @@ local function select_obfuscation_file_if_needed()
10421067
end
10431068
end
10441069

1045-
---@param register_name string? Register name or nil to deobfuscate under the cursor
1070+
---@param register_name string Register name or nil to deobfuscate under the cursor
10461071
local function deobfuscate_register(register_name)
10471072
select_obfuscation_file_if_needed()
10481073

@@ -1182,7 +1207,6 @@ end
11821207
---@param cursor_line integer
11831208
---@return integer? next_start_line
11841209
local function find_prev_stack_trace(lines, cursor_line)
1185-
local line_count = lines.line_count
11861210
local line_to_start_from = cursor_line
11871211

11881212
-- Go past the beginning of the current stack trace
@@ -1273,12 +1297,18 @@ function M.go_to_prev_stack_trace(win)
12731297
vim.api.nvim_win_set_cursor(win, { line_to_go_to, 0 })
12741298
end
12751299

1300+
---@class JavaHelpers.FoundStackTrace
1301+
---@field start_line integer
1302+
---@field element JavaHelpers.StackTraceElement
1303+
12761304
---@param lines JavaHelpers.TextLines
1277-
---@return integer[] start_lines
1278-
local function find_all_stack_trace_start_lines(lines)
1305+
---@return JavaHelpers.FoundStackTrace[] found_stack_traces
1306+
local function find_all_stack_traces(lines)
12791307
local line = 1
12801308
local count = lines.line_count
1281-
local start_lines = {}
1309+
---
1310+
---@type JavaHelpers.FoundStackTrace[]
1311+
local found_stack_traces = {}
12821312

12831313
while line <= count do
12841314
local start_line = find_next_stack_trace(lines, line)
@@ -1293,33 +1323,141 @@ local function find_all_stack_trace_start_lines(lines)
12931323
assert(first_line)
12941324
assert(last_line)
12951325

1296-
table.insert(start_lines, first_line)
1326+
table.insert(found_stack_traces, {
1327+
start_line = first_line,
1328+
element = element,
1329+
})
1330+
12971331
line = last_line
12981332
end
12991333

1300-
return start_lines
1334+
return found_stack_traces
1335+
end
1336+
1337+
---@param bufnr integer
1338+
---@param found_stack_traces JavaHelpers.FoundStackTrace[]
1339+
---@return JavaHelpers.FoundStackTrace? selected
1340+
local function await_pick_java_stack_trace(bufnr, found_stack_traces)
1341+
log.trace("Selecting line number in buffer " .. bufnr)
1342+
1343+
local co = coroutine.running()
1344+
1345+
assert(co, "await_pick_line_number_in_file must be called within a coroutine")
1346+
1347+
if #found_stack_traces == 0 then
1348+
return nil
1349+
end
1350+
1351+
if #found_stack_traces == 1 then
1352+
return found_stack_traces[1]
1353+
end
1354+
1355+
local is_file = vim.api.nvim_get_option_value("buftype", { buf = bufnr }) == ""
1356+
local file = nil
1357+
1358+
if is_file then
1359+
file = vim.api.nvim_buf_get_name(bufnr)
1360+
else
1361+
-- This is not a normal file so write it to a temporary file
1362+
file = vim.fn.tempname()
1363+
1364+
local f = io.open(file, "w")
1365+
1366+
if f then
1367+
f:write(utils.get_buffer_text(bufnr))
1368+
f:close()
1369+
end
1370+
end
1371+
1372+
local picker = require("snacks.picker")
1373+
local items = {}
1374+
local max_class_and_method_length = 1
1375+
1376+
for _, found_stack_trace in ipairs(found_stack_traces) do
1377+
local line_number = found_stack_trace.start_line
1378+
local element = found_stack_trace.element
1379+
1380+
local class_name = element.class_name
1381+
local method_name = element.method_name
1382+
local width = #class_name + 1 + #method_name
1383+
1384+
if width > max_class_and_method_length then
1385+
max_class_and_method_length = width
1386+
end
1387+
1388+
table.insert(items, {
1389+
file = file,
1390+
pos = { found_stack_trace.start_line, 0 },
1391+
found_java_stack_trace = found_stack_trace,
1392+
})
1393+
end
1394+
1395+
picker.pick({
1396+
items = items,
1397+
prompt = "Select strack trace: ",
1398+
format = function(item, _)
1399+
local found_java_stack_trace = item.found_java_stack_trace
1400+
local element = found_java_stack_trace.element
1401+
local cols = {}
1402+
1403+
add_snacks_picker_columns_for_element(cols, element, max_class_and_method_length)
1404+
1405+
return cols
1406+
end,
1407+
confirm = function(p, item)
1408+
p:close()
1409+
1410+
if not is_file then
1411+
os.remove(file)
1412+
end
1413+
1414+
vim.schedule(function()
1415+
if not item then
1416+
log.trace("No stack trace selected")
1417+
coroutine.resume(co, nil)
1418+
return
1419+
end
1420+
1421+
local selected = item.found_java_stack_trace
1422+
1423+
log.trace("Selected stack trace " .. selected.start_line .. " in " .. bufnr)
1424+
1425+
coroutine.resume(co, selected)
1426+
end)
1427+
end,
1428+
})
1429+
1430+
return coroutine.yield()
13011431
end
13021432

13031433
function M.pick_java_stack_trace(win)
1434+
if win == 0 then
1435+
win = vim.api.nvim_get_current_win()
1436+
end
1437+
13041438
local bufnr = vim.api.nvim_win_get_buf(win)
13051439
local file_path = vim.api.nvim_buf_get_name(bufnr)
13061440
local lines = utils.create_text_lines_from_buffer(bufnr)
1307-
local start_lines = find_all_stack_trace_start_lines(lines)
1441+
local found_stack_traces = find_all_stack_traces(lines)
13081442

1309-
if #start_lines == 0 then
1443+
if #found_stack_traces == 0 then
13101444
log.info("No stack traces found")
13111445
return
13121446
end
13131447

1448+
if #found_stack_traces == 1 then
1449+
vim.api.nvim_win_set_buf(win, bufnr)
1450+
vim.api.nvim_win_set_cursor(win, { found_stack_traces[1].start_line, 0 })
1451+
return
1452+
end
1453+
13141454
run_in_bg(function()
1315-
if #start_lines == 1 then
1316-
vim.api.nvim_win_set_cursor(win, { start_lines[1], 0 })
1317-
else
1318-
local selected_line = utils.await_pick_line_number_in_file("Select strack trace: ", file_path, start_lines)
1455+
local selected_stack_trace = await_pick_java_stack_trace(bufnr, found_stack_traces)
13191456

1320-
if selected_line then
1321-
vim.api.nvim_win_set_cursor(win, { selected_line, 0 })
1322-
end
1457+
if selected_stack_trace then
1458+
vim.api.nvim_set_current_win(win)
1459+
vim.api.nvim_win_set_buf(win, bufnr)
1460+
vim.api.nvim_win_set_cursor(win, { selected_stack_trace.start_line, 0 })
13231461
end
13241462
end)
13251463
end
@@ -1346,7 +1484,7 @@ function M.setup(opts)
13461484
nargs = "?",
13471485
})
13481486

1349-
vim.api.nvim_create_user_command("JavaHelpersPickStackTrace", function(command_opts)
1487+
vim.api.nvim_create_user_command("JavaHelpersPickStackTrace", function()
13501488
M.pick_java_stack_trace(0)
13511489
end, {
13521490
desc = "Pick Java stack trace",

0 commit comments

Comments
 (0)