diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js index c980ef119..369b79e6e 100644 --- a/frontend/components/ErrorMessage.js +++ b/frontend/components/ErrorMessage.js @@ -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() @@ -88,39 +88,62 @@ const at = html` from ` 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`${frame.call.substr(0, bracket_index)}<${ClickToExpandIfLong} text=${frame.call.substr(bracket_index)} />` - : html`${frame.call}` + const expanded = expanded_state || (frame.call === frame.call_short && frame.func === funcname_args(frame.call)[0]) || silly_to_hide - return html`${inner}` -} + 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)} { - e.preventDefault() - set_expanded(true) - }} - >...Show more...${text.slice(-1)}` + const funcname_display = funcname.match(/^#\d+(#\d+)?$/) + ? html`anonymous function` + : funcname + console.log(funcname, funcname.match(/^#\d+(#\d+)?$/), funcname_display) + + let inner = html`${funcname_display}<${HighlightCallArgumentNames} code=${call_funcname_args[1]} />` + + const id = useMemo(() => Math.random().toString(36).substring(7), [frame]) - return html`${expanded ? text : text.length < LIMIT_LONG ? text : collaped_text}` + return html`${inner} ${!expanded + ? html` { + e.preventDefault() + set_expanded(true) + }} + >...show types...` + : null}` } const LinePreview = ({ frame, num_context_lines = 2 }) => { @@ -172,6 +195,19 @@ const JuliaHighlightedLine = ({ code, frameLine, i }) => { >` } +const HighlightCallArgumentNames = ({ code }) => { + const code_ref = useRef(/** @type {HTMLPreElement?} */ (null)) + useLayoutEffect(() => { + if (code_ref.current) { + const html = code.replaceAll(/([^():{},; ]*)::/g, "$1::") + + code_ref.current.innerHTML = html + } + }, [code_ref.current, code]) + + return html`` +} + 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 }) => { @@ -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 diff --git a/frontend/treeview.css b/frontend/treeview.css index e4fa55fa8..31fbfc2ee 100644 --- a/frontend/treeview.css +++ b/frontend/treeview.css @@ -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; diff --git a/src/runner/PlutoRunner/src/display/Exception.jl b/src/runner/PlutoRunner/src/display/Exception.jl index 835c500ce..66e7e1b76 100644 --- a/src/runner/PlutoRunner/src/display/Exception.jl +++ b/src/runner/PlutoRunner/src/display/Exception.jl @@ -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)), @@ -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