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 URL improvements #3038

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
12 changes: 8 additions & 4 deletions frontend/components/ErrorMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ const StackFrameFilename = ({ frame, cell_id }) => {
e.preventDefault()
}}
>
${frame_cell_id == cell_id ? "This cell" : "Other cell"}: line ${frame.line}
${frame_cell_id == cell_id ? "This\xa0cell" : "Other\xa0cell"}: line ${frame.line}
</a>`
return html`<em>${a}</em>`
} else {
return html`<em title=${frame.path}
><a class="remote-url" href=${frame?.url?.startsWith?.("https") ? frame.url : null}>${frame.file}:${frame.line}</a></em
>`
const text =
frame.source_package != null && frame.source_package_type === "external"
? html`<strong>${frame.source_package}</strong>.jl / ${frame.file}`
: frame.file
const href = frame?.url?.startsWith?.("https") ? frame.url : null
return html`<em title=${frame.path}><a class="remote-url" href=${href}>${text}:${frame.line}${href == null ? null : `\xa0⬏`}</a></em>`
}
}

Expand Down Expand Up @@ -99,6 +102,7 @@ const JuliaHighlightedLine = ({ code, frameLine, i }) => {
useLayoutEffect(() => {
if (code_ref.current) {
code_ref.current.innerText = code
delete code_ref.current.dataset.highlighted
highlight(code_ref.current, "julia")
}
}, [code_ref.current, code])
Expand Down
4 changes: 2 additions & 2 deletions frontend/themes/dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@
/* jlerror */
--jlerror-header-color: #d9baba;
--jlerror-mark-bg-color: rgb(0 0 0 / 18%);
--jlerror-a-bg-color: rgba(82, 58, 58, 1);
--jlerror-a-border-left-color: #704141;
--jlerror-a-bg-color: hsl(65.82deg 17.14% 27.45%);
--jlerror-a-border-left-color: hsl(66 27% 35% / 1);
--jlerror-mark-color: #b1a9a9;

/* helpbox */
Expand Down
4 changes: 4 additions & 0 deletions frontend/treeview.css
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ jlerror > section .classical-frame > mark {
font-family: var(--julia-mono-font-stack);
font-variant-ligatures: none;
}

jlerror > section .classical-frame > mark > strong {
color: var(--black);
}
jlerror > section .classical-frame > em > a[href] {
background: var(--jlerror-a-bg-color);
border-radius: 4px;
Expand Down
1 change: 1 addition & 0 deletions src/runner/PlutoRunner/src/PlutoRunner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ include("./evaluation/deleting globals.jl")


include("./display/format_output.jl")
include("./display/method_url.jl")
include("./display/IOContext.jl")
include("./display/syntax error.jl")
include("./display/Exception.jl")
Expand Down
40 changes: 36 additions & 4 deletions src/runner/PlutoRunner/src/display/Exception.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import .BrowserMacrosLite

"Downstream packages can set this to false to obtain unprettified stack traces."
const PRETTY_STACKTRACES = Ref(true)

Expand All @@ -14,16 +16,30 @@ end

frame_is_from_usercode(frame::Base.StackTraces.StackFrame) = occursin("#==#", String(frame.file))

function frame_url(frame::Base.StackTraces.StackFrame)
function method_from_frame(frame::Base.StackTraces.StackFrame)
if frame.linfo isa Core.MethodInstance
Base.url(frame.linfo.def)
frame.linfo.def
elseif frame.linfo isa Method
Base.url(frame.linfo)
frame.linfo
else
nothing
end
end

function source_package(frame::Base.StackTraces.StackFrame)
if frame.linfo isa Core.MethodInstance
frame.linfo.def
elseif frame.linfo isa Method
frame.linfo
else
nothing
end |> source_package
end

function source_package(x)
BrowserMacrosLite.rootmodule(x)
end

function format_output(val::CapturedException; context=default_iocontext)
if has_julia_syntax && val.ex isa PrettySyntaxError
dict = convert_parse_error_to_dict(val.ex.ex.detail)
Expand All @@ -49,15 +65,31 @@ function format_output(val::CapturedException; context=default_iocontext)
stack_relevant = stack[1:something(limit, end)]

pretty = map(stack_relevant) do s
method = method_from_frame(s)

local url = local repo_url = local repo_type = local sp = nothing
if method isa Method
try
url = BrowserMacrosLite.method_url(method)
repo_url = BrowserMacrosLite._repo_url(url)
repo_type = BrowserMacrosLite.repotype(method)
sp = source_package(method)
catch e
end
end

Dict(
:call => pretty_stackcall(s, s.linfo),
:inlined => s.inlined,
:from_c => s.from_c,
:file => basename(String(s.file)),
:path => String(s.file),
:line => s.line,
:url => frame_url(s),
:linfo_type => string(typeof(s.linfo)),
:url => url,
:repo_url => repo_url,
:source_package => string(sp),
:source_package_type => repo_type,
)
end
else
Expand Down
113 changes: 113 additions & 0 deletions src/runner/PlutoRunner/src/display/method_url.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# This code is taken from https://github.com/adrhill/BrowserMacros.jl
# Thank you Adrian Hill!! ❤️

# It is a bit tricky to add dependencies to PlutoRunner, so we copied the relevant parts here. Hopefully, this will eventually make it into Julia base (https://github.com/JuliaLang/julia/issues/47709)

# License of BrowserMacros.jl:

# MIT License

# Copyright (c) 2022 Adrian Hill <[email protected]>

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


module BrowserMacrosLite

using Base: UUID, PkgId, load_path, url
using Pkg.Types: manifestfile_path, read_manifest
using Pkg
using Pkg.Operations: find_urls

ismatching(r::Regex, s) = !isnothing(match(r, s))
captures(r::Regex, s) = match(r, s).captures
onlycapture(r::Regex, s) = only(match(r, s).captures)

function rootmodule(m)
pm = parentmodule(m)
pm == m && return m
return rootmodule(pm)
end

URI(x) = String(x)


const JULIA_REPO = "https://github.com/JuliaLang/julia"
const PKG_REPO = "https://github.com/JuliaLang/Pkg.jl"

# Step 1: determine type of repository
method_url(m::Method) = method_url(m, Val(repotype(m)))

function repotype(m::Method)
path = String(m.file)
!ismatching(r"src", path) && return :Base
ismatching(r"stdlib", path) && return :stdlib
return :external
end

# Step 2: assemble URL using heuristics
method_url(m::Method, ::Val{:Base}) = URI(Base.url(m))

function method_url(m::Method, ::Val{:stdlib})
ver, package_name, path = captures(r"/stdlib/v(.*?)/(.*?)/src/(.*)", String(m.file))
if package_name == "Pkg"
return URI("$PKG_REPO/blob/release-$ver/src/$path#L$(m.line)")
end
return URI("$JULIA_REPO/blob/v$VERSION/stdlib/$package_name/src/$path#L$(m.line)")
end

function method_url(m::Method, ::Val{:external})
path = onlycapture(r"/src/(.*)", String(m.file))
id = PkgId(m.module)
url = _url(id)
ver = _version(id)
if ismatching(r"gitlab", url)
return URI("$url/~/blob/v$ver/src/$path#L$(m.line)")
end
return URI("$url/blob/v$ver/src/$path#L$(m.line)") # attempt GitHub-like URL
end

# Step 3: use Pkg internals to find repository URL
_manifest(loadpath) = read_manifest(manifestfile_path(dirname(loadpath)))

function _version(id::PkgId)
for path in load_path()
ismatching(r"julia/stdlib", path) && continue
manifest = _manifest(path)
pkg_entry = get(manifest, id.uuid, nothing)
ver = hasproperty(pkg_entry, :version) ? pkg_entry.version : nothing
!isnothing(ver) && return ver # else look up next environment in LOAD_PATH
end
return error("Could not find module $id in project dependencies.")
end

function _url(id::PkgId)
urls = find_urls(Pkg.Registry.reachable_registries(), id.uuid)
isempty(urls) && error("Could not find module $id in reachable registries.")
return first(splitext(first(urls))) # strip ".git" ending
end

# To avoid code duplication, repo_url trims method_url until the fifth "/", e.g.:
# https://github.com/foo/Bar.jl/blob/v0.1.0/src/qux.jl#L7 turns to
# https://github.com/foo/Bar.jl
repo_url(m::Method) = _repo_url(method_url(m))
_repo_url(url::String) = URI(onlycapture(r"((?:.*?\/){4}(?:.*?))\/", url))


end
Loading