From 99cf0e4a469d0d74d01db5f4baab307ea6439b2c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 3 Oct 2024 12:59:29 +0200 Subject: [PATCH] Stack trace tweaks part 2 --- frontend/components/ErrorMessage.js | 96 ++++++++++++++++--- frontend/treeview.css | 25 +++-- .../PlutoRunner/src/display/Exception.jl | 2 + 3 files changed, 102 insertions(+), 21 deletions(-) diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js index 21f120e19..03db50bd1 100644 --- a/frontend/components/ErrorMessage.js +++ b/frontend/components/ErrorMessage.js @@ -4,6 +4,7 @@ import { html, useContext, useEffect, useLayoutEffect, useRef, useState } from " import { highlight } from "./CellOutput.js" import { PkgTerminalView } from "./PkgTerminalView.js" import _ from "../imports/lodash.js" +import { open_bottom_right_panel } from "./BottomRightPanel.js" const extract_cell_id = (/** @type {string} */ file) => { const sep_index = file.indexOf("#==#") @@ -24,12 +25,40 @@ const focus_line = (cell_id, line) => }) ) +const DocLink = ({ frame }) => { + let pluto_actions = useContext(PlutoActionsContext) + + if (frame.parent_module == null) return null + if (ignore_funccall(frame)) return null + + const nb = pluto_actions.get_notebook() + + const pkg_name = frame.source_package + const builtin = ["Main", "Core", "Base"].includes(pkg_name) + const installed = nb?.nbpkg?.installed_versions?.[frame.source_package] != null + + if (!builtin && nb?.nbpkg != null && !installed) return null + + return html`   { + e.preventDefault() + open_bottom_right_panel("docs") + pluto_actions.set_doc_query(`${frame.parent_module}.${frame.call.split("(")[0]}`) + }} + >docs` +} + const StackFrameFilename = ({ frame, cell_id }) => { if (ignore_location(frame)) return null const frame_cell_id = extract_cell_id(frame.file) if (frame_cell_id != null) { - const a = html` { @@ -37,17 +66,18 @@ const StackFrameFilename = ({ frame, cell_id }) => { e.preventDefault() }} > - ${frame_cell_id == cell_id ? "This\xa0cell" : "Other\xa0cell"}: line ${frame.line} + ${frame_cell_id == cell_id ? "This\xa0cell" : "Other\xa0cell"}: line ${frame.line} ` - return html`${a}` } else { const sp = frame.source_package const origin = ["Main", "Core", "Base"].includes(sp) ? "julia" : sp - const text = sp != null ? html`${origin} → ${frame.file}` : frame.file + const file_line = html`${frame.file}:${frame.line}` + + const text = sp != null ? html`${origin} → ${file_line}` : file_line const href = frame?.url?.startsWith?.("https") ? frame.url : null - return html`${text}:${frame.line}` + return html`${text}` } } @@ -66,9 +96,12 @@ const Funccall = ({ frame }) => { ? html`${frame.call.substr(0, bracket_index)}<${ClickToExpandIfLong} text=${frame.call.substr(bracket_index)} />` : html`${frame.call}` - return html`${inner}${at}` + return html`${inner}` } +const LIMIT_LONG = 200, + LIMIT_PREVIEW = 100 + const ClickToExpandIfLong = ({ text }) => { let [expanded, set_expanded] = useState(false) @@ -76,16 +109,16 @@ const ClickToExpandIfLong = ({ text }) => { set_expanded(false) }, [text]) - const collaped_text = html`${text.slice(0, 250)} { e.preventDefault() set_expanded(true) }} - >...more......Show more...${text.slice(-1)}` - return html` ${expanded ? text : text.length < 300 ? text : collaped_text} ` + return html`${expanded ? text : text.length < LIMIT_LONG ? text : collaped_text}` } const LinePreview = ({ frame, num_context_lines = 2 }) => { @@ -161,7 +194,7 @@ export const ParseError = ({ cell_id, diagnostics }) => { ${diagnostics.map( ({ message, from, to, line }) => html`
  • // NOTE: this could be moved move to `StackFrameFilename` window.dispatchEvent(new CustomEvent("cell_highlight_range", { detail: { cell_id, from, to } }))} @@ -169,7 +202,11 @@ export const ParseError = ({ cell_id, diagnostics }) => { window.dispatchEvent(new CustomEvent("cell_highlight_range", { detail: { cell_id, from: null, to: null } }))} >
    - ${message}${at}<${StackFrameFilename} frame=${{ file: "#==#" + cell_id, line }} cell_id=${cell_id} /> + ${message} +
    + ${at} + <${StackFrameFilename} frame=${{ file: "#==#" + cell_id, line }} cell_id=${cell_id} /> +
  • ` )} @@ -179,6 +216,29 @@ 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) + + if (["_collect", "collect_similar", "iterate", "error", "macro expansion"].includes(funcname)) { + return false + } + + if (params == null) { + // no type signature... must be some function call that got optimized away or something special + // probably not directly relevant + return false + } + + if ((funcname.match(/#/g) ?? "").length >= 2) { + // anonymous function: #plot#142 + return false + } + + return true +} + export const ErrorMessage = ({ msg, stacktrace, cell_id }) => { let pluto_actions = useContext(PlutoActionsContext) const default_rewriter = { @@ -371,20 +431,26 @@ export const ErrorMessage = ({ msg, stacktrace, cell_id }) => {
      - ${limited_stacktrace.map((frame) => { + ${limited_stacktrace.map((frame, frame_index) => { const frame_cell_id = extract_cell_id(frame.file) const from_this_notebook = frame_cell_id != null const from_this_cell = cell_id === frame_cell_id - return html`
    1. + const important = frame_is_important_heuristic(frame, frame_index, limited_stacktrace, frame_cell_id) + + return html`
    2. <${Funccall} frame=${frame} /> - <${StackFrameFilename} frame=${frame} cell_id=${cell_id} /> +
      + ${at} + <${StackFrameFilename} frame=${frame} cell_id=${cell_id} /> + <${DocLink} frame=${frame} /> +
      ${from_this_notebook ? html`<${LinePreview} frame=${frame} num_context_lines=${from_this_cell ? 1 : 2} />` : null}
    3. ` })} ${limited - ? html`
    4. + ? html`
    5. { diff --git a/frontend/treeview.css b/frontend/treeview.css index 3de86abcc..dba349629 100644 --- a/frontend/treeview.css +++ b/frontend/treeview.css @@ -285,11 +285,18 @@ jlerror > section > ol { /* transform-origin: top; */ /* perspective-origin: top; */ } +jlerror > section > ol > li { + margin-block-end: 1em; +} + +jlerror > section > ol > li:not(.important):not(:hover) { + opacity: 0.5; +} jlerror > section > ol > li.from_this_notebook { --bg: var(--jl-info-acccolor); background: var(--bg); outline: 3px solid var(--bg); - padding: 0.4em 0.2em; + padding: 0.4em 0em; border-radius: 0.6em; } jlerror > section .classical-frame > mark { @@ -302,7 +309,13 @@ jlerror > section .classical-frame > mark { jlerror > section .classical-frame > mark > strong { color: var(--black); } -jlerror > section .classical-frame > em > a { +jlerror > section .frame-source { + display: flex; + flex-direction: row; + align-items: baseline; + /* justify-content: flex-end; */ +} +jlerror > section .frame-source > a { background: var(--jlerror-a-bg-color); border-radius: 4px; padding: 1px 7px; @@ -310,16 +323,16 @@ jlerror > section .classical-frame > em > a { border-left: 3px solid var(--jlerror-a-border-left-color); /* font-family: var(--system-ui-font-stack); */ } -jlerror > section .classical-frame > em > a:not([href]) { +jlerror > section .frame-source > a:not([href]) { filter: grayscale(1); } -jlerror > section .classical-frame > em > a[href].remote-url { +jlerror > section .frame-source > a[href].remote-url { filter: hue-rotate(160deg); } -jlerror > section li.from_this_notebook:not(.from_this_cell) .classical-frame > em > a[href] { +jlerror > section li.from_this_notebook:not(.from_this_cell) .frame-source > a[href] { filter: hue-rotate(50deg); } -jlerror > section .classical-frame > span { +jlerror > section .frame-source > span { opacity: 0.4; padding: 0px 0.2em; } diff --git a/src/runner/PlutoRunner/src/display/Exception.jl b/src/runner/PlutoRunner/src/display/Exception.jl index d292e170b..6ee21618a 100644 --- a/src/runner/PlutoRunner/src/display/Exception.jl +++ b/src/runner/PlutoRunner/src/display/Exception.jl @@ -62,6 +62,7 @@ function format_output(val::CapturedException; context=default_iocontext) pretty = map(stack_relevant) do s method = method_from_frame(s) sp = source_package(method) + pm = method isa Method ? parentmodule(method) : nothing Dict( :call => pretty_stackcall(s, s.linfo), @@ -73,6 +74,7 @@ function format_output(val::CapturedException; context=default_iocontext) :linfo_type => string(typeof(s.linfo)), :url => frame_url(method), :source_package => sp === nothing ? nothing : string(sp), + :parent_module => pm === nothing ? nothing : string(pm), ) end else