diff --git a/frontend/components/PkgTerminalView.js b/frontend/components/PkgTerminalView.js index 6c13c6538f..070806342d 100644 --- a/frontend/components/PkgTerminalView.js +++ b/frontend/components/PkgTerminalView.js @@ -11,9 +11,11 @@ const TerminalViewAnsiUp = ({ value }) => { if (parent) parent.scrollTop = 1e5 }, [node_ref.current, value]) - return html`
` + return !!value + ? html`
` + : null } export const PkgTerminalView = TerminalViewAnsiUp diff --git a/frontend/components/ProcessTab.js b/frontend/components/ProcessTab.js index 03bee6491c..fad12c5f1b 100644 --- a/frontend/components/ProcessTab.js +++ b/frontend/components/ProcessTab.js @@ -3,6 +3,7 @@ import { html, useEffect, useRef, useState } from "../imports/Preact.js" import { cl } from "../common/ClassTable.js" import { prettytime, useMillisSinceTruthy } from "./RunArea.js" import { DiscreteProgressBar } from "./DiscreteProgressBar.js" +import { PkgTerminalView } from "./PkgTerminalView.js" /** * @param {{ @@ -13,7 +14,7 @@ import { DiscreteProgressBar } from "./DiscreteProgressBar.js" export let ProcessTab = ({ notebook, my_clock_is_ahead_by }) => { return html`
- <${StatusItem} status_tree=${notebook.status_tree} my_clock_is_ahead_by=${my_clock_is_ahead_by} path=${[]} /> + <${StatusItem} status_tree=${notebook.status_tree} path=${[]} my_clock_is_ahead_by=${my_clock_is_ahead_by} nbpkg=${notebook.nbpkg} />
` } @@ -76,9 +77,10 @@ const to_ns = (x) => x * 1e9 * status_tree: import("./Editor.js").StatusEntryData?, * path: string[], * my_clock_is_ahead_by: number, + * nbpkg: import("./Editor.js").NotebookPkgData?, * }} props */ -const StatusItem = ({ status_tree, path, my_clock_is_ahead_by }) => { +const StatusItem = ({ status_tree, path, my_clock_is_ahead_by, nbpkg }) => { if (status_tree == null) return null const mystatus = path.reduce((entry, key) => entry.subtasks[key], status_tree) if (!mystatus) return null @@ -129,7 +131,13 @@ const StatusItem = ({ status_tree, path, my_clock_is_ahead_by }) => { .map(([key, _subtask]) => blocklist.includes(key) ? null - : html`<${StatusItem} key=${key} status_tree=${status_tree} my_clock_is_ahead_by=${my_clock_is_ahead_by} path=${[...path, key]} />` + : html`<${StatusItem} + key=${key} + status_tree=${status_tree} + my_clock_is_ahead_by=${my_clock_is_ahead_by} + path=${[...path, key]} + nbpkg=${nbpkg} + />` ) const render_child_progress = () => { @@ -179,7 +187,7 @@ const StatusItem = ({ status_tree, path, my_clock_is_ahead_by }) => { ${friendly_name(mystatus.name)}${inner_progress} ${finished ? prettytime(to_ns(end - start)) : busy ? prettytime(to_ns(busy_time)) : null} - ${inner} + ${inner}${is_open && mystatus.name === "pkg" ? html`<${PkgTerminalView} value=${nbpkg?.terminal_outputs?.nbpkg_sync} />` : undefined} ` } diff --git a/frontend/editor.css b/frontend/editor.css index d86fa0415d..7c4ee7da17 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2040,6 +2040,12 @@ pluto-helpbox { border-top-right-radius: 9px; box-shadow: 0 0 11px 0px var(--helpbox-box-shadow-color); } +pluto-helpbox > section { + height: 100%; + overflow: auto; + padding: 10px; +} + pluto-helpbox > header { display: flex; padding: 0.6em; @@ -2273,54 +2279,49 @@ pluto-helpbox.hidden > section { /* https://docs.julialang.org/en/v1/assets/themes/documenter-light.css */ /* see https://github.com/JuliaDocs/Documenter.jl for author information */ -pluto-helpbox { +.helpbox-docs { font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", sans-serif; line-height: 1.5; font-size: 0.9rem; } -pluto-helpbox pre, -pluto-helpbox code, -pluto-helpbox .cm-line { +.helpbox-docs pre, +.helpbox-docs code, +.helpbox-docs .cm-line { /* from https://docs.julialang.org/en/v1/assets/themes/documenter-light.css */ - font-family: "JuliaMono", "SFMono-Regular", "Menlo", "Consolas", "Liberation Mono", "DejaVu Sans Mono", monospace; + font-family: "Roboto Mono", "SFMono-Regular", "Menlo", "Consolas", "Liberation Mono", "DejaVu Sans Mono", monospace; font-size: 0.95em; line-height: initial; } -pluto-helpbox pre code { +.helpbox-docs pre code { font-size: 1em; } -pluto-helpbox pre code.hljs { +.helpbox-docs pre code.hljs { padding: 0; } -pluto-helpbox code .cm-editor .cm-content { +.helpbox-docs code .cm-editor .cm-content { padding: 0px; } -pluto-helpbox img { +.helpbox-docs img { max-width: 100%; } -pluto-helpbox > section { - height: 100%; - overflow: auto; - padding: 10px; -} -pluto-helpbox > section h1, -pluto-helpbox > section h2, -pluto-helpbox > section h3, -pluto-helpbox > section h4, -pluto-helpbox > section h5, -pluto-helpbox > section h6 { +.helpbox-docs > section h1, +.helpbox-docs > section h2, +.helpbox-docs > section h3, +.helpbox-docs > section h4, +.helpbox-docs > section h5, +.helpbox-docs > section h6 { font-family: inherit; border-bottom: none; font-size: 1rem; } -pluto-helpbox > section h1 { +.helpbox-docs > section h1 { font-size: 1.3rem; } -pluto-helpbox > section pre { +.helpbox-docs > section pre { padding: 0.7rem 0.5rem; -webkit-overflow-scrolling: touch; overflow-x: auto; @@ -2330,11 +2331,11 @@ pluto-helpbox > section pre { white-space: pre; word-wrap: normal; } -/* pluto-helpbox > section code { +/* .helpbox-docs > section code { background-color: whitesmoke; padding: 0.1em; } */ -pluto-helpbox > section hr { +.helpbox-docs > section hr { border: none; border-top: 3px solid var(--rule-color); } @@ -2379,11 +2380,13 @@ pluto-helpbox > section hr { pl-status { --status-color: var(--process-undefined); font-family: var(--system-ui-font-stack); + font-size: 0.9rem; display: flex; flex-direction: column; border-radius: 0.2em; /* margin: 0.3em; */ - margin-left: 0.7em; + --indent: 0.7rem; + margin-left: var(--indent); border-left: 3px solid transparent; margin-top: 0.4em; overflow: hidden; @@ -2522,6 +2525,10 @@ pl-status .status-time { gap: 0.5px; } +pl-status pkg-terminal { + margin-left: var(--indent); +} + /* FOOTER */ footer { diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index 866c072146..0672d6752d 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -101,31 +101,34 @@ function sync_nbpkg_core( removed = setdiff(old_packages, new_packages) added = setdiff(new_packages, old_packages) + can_skip = isempty(removed) && isempty(added) && notebook.nbpkg_ctx_instantiated iolistener = let busy_packages = notebook.nbpkg_ctx_instantiated ? added : new_packages - IOListener(callback=(s -> on_terminal_output(busy_packages, s))) + report_to = ["nbpkg_sync", busy_packages...] + IOListener(callback=(s -> on_terminal_output(report_to, s))) end cleanup[] = () -> stoplistening(iolistener) - - # We remember which Pkg.Types.PreserveLevel was used. If it's too low, we will recommend/require a notebook restart later. - local used_tier = Pkg.PRESERVE_ALL - - if !isready(pkg_token) - println(iolistener.buffer, "Waiting for other notebooks to finish Pkg operations...") - trigger(iolistener) - end - can_skip = isempty(removed) && isempty(added) && notebook.nbpkg_ctx_instantiated + Status.report_business_finished!(pkg_status, :analysis) + # We remember which Pkg.Types.PreserveLevel was used. If it's too low, we will recommend/require a notebook restart later. + local used_tier = Pkg.PRESERVE_ALL if !can_skip + # We have a global lock, `pkg_token`, on Pluto-managed Pkg operations, which is shared between all notebooks. If this lock is not ready right now then that means that we are going to wait at the `withtoken(pkg_token)` line below. + # We want to report that we are waiting, with a best guess of why. wait_business = if !isready(pkg_token) + reg = !PkgCompat._updated_registries_compat[] + + # Print something in the terminal logs + println(iolistener.buffer, "Waiting for $(reg ? "the package registry to update" : "other notebooks to finish Pkg operations")...") + trigger(iolistener) # manual trigger because we did not start listening yet + + # Create a business item Status.report_business_started!(pkg_status, - !PkgCompat._updated_registries_compat[] ? - :registry_update : - :waiting_for_others + reg ? :registry_update : :waiting_for_others ) end @@ -206,6 +209,8 @@ function sync_nbpkg_core( Status.report_business_started!(pkg_status, :add) start_time = time_ns() with_io_setup(notebook, iolistener) do + println(iolistener.buffer, "\nAdding packages...") + # We temporarily clear the "semver-compatible" [deps] entries, because Pkg already respects semver, unless it doesn't, in which case we don't want to force it. PkgCompat.clear_auto_compat_entries!(notebook.nbpkg_ctx) @@ -380,8 +385,8 @@ end function instantiate(notebook::Notebook, iolistener::IOListener) start_time = time_ns() - startlistening(iolistener) with_io_setup(notebook, iolistener) do + println(iolistener.buffer, "\nInstantiating...") @debug "PlutoPkg: Instantiating" notebook.path # update registries if this is the first time @@ -411,6 +416,7 @@ end function resolve(notebook::Notebook, iolistener::IOListener) startlistening(iolistener) with_io_setup(notebook, iolistener) do + println(iolistener.buffer, "\nResolving...") @debug "PlutoPkg: Instantiating" notebook.path Pkg.resolve(notebook.nbpkg_ctx) end @@ -488,6 +494,7 @@ function update_nbpkg_core( iolistener = let # we don't know which packages will be updated, so we send terminal output to all installed packages + report_to = ["nbpkg_update", old_packages...] IOListener(callback=(s -> on_terminal_output(old_packages, s))) end cleanup[] = () -> stoplistening(iolistener) diff --git a/src/webserver/Status.jl b/src/webserver/Status.jl index 0cc196aa9d..ec3ea910cd 100644 --- a/src/webserver/Status.jl +++ b/src/webserver/Status.jl @@ -6,6 +6,7 @@ Base.@kwdef mutable struct Business started_at::Union{Nothing,Float64}=nothing finished_at::Union{Nothing,Float64}=nothing subtasks::Dict{Symbol,Business}=Dict{Symbol,Business}() + update_listener_ref::Ref{Union{Nothing,Function}}=Ref{Union{Nothing,Function}}(nothing) lock::Threads.SpinLock=Threads.SpinLock() end