Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack trace: bracket-matched hiding and expanding and more #3045

Merged
merged 2 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 62 additions & 26 deletions frontend/components/ErrorMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const DocLink = ({ frame }) => {
if (frame.parent_module == null) return null
if (ignore_funccall(frame)) return null

const funcname = frame.call.split("(")[0]
const funcname = frame.func
if (funcname === "") return null

const nb = pluto_actions.get_notebook()
Expand Down Expand Up @@ -88,39 +88,62 @@ const at = html`<span> from </span>`
const ignore_funccall = (frame) => frame.call === "top-level scope"
const ignore_location = (frame) => frame.file === "none"

const funcname_args = (call) => {
const anon_match = call.indexOf(")(")
if (anon_match != -1) {
return [call.substring(0, anon_match + 1), call.substring(anon_match + 1)]
} else {
const bracket_index = call.indexOf("(")
if (bracket_index != -1) {
return [call.substring(0, bracket_index), call.substring(bracket_index)]
} else {
return [call, ""]
}
}
}

const Funccall = ({ frame }) => {
if (ignore_funccall(frame)) return null
let [expanded_state, set_expanded] = useState(false)
useEffect(() => {
set_expanded(false)
}, [frame])

const bracket_index = frame.call.indexOf("(")
const silly_to_hide = (frame.call_short.match(/…/g) ?? "").length <= 1 && frame.call.length < frame.call_short.length + 7

let inner =
bracket_index != -1
? html`<strong>${frame.call.substr(0, bracket_index)}</strong><${ClickToExpandIfLong} text=${frame.call.substr(bracket_index)} />`
: html`<strong>${frame.call}</strong>`
const expanded = expanded_state || (frame.call === frame.call_short && frame.func === funcname_args(frame.call)[0]) || silly_to_hide

return html`<mark>${inner}</mark>`
}
if (ignore_funccall(frame)) return null

const LIMIT_LONG = 200,
LIMIT_PREVIEW = 100
const call = expanded ? frame.call : frame.call_short

const ClickToExpandIfLong = ({ text }) => {
let [expanded, set_expanded] = useState(false)
const call_funcname_args = funcname_args(call)
const funcname = expanded ? call_funcname_args[0] : frame.func

useEffect(() => {
set_expanded(false)
}, [text])
// if function name is #12 or #15#16 then it is an anonymous function

const collaped_text = html`${text.slice(0, LIMIT_PREVIEW)}<a
href="#"
onClick=${(e) => {
e.preventDefault()
set_expanded(true)
}}
>...Show more...</a
>${text.slice(-1)}`
const funcname_display = funcname.match(/^#\d+(#\d+)?$/)
? html`<abbr title="A (mini-)function that is defined without the 'function' keyword, but using -> or 'do'.">anonymous function</abbr>`
: funcname
console.log(funcname, funcname.match(/^#\d+(#\d+)?$/), funcname_display)

let inner = html`<strong>${funcname_display}</strong><${HighlightCallArgumentNames} code=${call_funcname_args[1]} />`

const id = useMemo(() => Math.random().toString(36).substring(7), [frame])

return html`<span>${expanded ? text : text.length < LIMIT_LONG ? text : collaped_text}</span>`
return html`<mark id=${id}>${inner}</mark> ${!expanded
? html`<a
aria-expanded=${expanded}
aria-controls=${id}
title="Display the complete type information of this function call"
role="button"
href="#"
onClick=${(e) => {
e.preventDefault()
set_expanded(true)
}}
>...show types...</a
>`
: null}`
}

const LinePreview = ({ frame, num_context_lines = 2 }) => {
Expand Down Expand Up @@ -172,6 +195,19 @@ const JuliaHighlightedLine = ({ code, frameLine, i }) => {
></code>`
}

const HighlightCallArgumentNames = ({ code }) => {
const code_ref = useRef(/** @type {HTMLPreElement?} */ (null))
useLayoutEffect(() => {
if (code_ref.current) {
const html = code.replaceAll(/([^():{},; ]*)::/g, "<span class='argument_name'>$1</span>::")

code_ref.current.innerHTML = html
}
}, [code_ref.current, code])

return html`<s-span ref=${code_ref} class="language-julia"></s-span>`
}

const insert_commas_and_and = (/** @type {any[]} */ xs) => xs.flatMap((x, i) => (i === xs.length - 1 ? [x] : i === xs.length - 2 ? [x, " and "] : [x, ", "]))

export const ParseError = ({ cell_id, diagnostics }) => {
Expand Down Expand Up @@ -218,7 +254,7 @@ export const ParseError = ({ cell_id, diagnostics }) => {
const frame_is_important_heuristic = (frame, frame_index, limited_stacktrace, frame_cell_id) => {
if (frame_cell_id != null) return true

const [funcname, params] = frame.call.split("(", 2)
const [funcname, params] = funcname_args(frame.call)

if (["_collect", "collect_similar", "iterate", "error", "macro expansion"].includes(funcname)) {
return false
Expand Down
8 changes: 8 additions & 0 deletions frontend/treeview.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,14 @@ jlerror > section .classical-frame > mark {
jlerror > section .classical-frame > mark > strong {
color: var(--black);
}
jlerror > section .classical-frame s-span {
/* color: var(--cm-color-type); */
}
jlerror > section .classical-frame s-span .argument_name {
color: var(--jlerror-mark-color);
color: var(--cm-color-var);
color: var(--cm-color-type);
}
jlerror > section .frame-source {
display: flex;
flex-direction: row;
Expand Down
16 changes: 15 additions & 1 deletion src/runner/PlutoRunner/src/display/Exception.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ function format_output(val::CapturedException; context=default_iocontext)
stack_relevant = stack[1:something(limit, end)]

pretty = map(stack_relevant) do s
func = s.func === nothing ? nothing : s.func isa Symbol ? String(s.func) : repr(s.func)
method = method_from_frame(s)
sp = source_package(method)
pm = VERSION >= v"1.9" && method isa Method ? parentmodule(method) : nothing
call = replace(pretty_stackcall(s, s.linfo), r"Main\.var\"workspace#\d+\"\." => "")

Dict(
:call => replace(pretty_stackcall(s, s.linfo), r"Main\.var\"workspace#\d+\"\." => ""),
:call => call,
:call_short => type_depth_limit(call, 0),
:func => func,
:inlined => s.inlined,
:from_c => s.from_c,
:file => basename(String(s.file)),
Expand Down Expand Up @@ -124,6 +128,16 @@ function pretty_stackcall(frame::Base.StackFrame, linfo::Module)
end


function type_depth_limit(call::String, n::Int)
!occursin("{" , call) && return call
@static if isdefined(Base, :type_depth_limit) && hasmethod(Base.type_depth_limit, Tuple{String, Int})
Base.type_depth_limit(call, n)
else
call
end
end


"Because even showerror can error... 👀"
function try_showerror(io::IO, e, args...)
try
Expand Down
Loading