From 1296666c0894f04be10ba0169fc43cfe82db7eeb Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 9 Jun 2021 17:14:56 +0200 Subject: [PATCH 01/71] Allow fancy paste in the cell input --- frontend/components/CellInput.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index b47f98162b..3854dbc909 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -473,15 +473,31 @@ export const CellInput = ({ }) cm.on("paste", (cm, e) => { - const topaste = e.clipboardData.getData("text/plain") - const deserializer = detect_deserializer(topaste, false) - if (deserializer != null) { - pluto_actions.add_deserialized_cells(topaste, -1, deserializer) - e.stopImmediatePropagation() + console.log(cell_id) + const cdata = e.clipboardData + if (cdata.files.length == 0) { // The clipboard is not a file + const topaste = e.clipboardData.getData("text/plain") + const deserializer = detect_deserializer(topaste, false) + if (deserializer != null) { + pluto_actions.add_deserialized_cells(topaste, -1, deserializer) + e.stopImmediatePropagation() + e.preventDefault() + e.codemirrorIgnore = true + } + e.stopPropagation() + } else { /* We want to capture the file and save it on the computer */ e.preventDefault() - e.codemirrorIgnore = true + let myBlob = cdata.files[0] + let ftype,fext + [ftype, fext] = myBlob.type.split("/") /* Separate image type and extension */ + // Create the href to download the image + let link = document.createElement("a") + link.href = URL.createObjectURL(myBlob) /* Create URL from the Blob */ + let fname = cell_id + "." + fext /* Create the file name from t he cell_id */ + link.download = fname + link.click() // Downlaod the file + cm.setValue(`show_pasted_image(\"${fname}\")`) } - e.stopPropagation() }) cm.on("mousedown", (cm, e) => { From 3fb5198fdd1314c3e6cc3309d2b48c8964b01597 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 6 Jul 2021 21:13:29 +0200 Subject: [PATCH 02/71] Merge v15.0 from Pluto Some Pkg tweaks (#1292) --- frontend/components/CellInput.js | 87 +++ frontend/components/PkgStatusMark.js | 172 ++++++ sample/JavaScript.jl | 84 +++ sample/Plots.jl.jl | 811 +++++++++++++++++++++++++++ sample/PlutoUI.jl.jl | 21 +- src/analysis/topological_order.jl | 5 +- src/notebook/Notebook.jl | 30 + src/packages/Packages.jl | 499 ++++++++++++++++ src/packages/PkgCompat.jl | 456 +++++++++++++++ 9 files changed, 2162 insertions(+), 3 deletions(-) create mode 100644 frontend/components/PkgStatusMark.js create mode 100644 src/packages/Packages.jl create mode 100644 src/packages/PkgCompat.jl diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 3854dbc909..4ad99c5f2a 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -4,6 +4,7 @@ import observablehq_for_myself from "../common/SetupCellEnvironment.js" import { utf8index_to_ut16index } from "../common/UnicodeTools.js" import { has_ctrl_or_cmd_pressed, map_cmd_to_ctrl_on_mac } from "../common/KeyboardShortcuts.js" import { PlutoContext } from "../common/PlutoContext.js" +import { nbpkg_fingerprint, PkgStatusMark, PkgActivateMark, pkg_disablers } from "./PkgStatusMark.js" //@ts-ignore import { mac, chromeOS } from "https://cdn.jsdelivr.net/gh/codemirror/CodeMirror@5.60.0/src/util/browser.js" @@ -73,6 +74,92 @@ export const CellInput = ({ const time_last_being_force_focussed_ref = useRef(0) const time_last_genuine_backspace = useRef(0) + const pkg_bubbles = useRef(new Map()) + + const nbpkg_ref = useRef(nbpkg) + useEffect(() => { + nbpkg_ref.current = nbpkg + pkg_bubbles.current.forEach((b) => { + b.on_nbpkg(nbpkg) + }) + // console.log("nbpkg effect!", nbpkg_fingerprint(nbpkg)) + }, nbpkg_fingerprint(nbpkg)) + + const update_line_bubbles = (line_i) => { + const cm = cm_ref.current + /** @type {string} */ + const line = cm.getLine(line_i) + if (line != undefined) { + // search for the "import Example, Plots" expression using regex + + // dunno + // const re = /(using|import)\s*(\w+(?:\,\s*\w+)*)/g + + // import A: b. c + // const re = /(using|import)(\s*\w+(\.\w+)*(\s*\:(\s*\w+\,)*(\s*\w+)?))/g + + // import A, B, C + const re = /(using|import)(\s*\w+(\.\w+)*)(\s*\,\s*\w+(\.\w+)*)*/g + // const re = /(using|import)\s*(\w+)/g + for (const import_match of line.matchAll(re)) { + const start = import_match.index + import_match[1].length + + // ask codemirror what its parser found for the "import" or "using" word. If it is not a "keyword", then this is part of a comment or a string. + const import_token = cm.getTokenAt({ line: line_i, ch: start }, true) + + if (import_token.type === "keyword") { + const inner = import_match[0].substr(import_match[1].length) + + // find the package name, e.g. `Plot` for `Plot.Extras.coolplot` + const inner_re = /(\w+)(\.\w+)*/g + for (const package_match of inner.matchAll(inner_re)) { + const package_name = package_match[1] + + if (package_name !== "Base" && package_name !== "Core") { + // if the widget already exists, keep it, if not, create a new one + const widget = get(pkg_bubbles.current, package_name, () => { + const b = PkgStatusMark({ + pluto_actions: pluto_actions, + package_name: package_name, + refresh_cm: () => cm.refresh(), + notebook_id: notebook_id, + }) + b.on_nbpkg(nbpkg_ref.current) + return b + }) + + cm.setBookmark( + { line: line_i, ch: start + package_match.index + package_match[0].length }, + { + widget: widget, + } + ) + } + } + } + } + + const match = _.find(pkg_disablers, (f_name) => line.includes(f_name)) + if (match != null) { + // if the widget already exists, keep it, if not, create a new one + const widget = get(pkg_bubbles.current, `disable-pkg-${match}-${line_i}`, () => + PkgActivateMark({ + package_name: match, + refresh_cm: () => cm.refresh(), + }) + ) + + cm.setBookmark( + { line: line_i, ch: 999 }, + { + widget: widget, + } + ) + } + } + } + const update_all_line_bubbles = () => range(0, cm_ref.current.lineCount() - 1).forEach(update_line_bubbles) + useEffect(() => { const first_time = remote_code_ref.current == null const current_value = cm_ref.current?.getValue() ?? "" diff --git a/frontend/components/PkgStatusMark.js b/frontend/components/PkgStatusMark.js new file mode 100644 index 0000000000..0de9d2dd82 --- /dev/null +++ b/frontend/components/PkgStatusMark.js @@ -0,0 +1,172 @@ +import _ from "../imports/lodash.js" +import { html as phtml } from "../imports/Preact.js" + +import observablehq_for_myself from "../common/SetupCellEnvironment.js" +// widgets inside codemirror need to be DOM elements, not Preact VDOM components. So in this code, we will use html from observablehq, which is just like html from Preact, except it creates DOM nodes directly, not Preact VDOM elements. +const html = observablehq_for_myself.html + +export const nbpkg_fingerprint = (nbpkg) => (nbpkg == null ? [null] : Object.entries(nbpkg)) + +export const nbpkg_fingerprint_without_terminal = (nbpkg) => + nbpkg == null ? [null] : Object.entries(nbpkg).flatMap(([k, v]) => (k === "terminal_outputs" ? [] : [v])) + +const can_update = (installed, available) => { + if (installed === "stdlib" || !_.isArray(available)) { + return false + } else { + // return true + return _.last(available) !== installed + } +} + +export const package_status = ({ nbpkg, package_name, available_versions, is_disable_pkg }) => { + let status = null + let hint_raw = null + let hint = null + let offer_update = false + const chosen_version = nbpkg?.installed_versions[package_name] + const busy = (nbpkg?.busy_packages ?? []).includes(package_name) || nbpkg?.instantiating + + if (is_disable_pkg) { + const f_name = package_name.substring(0, package_name.length - 1) + status = "disable_pkg" + hint_raw = `${f_name} disables Pluto's built-in package manager.` + hint = phtml`${f_name} disables Pluto's built-in package manager.` + } else if (chosen_version != null || _.isEqual(available_versions, ["stdlib"])) { + if (chosen_version == null || chosen_version === "stdlib") { + status = "installed" + hint_raw = `${package_name} is part of Julia's pre-installed 'standard library'.` + hint = phtml`${package_name} is part of Julia's pre-installed standard library.` + } else { + if (busy) { + status = "busy" + hint_raw = `${package_name} (v${chosen_version}) is installing...` + hint = phtml`
${package_name} v${chosen_version}
is installing...` + } else { + status = "installed" + hint_raw = `${package_name} (v${chosen_version}) is installed in the notebook.` + hint = phtml`
${package_name} v${chosen_version}
is installed in the notebook.` + offer_update = can_update(chosen_version, available_versions) + } + } + } else { + if (_.isArray(available_versions)) { + if (available_versions.length === 0) { + status = "not_found" + hint_raw = `The package "${package_name}" could not be found in the registry. Did you make a typo?` + hint = phtml`The package "${package_name}" could not be found in the registry.
Did you make a typo?
` + } else { + status = "will_be_installed" + hint_raw = `${package_name} (v${_.last(available_versions)}) will be installed in the notebook when you run this cell.` + hint = phtml`
${package_name} v${_.last( + available_versions + )}
will be installed in the notebook when you run this cell.` + } + } + } + + return { status, hint, hint_raw, available_versions, chosen_version, busy, offer_update } +} + +// not preact because we're too cool +export const PkgStatusMark = ({ package_name, refresh_cm, pluto_actions, notebook_id }) => { + const button = html`` + const node = html`${button}` + const nbpkg_ref = { current: null } + const available_versions_ref = { current: null } + + const render = () => { + const { status, hint_raw } = package_status({ + nbpkg: nbpkg_ref.current, + package_name: package_name, + is_disable_pkg: false, + available_versions: available_versions_ref.current, + }) + + node.title = hint_raw + + node.classList.toggle("busy", status === "busy") + node.classList.toggle("installed", status === "installed") + node.classList.toggle("not_found", status === "not_found") + node.classList.toggle("will_be_installed", status === "will_be_installed") + + // We don't need to refresh the codemirror for most updates because every mark is exactly the same size. + // refresh_cm() + } + + node.on_nbpkg = (p) => { + // if nbpkg is switched on/off + if ((nbpkg_ref.current == null) !== (p == null)) { + // refresh codemirror because the mark will appear/disappear + refresh_cm() + setTimeout(refresh_cm, 1000) + } + nbpkg_ref.current = p + render() + } + ;(pluto_actions.get_avaible_versions({ package_name, notebook_id }) ?? Promise.resolve([])).then((versions) => { + available_versions_ref.current = versions + render() + }) + + button.onclick = () => { + window.dispatchEvent( + new CustomEvent("open nbpkg popup", { + detail: { + status_mark_element: node, + package_name: package_name, + is_disable_pkg: false, + }, + }) + ) + } + + return node +} + +// not preact because we're too cool +export const PkgActivateMark = ({ package_name, refresh_cm }) => { + const button = html`` + const node = html`${button}` + + const render = () => { + const { hint_raw } = package_status({ + nbpkg: null, + package_name: package_name, + is_disable_pkg: true, + available_versions: null, + }) + node.title = hint_raw + node.classList.toggle("disable_pkg", true) + } + render() + + node.on_nbpkg = (p) => {} + + button.onclick = () => { + window.dispatchEvent( + new CustomEvent("open nbpkg popup", { + detail: { + status_mark_element: node, + package_name: package_name, + is_disable_pkg: true, + }, + }) + ) + } + + return node +} + +// This list appears multiple times in our codebase. Be sure to match edits everywhere. +export const pkg_disablers = [ + "Pkg.activate(", + "Pkg.API.activate(", + "Pkg.develop(", + "Pkg.API.develop(", + "Pkg.add(", + "Pkg.API.add(", + // https://juliadynamics.github.io/DrWatson.jl/dev/project/#DrWatson.quickactivate + "quickactivate(", + "@quickactivate", +] diff --git a/sample/JavaScript.jl b/sample/JavaScript.jl index e9c5524d20..83d5b770af 100644 --- a/sample/JavaScript.jl +++ b/sample/JavaScript.jl @@ -997,6 +997,90 @@ $(embed_display(rand(4))) You can [learn more](https://github.com/fonsp/Pluto.jl/pull/1126) about how this feature works, or how to use it inside packages. """ +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" + +[compat] +HypertextLiteral = "~0.8.0" +PlutoUI = "~0.7.9" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[HypertextLiteral]] +git-tree-sha1 = "1e3ccdc7a6f7b577623028e0095479f4727d8ec1" +uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +version = "0.8.0" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.1" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "1.1.0" + +[[PlutoUI]] +deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"] +git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb" +uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +version = "0.7.9" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[Reexport]] +git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.1.0" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[Suppressor]] +git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787" +uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb" +version = "0.2.0" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +""" + # ╔═╡ Cell order: # ╟─97914842-76d2-11eb-0c48-a7eedca870fb # ╠═571613a1-6b4b-496d-9a68-aac3f6a83a4b diff --git a/sample/Plots.jl.jl b/sample/Plots.jl.jl index 68aaf2d25e..bfa0dc325e 100644 --- a/sample/Plots.jl.jl +++ b/sample/Plots.jl.jl @@ -230,6 +230,817 @@ md""" Try changing the function for the base plot, or the addition in the bottom cell. No weird effects! """ +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + +[compat] +Plots = "~1.18.0" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +[[Adapt]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7" +uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +version = "3.3.1" + +[[ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" + +[[Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.6+5" + +[[Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "e2f47f6d8337369411569fd45ae5753ca10394c6" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.16.0+6" + +[[ColorSchemes]] +deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random", "StaticArrays"] +git-tree-sha1 = "c8fd01e4b736013bc61b704871d20503b33ea402" +uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" +version = "3.12.1" + +[[ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.0" + +[[Colors]] +deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] +git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40" +uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" +version = "0.12.8" + +[[Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "dc7dedc2c2aa9faf59a55c622760a25cbefbe941" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "3.31.0" + +[[CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" + +[[Contour]] +deps = ["StaticArrays"] +git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7" +uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" +version = "0.5.7" + +[[DataAPI]] +git-tree-sha1 = "ee400abb2298bd13bfc3df1c412ed228061a2385" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.7.0" + +[[DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.9" + +[[DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[Downloads]] +deps = ["ArgTools", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" + +[[EarCut_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "92d8f9f208637e8d2d28c664051a00569c01493d" +uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5" +version = "2.1.5+1" + +[[Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.2.10+0" + +[[FFMPEG]] +deps = ["FFMPEG_jll"] +git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8" +uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" +version = "0.4.1" + +[[FFMPEG_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "LibVPX_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "3cc57ad0a213808473eafef4845a74766242e05f" +uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" +version = "4.3.1+4" + +[[FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.4" + +[[Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "35895cf184ceaab11fd778b4590144034a167a2f" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.1+14" + +[[Formatting]] +deps = ["Printf"] +git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8" +uuid = "59287772-0a20-5a39-b81b-1366585eb4c0" +version = "0.4.2" + +[[FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.10.1+5" + +[[FriBidi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91" +uuid = "559328eb-81f9-559d-9380-de523a88c83c" +version = "1.0.10+0" + +[[GLFW_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"] +git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157" +uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" +version = "3.3.5+0" + +[[GR]] +deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"] +git-tree-sha1 = "b83e3125048a9c3158cbb7ca423790c7b1b57bea" +uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" +version = "0.57.5" + +[[GR_jll]] +deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "e14907859a1d3aee73a019e7b3c98e9e7b8b5b3e" +uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" +version = "0.57.3+0" + +[[GeometryBasics]] +deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"] +git-tree-sha1 = "15ff9a14b9e1218958d3530cc288cf31465d9ae2" +uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326" +version = "0.3.13" + +[[Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "47ce50b742921377301e15005c96e979574e130b" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.68.1+0" + +[[Grisu]] +git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" +uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" +version = "1.0.2" + +[[HTTP]] +deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] +git-tree-sha1 = "c6a1fff2fd4b1da29d3dccaffb1e1001244d844e" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "0.9.12" + +[[IniFile]] +deps = ["Test"] +git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8" +uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" +version = "0.5.0" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[IterTools]] +git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18" +uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +version = "1.3.0" + +[[IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[JLLWrappers]] +deps = ["Preferences"] +git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.3.0" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.1" + +[[JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "2.1.0+0" + +[[LAME_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c" +uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" +version = "3.100.1+0" + +[[LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.1+0" + +[[LaTeXStrings]] +git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.2.1" + +[[Latexify]] +deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"] +git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92" +uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" +version = "0.15.6" + +[[LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" + +[[LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" + +[[LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" + +[[LibVPX_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "12ee7e23fa4d18361e7c2cde8f8337d4c3101bc7" +uuid = "dd192d2f-8180-539f-9fb4-cc70b1dcf69a" +version = "1.10.0+0" + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+0" + +[[Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"] +git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.7+0" + +[[Libglvnd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"] +git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf" +uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" +version = "1.3.0+3" + +[[Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.42.0+0" + +[[Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.16.1+1" + +[[Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.35.0+0" + +[[Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.3.0+0" + +[[Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.36.0+0" + +[[LinearAlgebra]] +deps = ["Libdl"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.6" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] +git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.0.3" + +[[MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" + +[[Measures]] +git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f" +uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" +version = "0.3.1" + +[[Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "4ea90bd5d3985ae1f9a908bd4500ae88921c5ce7" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.0.0" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" + +[[NaNMath]] +git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "0.3.5" + +[[NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" + +[[Ogg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f" +uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" +version = "1.3.5+0" + +[[OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.10+0" + +[[Opus_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720" +uuid = "91d4177d-7536-5919-b921-800302f37372" +version = "1.3.2+0" + +[[OrderedCollections]] +git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.4.1" + +[[PCRE_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488" +uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc" +version = "8.44.0+0" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "1.1.0" + +[[Pixman_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.40.1+0" + +[[Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" + +[[PlotThemes]] +deps = ["PlotUtils", "Requires", "Statistics"] +git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d" +uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" +version = "2.0.1" + +[[PlotUtils]] +deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"] +git-tree-sha1 = "501c20a63a34ac1d015d5304da0e645f42d91c9f" +uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" +version = "1.0.11" + +[[Plots]] +deps = ["Base64", "Contour", "Dates", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"] +git-tree-sha1 = "9f126950870ef24ce75cdd841f4b7cf34affc6d2" +uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +version = "1.18.0" + +[[Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.2.2" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Qt5Base_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"] +git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8" +uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1" +version = "5.15.3+0" + +[[REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[RecipesBase]] +git-tree-sha1 = "b3fb709f3c97bfc6e948be68beeecb55a0b340ae" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.1.1" + +[[RecipesPipeline]] +deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"] +git-tree-sha1 = "9b8e57e3cca8828a1bc759840bfe48d64db9abfb" +uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" +version = "0.3.3" + +[[Reexport]] +git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.1.0" + +[[Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.1.3" + +[[SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[Scratch]] +deps = ["Dates"] +git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.1.0" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[Showoff]] +deps = ["Dates", "Grisu"] +git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" +uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" +version = "1.0.3" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "2ec1962eba973f383239da22e75218565c390a96" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.0.0" + +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[StaticArrays]] +deps = ["LinearAlgebra", "Random", "Statistics"] +git-tree-sha1 = "896d55218776ab8f23fb7b222a5a4a946d4aafc2" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.2.5" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[StatsAPI]] +git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.0.0" + +[[StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "2f6792d523d7448bbe2fec99eca9218f06cc746d" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.33.8" + +[[StructArrays]] +deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"] +git-tree-sha1 = "000e168f5cc9aded17b6999a560b7c11dda69095" +uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" +version = "0.6.0" + +[[TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" + +[[TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"] +git-tree-sha1 = "8ed4a3ea724dac32670b062be3ef1c1de6773ae8" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.4.4" + +[[Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" + +[[Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[URIs]] +git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.3.0" + +[[UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[Wayland_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23" +uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" +version = "1.19.0+0" + +[[Wayland_protocols_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"] +git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670" +uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91" +version = "1.18.0+4" + +[[XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.9.12+0" + +[[XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.6.9+4" + +[[Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.9+4" + +[[Xorg_libXcursor_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] +git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd" +uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" +version = "1.2.0+4" + +[[Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.3+4" + +[[Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.4+4" + +[[Xorg_libXfixes_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4" +uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" +version = "5.0.3+4" + +[[Xorg_libXi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] +git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246" +uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" +version = "1.7.10+4" + +[[Xorg_libXinerama_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"] +git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123" +uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" +version = "1.1.4+4" + +[[Xorg_libXrandr_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"] +git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631" +uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" +version = "1.5.2+4" + +[[Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.10+4" + +[[Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.0+3" + +[[Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.13.0+3" + +[[Xorg_libxkbfile_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] +git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2" +uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" +version = "1.1.0+4" + +[[Xorg_xcb_util_image_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"] +git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97" +uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" +version = "0.4.0+1" + +[[Xorg_xcb_util_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"] +git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1" +uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" +version = "0.4.0+1" + +[[Xorg_xcb_util_keysyms_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"] +git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00" +uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" +version = "0.4.0+1" + +[[Xorg_xcb_util_renderutil_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"] +git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e" +uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" +version = "0.3.9+1" + +[[Xorg_xcb_util_wm_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"] +git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67" +uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" +version = "0.4.1+1" + +[[Xorg_xkbcomp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"] +git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b" +uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" +version = "1.4.2+4" + +[[Xorg_xkeyboard_config_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"] +git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d" +uuid = "33bec58e-1273-512f-9401-5d533626f822" +version = "2.27.0+4" + +[[Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.4.0+3" + +[[Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" + +[[Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.0+0" + +[[libass_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "acc685bcf777b2202a904cdcb49ad34c2fa1880c" +uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" +version = "0.14.0+4" + +[[libfdk_aac_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "7a5780a0d9c6864184b3a2eeeb833a0c871f00ab" +uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" +version = "0.1.6+4" + +[[libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.38+0" + +[[libvorbis_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"] +git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb" +uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" +version = "1.3.7+0" + +[[nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" + +[[p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" + +[[x264_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "d713c1ce4deac133e3334ee12f4adff07f81778f" +uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" +version = "2020.7.14+2" + +[[x265_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab" +uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" +version = "3.0.0+3" + +[[xkbcommon_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] +git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6" +uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" +version = "0.9.1+5" +""" + # ╔═╡ Cell order: # ╟─7b93882c-9ad8-11ea-0288-0941e163f9d5 # ╟─d889e13e-f0ff-11ea-3306-57491904d83f diff --git a/sample/PlutoUI.jl.jl b/sample/PlutoUI.jl.jl index ba4ff8f57a..02615151d5 100644 --- a/sample/PlutoUI.jl.jl +++ b/sample/PlutoUI.jl.jl @@ -478,8 +478,25 @@ space # ╔═╡ d163f434-cc5a-11ea-19e9-9319ba994efa space -# ╔═╡ 7242e236-cc4c-11ea-06d9-c1825cfe1fea -md"#### Footnote about package environments +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" + +[compat] +PlutoUI = "~0.7.9" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" `PlutoUI` needs to be installed for this sample notebook, but we don't want to add it to your global package environment. (Although you should install PlutoUI if you find it useful! It's a tiny package.) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index c898fc6b9a..cdbdb6fab3 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -125,7 +125,10 @@ function cell_precedence_heuristic(topology::NotebookTopology, cell::Cell)::Real 1 elseif Symbol("Pkg.API.activate") ∈ top.references || Symbol("Pkg.activate") ∈ top.references || - Symbol("@pkg_str") ∈ top.references + Symbol("@pkg_str") ∈ top.references || + # https://juliadynamics.github.io/DrWatson.jl/dev/project/#DrWatson.quickactivate + Symbol("quickactivate") ∈ top.references || + Symbol("@quickactivate") ∈ top.references 2 elseif Symbol("Pkg.API.add") ∈ top.references || Symbol("Pkg.add") ∈ top.references || diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index d863876c08..6da45053ce 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -112,6 +112,36 @@ function save_notebook(io, notebook::Notebook) print(io, _cell_suffix) end + + using_plutopkg = notebook.nbpkg_ctx !== nothing + + write_package = if using_plutopkg + ptoml_path = joinpath(PkgCompat.env_dir(notebook.nbpkg_ctx), "Project.toml") + mtoml_path = joinpath(PkgCompat.env_dir(notebook.nbpkg_ctx), "Manifest.toml") + + ptoml_contents = isfile(ptoml_path) ? read(ptoml_path, String) : "" + mtoml_contents = isfile(mtoml_path) ? read(mtoml_path, String) : "" + + !isempty(strip(ptoml_contents)) + else + false + end + + if write_package + println(io, _cell_id_delimiter, string(_ptoml_cell_id)) + print(io, "PLUTO_PROJECT_TOML_CONTENTS = \"\"\"\n") + write(io, ptoml_contents) + print(io, "\"\"\"") + print(io, _cell_suffix) + + println(io, _cell_id_delimiter, string(_mtoml_cell_id)) + print(io, "PLUTO_MANIFEST_TOML_CONTENTS = \"\"\"\n") + write(io, mtoml_contents) + print(io, "\"\"\"") + print(io, _cell_suffix) + end + + println(io, _cell_id_delimiter, "Cell order:") for c in notebook.cells delim = c.code_folded ? _order_delimiter_folded : _order_delimiter diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl new file mode 100644 index 0000000000..01d499a611 --- /dev/null +++ b/src/packages/Packages.jl @@ -0,0 +1,499 @@ + +import .ExpressionExplorer: external_package_names +import .PkgCompat +import .PkgCompat: select, is_stdlib + +const tiers = [ + Pkg.PRESERVE_ALL, + Pkg.PRESERVE_DIRECT, + Pkg.PRESERVE_SEMVER, + Pkg.PRESERVE_NONE, +] + +const pkg_token = Token() + +# This list appears multiple times in our codebase. Be sure to match edits everywhere. +function use_plutopkg(topology::NotebookTopology) + !any(values(topology.nodes)) do node + Symbol("Pkg.activate") ∈ node.references || + Symbol("Pkg.API.activate") ∈ node.references || + Symbol("Pkg.develop") ∈ node.references || + Symbol("Pkg.API.develop") ∈ node.references || + Symbol("Pkg.add") ∈ node.references || + Symbol("Pkg.API.add") ∈ node.references || + # https://juliadynamics.github.io/DrWatson.jl/dev/project/#DrWatson.quickactivate + Symbol("quickactivate") ∈ node.references || + Symbol("@quickactivate") ∈ node.references + end +end + +function external_package_names(topology::NotebookTopology)::Set{Symbol} + union!(Set{Symbol}(), external_package_names.(c.module_usings_imports for c in values(topology.codes))...) +end + + +PkgCompat.project_file(notebook::Notebook) = PkgCompat.project_file(PkgCompat.env_dir(notebook.nbpkg_ctx)) +PkgCompat.manifest_file(notebook::Notebook) = PkgCompat.manifest_file(PkgCompat.env_dir(notebook.nbpkg_ctx)) + + +""" +```julia +sync_nbpkg_core(notebook::Notebook; on_terminal_output::Function=((args...) -> nothing)) +``` + +Update the notebook package environment to match the notebook's code. This will: +- Add packages that should be added (because they are imported in a cell). +- Remove packages that are no longer needed. +- Make sure that the environment is instantiated. +- Detect the use of `Pkg.activate` and enable/disabled nbpkg accordingly. +""" +function sync_nbpkg_core(notebook::Notebook; on_terminal_output::Function=((args...) -> nothing)) + + 👺 = false + + use_plutopkg_old = notebook.nbpkg_ctx !== nothing + use_plutopkg_new = use_plutopkg(notebook.topology) + + if !use_plutopkg_old && use_plutopkg_new + @info "Started using PlutoPkg!! HELLO reproducibility!" + + 👺 = true + notebook.nbpkg_ctx = PkgCompat.create_empty_ctx() + end + if use_plutopkg_old && !use_plutopkg_new + @info "Stopped using PlutoPkg 💔😟😢" + + no_packages_loaded_yet = ( + notebook.nbpkg_restart_required_msg === nothing && + notebook.nbpkg_restart_recommended_msg === nothing && + all(PkgCompat.is_stdlib, keys(PkgCompat.project(notebook.nbpkg_ctx).dependencies)) + ) + 👺 = !no_packages_loaded_yet + notebook.nbpkg_ctx = nothing + end + + + if notebook.nbpkg_ctx !== nothing + PkgCompat.mark_original!(notebook.nbpkg_ctx) + + old_packages = String.(keys(PkgCompat.project(notebook.nbpkg_ctx).dependencies)) + new_packages = String.(external_package_names(notebook.topology)) # search all cells for imports and usings + + removed = setdiff(old_packages, new_packages) + added = setdiff(new_packages, old_packages) + + iolistener = let + busy_packages = notebook.nbpkg_ctx_instantiated ? added : new_packages + IOListener(callback=(s -> on_terminal_output(busy_packages, s))) + end + + # 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 + + if !can_skip + return withtoken(pkg_token) do + PkgCompat.refresh_registry_cache() + + if !notebook.nbpkg_ctx_instantiated + notebook.nbpkg_ctx = PkgCompat.clear_stdlib_compat_entries(notebook.nbpkg_ctx) + PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do + withinteractive(false) do + try + Pkg.resolve(notebook.nbpkg_ctx) + catch e + @warn "Failed to resolve Pkg environment. Removing Manifest and trying again..." exception=e + reset_nbpkg(notebook; keep_project=true, save=false, backup=false) + Pkg.resolve(notebook.nbpkg_ctx) + end + end + end + end + + to_remove = filter(removed) do p + haskey(PkgCompat.project(notebook.nbpkg_ctx).dependencies, p) + end + if !isempty(to_remove) + @debug to_remove + # See later comment + mkeys() = Set(filter(!is_stdlib, [m.name for m in values(PkgCompat.dependencies(notebook.nbpkg_ctx))])) + old_manifest_keys = mkeys() + + Pkg.rm(notebook.nbpkg_ctx, [ + Pkg.PackageSpec(name=p) + for p in to_remove + ]) + + # We record the manifest before and after, to prevent recommending a reboot when nothing got removed from the manifest (e.g. when removing GR, but leaving Plots), or when only stdlibs got removed. + new_manifest_keys = mkeys() + + # TODO: we might want to upgrade other packages now that constraints have loosened? Does this happen automatically? + end + + + # TODO: instead of Pkg.PRESERVE_ALL, we actually want: + # "Pkg.PRESERVE_DIRECT, but preserve exact verisons of Base.loaded_modules" + + to_add = filter(PkgCompat.package_exists, added) + + if !isempty(to_add) + @debug to_add + startlistening(iolistener) + + PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do + withinteractive(false) do + # 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. + notebook.nbpkg_ctx = PkgCompat.clear_auto_compat_entries(notebook.nbpkg_ctx) + + try + for tier in [ + Pkg.PRESERVE_ALL, + Pkg.PRESERVE_DIRECT, + Pkg.PRESERVE_SEMVER, + Pkg.PRESERVE_NONE, + ] + used_tier = tier + + try + Pkg.add(notebook.nbpkg_ctx, [ + Pkg.PackageSpec(name=p) + for p in to_add + ]; preserve=used_tier) + + break + catch e + if used_tier == Pkg.PRESERVE_NONE + # give up + rethrow(e) + end + end + end + finally + notebook.nbpkg_ctx = PkgCompat.write_auto_compat_entries(notebook.nbpkg_ctx) + end + + # Now that Pkg is set up, the notebook process will call `using Package`, which can take some time. We write this message to the io, to notify the user. + println(iolistener.buffer, "\e[32m\e[1mLoading\e[22m\e[39m packages...") + end + end + + @debug "PlutoPkg done" + end + + should_instantiate = !notebook.nbpkg_ctx_instantiated || !isempty(to_add) || !isempty(to_remove) + if should_instantiate + startlistening(iolistener) + PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do + @debug "Instantiating" + + # Pkg.instantiate assumes that the environment to be instantiated is active, so we will have to modify the LOAD_PATH of this Pluto server + # We could also run the Pkg calls on the notebook process, but somehow I think that doing it on the server is more charming, though it requires this workaround. + env_dir = PkgCompat.env_dir(notebook.nbpkg_ctx) + pushfirst!(LOAD_PATH, env_dir) + + # update registries if this is the first time + PkgCompat.update_registries(notebook.nbpkg_ctx) + # instantiate without forcing registry update + PkgCompat.instantiate(notebook.nbpkg_ctx; update_registry=false) + + @assert LOAD_PATH[1] == env_dir + popfirst!(LOAD_PATH) + end + notebook.nbpkg_ctx_instantiated = true + end + + stoplistening(iolistener) + + return ( + did_something=👺 || ( + should_instantiate || (use_plutopkg_old != use_plutopkg_new) + ), + used_tier=used_tier, + # changed_versions=Dict{String,Pair}(), + restart_recommended=👺 || ( + (!isempty(to_remove) && old_manifest_keys != new_manifest_keys) || + used_tier != Pkg.PRESERVE_ALL + ), + restart_required=👺 || ( + used_tier ∈ [Pkg.PRESERVE_SEMVER, Pkg.PRESERVE_NONE] + ), + ) + end + end + end + return ( + did_something=👺 || (use_plutopkg_old != use_plutopkg_new), + used_tier=Pkg.PRESERVE_ALL, + # changed_versions=Dict{String,Pair}(), + restart_recommended=👺 || false, + restart_required=👺 || false, + ) +end + +""" +```julia +sync_nbpkg(session::ServerSession, notebook::Notebook; save::Bool=true) +``` + +In addition to the steps performed by [`sync_nbpkg_core`](@ref): +- Capture terminal outputs and store them in the `notebook` +- Update the clients connected to `notebook` +- `try` `catch` and reset the package environment on failure. +""" +function sync_nbpkg(session, notebook; save::Bool=true) + try + pkg_result = withtoken(notebook.executetoken) do + function iocallback(pkgs, s) + notebook.nbpkg_busy_packages = pkgs + for p in pkgs + notebook.nbpkg_terminal_outputs[p] = s + end + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + end + sync_nbpkg_core(notebook; on_terminal_output=iocallback) + end + + if pkg_result.did_something + @debug "PlutoPkg: success!" pkg_result + + if pkg_result.restart_recommended + @debug "PlutoPkg: Notebook restart recommended" + notebook.nbpkg_restart_recommended_msg = "yes" + end + if pkg_result.restart_required + @debug "PlutoPkg: Notebook restart REQUIRED" + notebook.nbpkg_restart_required_msg = "yes" + end + + notebook.nbpkg_busy_packages = String[] + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + save && save_notebook(notebook) + end + catch e + bt = catch_backtrace() + old_packages = try String.(keys(PkgCompat.project(notebook.nbpkg_ctx).dependencies)); catch; ["unknown"] end + new_packages = try String.(external_package_names(notebook.topology)); catch; ["unknown"] end + @warn """ + PlutoPkg: Failed to add/remove packages! Resetting package environment... + """ PLUTO_VERSION VERSION old_packages new_packages exception=(e, bt) + # TODO: send to user + + error_text = sprint(showerror, e, bt) + for p in notebook.nbpkg_busy_packages + old = get(notebook.nbpkg_terminal_outputs, p, "") + notebook.nbpkg_terminal_outputs[p] = old * "\n\n\nPkg error!\n\n" * error_text + end + notebook.nbpkg_busy_packages = String[] + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + + # Clear the embedded Project and Manifest and require a restart from the user. + reset_nbpkg(notebook; keep_project=false, save=save) + notebook.nbpkg_restart_required_msg = "yes" + notebook.nbpkg_ctx_instantiated = false + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + + save && save_notebook(notebook) + end +end + +function writebackup(notebook::Notebook) + backup_path = backup_filename(notebook.path) + Pluto.readwrite(notebook.path, backup_path) + + @info "Backup saved to" backup_path + + backup_path +end + +function reset_nbpkg(notebook::Notebook; keep_project::Bool=false, backup::Bool=true, save::Bool=true) + backup && save && writebackup(notebook) + + if notebook.nbpkg_ctx !== nothing + p = PkgCompat.project_file(notebook) + m = PkgCompat.manifest_file(notebook) + keep_project || (isfile(p) && rm(p)) + isfile(m) && rm(m) + + notebook.nbpkg_ctx = PkgCompat.load_ctx(PkgCompat.env_dir(notebook.nbpkg_ctx)) + else + notebook.nbpkg_ctx = use_plutopkg(notebook.topology) ? PkgCompat.create_empty_ctx() : nothing + end + + save && save_notebook(notebook) +end + +function update_nbpkg_core(notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.UPLEVEL_MAJOR, on_terminal_output::Function=((args...) -> nothing)) + if notebook.nbpkg_ctx !== nothing + PkgCompat.mark_original!(notebook.nbpkg_ctx) + + old_packages = String.(keys(PkgCompat.project(notebook.nbpkg_ctx).dependencies)) + + iolistener = let + # we don't know which packages will be updated, so we send terminal output to all installed packages + IOListener(callback=(s -> on_terminal_output(old_packages, s))) + end + + # 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 + + return withtoken(pkg_token) do + PkgCompat.refresh_registry_cache() + + if !notebook.nbpkg_ctx_instantiated + notebook.nbpkg_ctx = PkgCompat.clear_stdlib_compat_entries(notebook.nbpkg_ctx) + PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do + withinteractive(false) do + try + Pkg.resolve(notebook.nbpkg_ctx) + catch e + @warn "Failed to resolve Pkg environment. Removing Manifest and trying again..." exception=e + reset_nbpkg(notebook; keep_project=true, save=false, backup=false) + Pkg.resolve(notebook.nbpkg_ctx) + end + end + end + end + + startlistening(iolistener) + + PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do + # We temporarily clear the "semver-compatible" [deps] entries, because it is difficult to update them after the update 🙈. TODO + notebook.nbpkg_ctx = PkgCompat.clear_auto_compat_entries(notebook.nbpkg_ctx) + + try + ### + Pkg.update(notebook.nbpkg_ctx; level=level) + ### + finally + notebook.nbpkg_ctx = PkgCompat.write_auto_compat_entries(notebook.nbpkg_ctx) + end + end + + stoplistening(iolistener) + + 🐧 = !PkgCompat.is_original(notebook.nbpkg_ctx) + ( + did_something=🐧, + restart_recommended=🐧, + restart_required=🐧, + ) + end + end + ( + did_something=false, + restart_recommended=false, + restart_required=false, + ) +end + + +function update_nbpkg(session, notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.UPLEVEL_MAJOR, backup::Bool=true, save::Bool=true) + if backup && save + bp = writebackup(notebook) + end + + try + pkg_result = withtoken(notebook.executetoken) do + original_outputs = deepcopy(notebook.nbpkg_terminal_outputs) + function iocallback(pkgs, s) + notebook.nbpkg_busy_packages = pkgs + for p in pkgs + original = get(original_outputs, p, "") + notebook.nbpkg_terminal_outputs[p] = original * "\n\n" * s + end + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + end + update_nbpkg_core(notebook; level=level, on_terminal_output=iocallback) + end + + if pkg_result.did_something + if pkg_result.restart_recommended + @debug "PlutoPkg: Notebook restart recommended" + notebook.nbpkg_restart_recommended_msg = "yes" + end + if pkg_result.restart_required + @debug "PlutoPkg: Notebook restart REQUIRED" + notebook.nbpkg_restart_required_msg = "yes" + end + else + isfile(bp) && rm(bp) + end + finally + notebook.nbpkg_busy_packages = String[] + update_nbpkg_cache!(notebook) + send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) + save && save_notebook(notebook) + end +end + +nbpkg_cache(ctx::Union{Nothing,PkgContext}) = ctx === nothing ? Dict{String,String}() : Dict{String,String}( + x => string(PkgCompat.get_manifest_version(ctx, x)) for x in keys(PkgCompat.project(ctx).dependencies) +) + +function update_nbpkg_cache!(notebook::Notebook) + notebook.nbpkg_installed_versions_cache = nbpkg_cache(notebook.nbpkg_ctx) + notebook +end + +const is_interactive_defined = isdefined(Base, :is_interactive) && !Base.isconst(Base, :is_interactive) +function withinteractive(f::Function, value::Bool) + old_value = isinteractive() + @static if is_interactive_defined + Core.eval(Base, :(is_interactive = $value)) + end + try + f() + finally + @static if is_interactive_defined + Core.eval(Base, :(is_interactive = $old_value)) + end + end +end + +"A polling system to watch for writes to an IOBuffer. Up-to-date content will be passed as string to the `callback` function." +Base.@kwdef struct IOListener + callback::Function + buffer::IOBuffer=IOBuffer() + interval::Real=1.0/60 + running::Ref{Bool}=Ref(false) + last_size::Ref{Int}=Ref(-1) +end +function trigger(listener::IOListener) + new_size = listener.buffer.size + if new_size > listener.last_size[] + listener.last_size[] = new_size + new_contents = String(listener.buffer.data[1:new_size]) + listener.callback(new_contents) + end +end +function startlistening(listener::IOListener) + if !listener.running[] + listener.running[] = true + @async while listener.running[] + trigger(listener) + sleep(listener.interval) + end + end +end +function stoplistening(listener::IOListener) + if listener.running[] + listener.running[] = false + trigger(listener) + end +end diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl new file mode 100644 index 0000000000..39e700cc8f --- /dev/null +++ b/src/packages/PkgCompat.jl @@ -0,0 +1,456 @@ +module PkgCompat + +export package_versions, package_completions + +import Pkg +import Pkg.Types: VersionRange + +import ..Pluto + +# Should be in Base +flatmap(args...) = vcat(map(args...)...) + +# Should be in Base +function select(f::Function, xs) + for x ∈ xs + if f(x) + return x + end + end + nothing +end + + +#= + +NOTE ABOUT PUBLIC/INTERNAL PKG API + +Pkg.jl exposes lots of API, but only some of it is "public": guaranteed to remain available. API is public if it is listed here: +https://pkgdocs.julialang.org/v1/api/ + +In this file, I labeled functions by their status using 🐸, ⚠️, etc. + +A status in brackets (like this) means that it is only called within this file, and the fallback might be in a caller function. + +--- + +I tried to only use public API, except: +- I use the `Pkg.Types.Context` value as first argument for many functions, since the server process manages multiple notebook processes, each with their own package environment. We could get rid of this, by settings `Base.ACTIVE_PROJECT[]` before and after each Pkg call. (This is temporarily activating the notebook environment.) This does have a performance impact, since the project and manifest caches are regenerated every time. +- https://github.com/JuliaLang/Pkg.jl/issues/2607 seems to be impossible with the current public API. +- Some functions try to use internal API for optimization/better features. + +=# + + + + + +### +# CONTEXT +### + + +const PkgContext = if isdefined(Pkg, :Context) + Pkg.Context +elseif isdefined(Pkg, :Types) && isdefined(Pkg.Types, :Context) + Pkg.Types.Context +elseif isdefined(Pkg, :API) && isdefined(Pkg.API, :Context) + Pkg.API.Context +else + Pkg.Types.Context +end + +# 🐸 "Public API", but using PkgContext +load_ctx(env_dir)::PkgContext = PkgContext(env=Pkg.Types.EnvCache(joinpath(env_dir, "Project.toml"))) + +# 🐸 "Public API", but using PkgContext +create_empty_ctx()::PkgContext = load_ctx(mktempdir()) + +# ⚠️ Internal API with fallback +function load_ctx(original::PkgContext) + new = load_ctx(env_dir(original)) + + try + new.env.original_project = original.env.original_project + new.env.original_manifest = original.env.original_manifest + catch e + @warn "Pkg compat: failed to set original_project" exception=(e,catch_backtrace()) + end + + new +end + +# ⚠️ Internal API with fallback +function mark_original!(ctx::PkgContext) + try + ctx.env.original_project = deepcopy(ctx.env.project) + ctx.env.original_manifest = deepcopy(ctx.env.manifest) + catch e + @warn "Pkg compat: failed to set original_project" exception=(e,catch_backtrace()) + end +end + +# ⚠️ Internal API with fallback +function is_original(ctx::PkgContext)::Bool + try + ctx.env.original_project == ctx.env.project && + ctx.env.original_manifest == ctx.env.manifest + catch e + @warn "Pkg compat: failed to get original_project" exception=(e,catch_backtrace()) + false + end +end + + + +# 🐸 "Public API", but using PkgContext +env_dir(ctx::PkgContext) = dirname(ctx.env.project_file) + +project_file(x::AbstractString) = joinpath(x, "Project.toml") +manifest_file(x::AbstractString) = joinpath(x, "Manifest.toml") +project_file(ctx::PkgContext) = joinpath(env_dir(ctx), "Project.toml") +manifest_file(ctx::PkgContext) = joinpath(env_dir(ctx), "Manifest.toml") + + +# ⚠️ Internal API with fallback +function withio(f::Function, ctx::PkgContext, io::IO) + @static if :io ∈ fieldnames(PkgContext) + old_io = ctx.io + ctx.io = io + result = try + f() + finally + ctx.io = old_io + nothing + end + result + else + f() + end +end + + +### +# REGISTRIES +### + +# (⛔️ Internal API) +"Return paths to all installed registries." +_get_registry_paths() = @static if isdefined(Pkg, :Types) && isdefined(Pkg.Types, :registries) + Pkg.Types.registries() +elseif isdefined(Pkg, :Registry) && isdefined(Pkg.Registry, :reachable_registries) + registry_specs = Pkg.Registry.reachable_registries() + [s.path for s in registry_specs] +elseif isdefined(Pkg, :Types) && isdefined(Pkg.Types, :collect_registries) + registry_specs = Pkg.Types.collect_registries() + [s.path for s in registry_specs] +else + String[] +end + +# (⛔️ Internal API) +_get_registries() = map(_get_registry_paths()) do r + @static if isdefined(Pkg, :Registry) && isdefined(Pkg.Registry, :RegistryInstance) + Pkg.Registry.RegistryInstance(r) + else + r => Pkg.Types.read_registry(joinpath(r, "Registry.toml")) + end +end + +# (⛔️ Internal API) +"Contains all registries as `Pkg.Types.Registry` structs." +const _parsed_registries = Ref(_get_registries()) + +# (⛔️ Internal API) +"Re-parse the installed registries from disk." +function refresh_registry_cache() + _parsed_registries[] = _get_registries() +end + +const _updated_registries_compat = Ref(false) + +# ⚠️✅ Internal API with good fallback +function update_registries(ctx) + @static if isdefined(Pkg, :Types) && isdefined(Pkg.Types, :update_registries) + Pkg.Types.update_registries(ctx) + else + if !_updated_registries_compat[] + _updated_registries_compat[] = true + Pkg.Registry.update() + end + end +end + +# ⚠️✅ Internal API with fallback +function instantiate(ctx; update_registry::Bool) + @static if hasmethod(Pkg.instantiate, Tuple{}, (:update_registry,)) + Pkg.instantiate(ctx; update_registry=update_registry) + else + Pkg.instantiate(ctx) + end +end + + + +# (⚠️ Internal API with fallback) +_stdlibs() = try + values(Pkg.Types.stdlibs()) +catch e + @warn "Pkg compat: failed to load standard libraries." exception=(e,catch_backtrace()) + + String["CRC32c", "Future", "Sockets", "MbedTLS_jll", "Random", "ArgTools", "libLLVM_jll", "GMP_jll", "Pkg", "Serialization", "LibSSH2_jll", "SHA", "OpenBLAS_jll", "REPL", "LibUV_jll", "nghttp2_jll", "Unicode", "Profile", "SparseArrays", "LazyArtifacts", "CompilerSupportLibraries_jll", "Base64", "Artifacts", "PCRE2_jll", "Printf", "p7zip_jll", "UUIDs", "Markdown", "TOML", "OpenLibm_jll", "Test", "MPFR_jll", "Mmap", "SuiteSparse", "LibGit2", "LinearAlgebra", "Logging", "NetworkOptions", "LibGit2_jll", "LibOSXUnwind_jll", "Dates", "LibUnwind_jll", "Libdl", "LibCURL_jll", "dSFMT_jll", "Distributed", "InteractiveUtils", "Downloads", "SharedArrays", "SuiteSparse_jll", "LibCURL", "Statistics", "Zlib_jll", "FileWatching", "DelimitedFiles", "Tar", "MozillaCACerts_jll"] +end + +# ⚠️ Internal API with fallback +is_stdlib(package_name::AbstractString) = package_name ∈ _stdlibs() + +global_ctx = PkgContext() + +### +# Package names +### + +# ⚠️ Internal API with fallback +function package_completions(partial_name::AbstractString)::Vector{String} + String[ + filter(s -> startswith(s, partial_name), collect(_stdlibs())); + _registered_package_completions(partial_name) + ] +end + +# (⚠️ Internal API with fallback) +function _registered_package_completions(partial_name::AbstractString)::Vector{String} + # compat + try + @static if hasmethod(Pkg.REPLMode.complete_remote_package, (String,)) + Pkg.REPLMode.complete_remote_package(partial_name) + else + Pkg.REPLMode.complete_remote_package(partial_name, 1, length(partial_name))[1] + end + catch e + @warn "Pkg compat: failed to autocomplete packages" exception=(e,catch_backtrace()) + String[] + end +end + +### +# Package versions +### + +# (⛔️ Internal API) +""" +Return paths to all found registry entries of a given package name. + +# Example +```julia +julia> Pluto.PkgCompat._registry_entries("Pluto") +1-element Vector{String}: + "/Users/fons/.julia/registries/General/P/Pluto" +``` +""" +function _registry_entries(package_name::AbstractString, registries::Vector=_parsed_registries[])::Vector{String} + flatmap(registries) do (rpath, r) + packages = values(r["packages"]) + String[ + joinpath(rpath, d["path"]) + for d in packages + if d["name"] == package_name + ] + end +end + +# (⛔️ Internal API) +function _package_versions_from_path(registry_entry_fullpath::AbstractString)::Vector{VersionNumber} + # compat + vd = @static if isdefined(Pkg, :Operations) && isdefined(Pkg.Operations, :load_versions) && hasmethod(Pkg.Operations.load_versions, (String,)) + Pkg.Operations.load_versions(registry_entry_fullpath) + else + Pkg.Operations.load_versions(PkgContext(), registry_entry_fullpath) + end + vd |> keys |> collect +end + +# ⚠️ Internal API with fallback +# See https://github.com/JuliaLang/Pkg.jl/issues/2607 +""" +Return all registered versions of the given package. Returns `["stdlib"]` for standard libraries, and a `Vector{VersionNumber}` for registered packages. +""" +function package_versions(package_name::AbstractString)::Vector + if is_stdlib(package_name) + ["stdlib"] + else + try + @static(if isdefined(Pkg, :Registry) && isdefined(Pkg.Registry, :uuids_from_name) + flatmap(_parsed_registries[]) do reg + uuids_with_name = Pkg.Registry.uuids_from_name(reg, package_name) + flatmap(uuids_with_name) do u + pkg = get(reg, u, nothing) + if pkg !== nothing + info = Pkg.Registry.registry_info(pkg) + collect(keys(info.version_info)) + else + [] + end + end + end + else + ps = _registry_entries(package_name) + flatmap(_package_versions_from_path, ps) + end) |> sort + catch e + @warn "Pkg compat: failed to get installable versions." exception=(e,catch_backtrace()) + ["latest"] + end + end +end + +# ⚠️ Internal API with fallback +"Does a package with this name exist in one of the installed registries?" +package_exists(package_name::AbstractString)::Bool = + package_versions(package_name) |> !isempty + +# 🐸 "Public API", but using PkgContext +function dependencies(ctx) + # Pkg.dependencies(ctx) should also work on 1.5, but there is some weird bug (run the tests without this patch). This is probably some Pkg bug that got fixed. + @static if VERSION < v"1.6.0-a" + ctx.env.manifest + else + try + # ctx.env.manifest + @static if hasmethod(Pkg.dependencies, (PkgContext,)) + Pkg.dependencies(ctx) + else + Pkg.dependencies(ctx.env) + end + catch e + if !occursin(r"expected.*exist.*manifest", sprint(showerror, e)) + @error """ + Pkg error: you might need to use + + Pluto.reset_notebook_environment(notebook_path) + + to reset this notebook's environment. + + Before doing so, consider sending your notebook file to https://github.com/fonsp/Pluto.jl/issues together with the following info: + """ Pluto.PLUTO_VERSION VERSION exception=(e,catch_backtrace()) + end + + Dict() + end + end +end + +function project(ctx::PkgContext) + @static if hasmethod(Pkg.project, (PkgContext,)) + Pkg.project(ctx) + else + Pkg.project(ctx.env) + end +end + +# 🐸 "Public API", but using PkgContext +"Find a package in the manifest. Return `nothing` if not found." +_get_manifest_entry(ctx::PkgContext, package_name::AbstractString) = + select(e -> e.name == package_name, values(dependencies(ctx))) + +# ⚠️ Internal API with fallback +""" +Find a package in the manifest given its name, and return its installed version. Return `"stdlib"` for a standard library, and `nothing` if not found. +""" +function get_manifest_version(ctx::PkgContext, package_name::AbstractString) + if is_stdlib(package_name) + "stdlib" + else + entry = _get_manifest_entry(ctx, package_name) + entry === nothing ? nothing : entry.version + end +end + +### +# WRITING COMPAT ENTRIES +### + + +_project_key_order = ["name", "uuid", "keywords", "license", "desc", "deps", "compat"] +project_key_order(key::String) = + something(findfirst(x -> x == key, _project_key_order), length(_project_key_order) + 1) + +# ✅ Public API +function _modify_compat(f!::Function, ctx::PkgContext)::PkgContext + toml = Pkg.TOML.parsefile(project_file(ctx)) + compat = get!(Dict, toml, "compat") + + f!(compat) + + isempty(compat) && delete!(toml, "compat") + + write(project_file(ctx), sprint() do io + Pkg.TOML.print(io, toml; sorted=true, by=(key -> (project_key_order(key), key))) + end) + + return load_ctx(ctx) +end + + +# ✅ Public API +""" +Add any missing [`compat`](https://pkgdocs.julialang.org/v1/compatibility/) entries to the `Project.toml` for all direct dependencies. This serves as a 'fallback' in case someone (with a different Julia version) opens your notebook without being able to load the `Manifest.toml`. Return the new `PkgContext`. + +The automatic compat entry is: `"~" * string(installed_version)`. +""" +function write_auto_compat_entries(ctx::PkgContext)::PkgContext + _modify_compat(ctx) do compat + for p in keys(project(ctx).dependencies) + if !haskey(compat, p) + m_version = get_manifest_version(ctx, p) + if m_version !== nothing && !is_stdlib(p) + compat[p] = "~" * string(m_version) + end + end + end + end +end + + +# ✅ Public API +""" +Remove any automatically-generated [`compat`](https://pkgdocs.julialang.org/v1/compatibility/) entries from the `Project.toml`. This will undo the effects of [`write_auto_compat_entries`](@ref) but leave other (e.g. manual) compat entries intact. Return the new `PkgContext`. +""" +function clear_auto_compat_entries(ctx::PkgContext)::PkgContext + if isfile(project_file(ctx)) + _modify_compat(ctx) do compat + for p in keys(compat) + m_version = get_manifest_version(ctx, p) + if m_version !== nothing && !is_stdlib(p) + if compat[p] == "~" * string(m_version) + delete!(compat, p) + end + end + end + end + else + ctx + end +end + +# ✅ Public API +""" +Remove any [`compat`](https://pkgdocs.julialang.org/v1/compatibility/) entries from the `Project.toml` for standard libraries. These entries are created when an old version of Julia uses a package that later became a standard library, like https://github.com/JuliaPackaging/Artifacts.jl. Return the new `PkgContext`. +""" +function clear_stdlib_compat_entries(ctx::PkgContext)::PkgContext + if isfile(project_file(ctx)) + _modify_compat(ctx) do compat + for p in keys(compat) + if is_stdlib(p) + @info "Removing compat entry for stdlib" p + delete!(compat, p) + end + end + end + else + ctx + end +end + + +end \ No newline at end of file From 829ae397d17bf83a614cff7373e912b8ded4dd93 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 03:30:31 +0300 Subject: [PATCH 03/71] Almost Markdown --- frontend/components/CellInput.js | 10 ++++- frontend/imports/CodemirrorPlutoSetup.js | 52 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index bb2c9adddb..281ff4978e 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -79,7 +79,14 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.tagName, color: "#ef6155" }, { tag: tags.link, color: "#815ba4" }, { tag: tags.invalid, color: "#000", background: "#ef6155" }, - // ...Object.keys(tags).map((x) => ({ tag: x, color: x })), + // Object.keys(tags).map((x) => ({ tag: x, color: x })), + { tag: tags.heading, color: "#0e2bb9", fontWeight: 500 }, + { tag: tags.heading1, color: "#0e2bb9", fontWeight: 500, fontSize: "1.5em" }, + { tag: tags.heading2, color: "red", fontWeight: 400, fontSize: "1.4em" }, + { tag: tags.heading3, color: "red", fontWeight: 400, fontSize: "1.25em" }, + { tag: tags.heading4, color: "red", fontWeight: 400, fontSize: "1.1em" }, + { tag: tags.heading5, color: "red", fontWeight: 400, fontSize: "1em" }, + { tag: tags.heading6, color: "red", fontWeight: "bold", fontSize: "0.8em" }, ]) const getValue6 = (/** @type {EditorView} */ cm) => cm.state.doc.toString() @@ -339,7 +346,6 @@ export const CellInput = ({ nbpkg_compartment, used_variables_compartment, editable_compartment, - pkgBubblePlugin({ pluto_actions, notebook_id }), pluto_syntax_colors, lineNumbers(), diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 424422a9b2..28eeeba8f2 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -5,7 +5,7 @@ import { EditorView, placeholder, julia_legacy, - julia_andrey, + julia_andrey as julia_andrey_original, keymap, history, historyKeymap, @@ -44,7 +44,54 @@ import { indentUnit, combineConfig, NodeProp, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@05fdefd/dist/index.es.min.js" + parseMixed, + markdown, +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@1bd6504/dist/index.es.min.js" + +import { parser as htmlParser } from "https://unpkg.com/@lezer/html@0.15.0?module" + +console.log(htmlParser, markdown, markdown()) +const mdParser = markdown().language.parser +const wrapper = parseMixed((node, input) => { + console.log(node, input) + if (node.type.id !== 69 /* TemplateString */) { + return null + } + const tag = input.doc.text[0].replace(/\"/g, "") + console.log(tag) + let parser + + if (tag === "html") { + parser = htmlParser + } else if (tag === "md") { + parser = mdParser + console.log("going for markdown") + } else { + return null + } + + const overlay = [] //: { from: number, to: number }[] = []; + let from = node.from + console.log(node.node.firstChild) + for (let child = node.node.firstChild; child !== null; child = child?.nextSibling) { + overlay.push({ from, to: child.from }) + from = child.to + } + + if (overlay.length === 0) { + return { parser } + } + + overlay.push({ from, to: node.to }) + + console.log(overlay) + return { parser, overlay: overlay } +}) + +const id = julia_andrey_original() + +id.language.parser = id.language.parser.configure({ wrap: wrapper }) +const julia_andrey = () => id export { EditorState, @@ -92,4 +139,5 @@ export { indentUnit, combineConfig, NodeProp, + markdown, } From 9e9a542931573655f22c383b35b9990199b33756 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 03:49:51 +0300 Subject: [PATCH 04/71] how about HTML --- frontend/components/CellInput.js | 8 ++++++-- frontend/imports/CodemirrorPlutoSetup.js | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 281ff4978e..f2cd13391f 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -44,6 +44,7 @@ import { indentUnit, StateField, StateEffect, + html as htmlLang, } from "../imports/CodemirrorPlutoSetup.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" @@ -67,8 +68,7 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.comment, color: "#e96ba8", fontStyle: "italic" }, { tag: tags.atom, color: "#815ba4" }, { tag: tags.number, color: "#815ba4" }, - // { tag: tags.property, color: "#48b685" }, - // { tag: tags.attribute, color: "#48b685" }, + { tag: tags.bracket, color: "#48b685" }, { tag: tags.keyword, color: "#ef6155" }, { tag: tags.string, color: "#da5616" }, { tag: tags.variableName, color: "#5668a4", fontWeight: 700 }, @@ -87,6 +87,9 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.heading4, color: "red", fontWeight: 400, fontSize: "1.1em" }, { tag: tags.heading5, color: "red", fontWeight: 400, fontSize: "1em" }, { tag: tags.heading6, color: "red", fontWeight: "bold", fontSize: "0.8em" }, + { tag: tags.url, color: "#48b685", fontDecoration: "underline" }, + { tag: tags.quote, color: "cyan", fontStyle: "italic" }, + { tag: tags.literal, color: "grey", fontWeight: 700 }, ]) const getValue6 = (/** @type {EditorView} */ cm) => cm.state.doc.toString() @@ -393,6 +396,7 @@ export const CellInput = ({ EditorState.tabSize.of(4), indentUnit.of("\t"), julia_andrey(), + htmlLang(), go_to_definition_plugin, pluto_autocomplete({ request_autocomplete: async ({ text }) => { diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 28eeeba8f2..da85a27305 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -46,11 +46,12 @@ import { NodeProp, parseMixed, markdown, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@1bd6504/dist/index.es.min.js" + html, + htmlLanguage, +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@c31a14d/dist/index.es.min.js" -import { parser as htmlParser } from "https://unpkg.com/@lezer/html@0.15.0?module" - -console.log(htmlParser, markdown, markdown()) +const htmlParser = html().language.parser +console.log(html(), htmlParser, markdown, markdown()) const mdParser = markdown().language.parser const wrapper = parseMixed((node, input) => { console.log(node, input) @@ -140,4 +141,5 @@ export { combineConfig, NodeProp, markdown, + html, } From 5d0ca9cccb47dc26c0544005b30ebe3abfd1c784 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 04:07:46 +0300 Subject: [PATCH 05/71] Better HTML/MD --- frontend/components/CellInput.js | 16 +++++++++++++--- frontend/imports/CodemirrorPlutoSetup.js | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index f2cd13391f..5177ca0028 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -53,6 +53,7 @@ import { drag_n_drop_plugin } from "./useDropHandler.js" import { cell_movement_plugin } from "./CellInput/cell_movement_plugin.js" import { pluto_paste_plugin } from "./CellInput/pluto_paste_plugin.js" import { bracketMatching } from "./CellInput/block_matcher_plugin.js" +import { markdown } from "../imports/CodemirrorPlutoSetup.js" export const pluto_syntax_colors = HighlightStyle.define([ /* The following three need a specific version of the julia parser, will add that later (still messing with it 😈) */ @@ -80,6 +81,7 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.link, color: "#815ba4" }, { tag: tags.invalid, color: "#000", background: "#ef6155" }, // Object.keys(tags).map((x) => ({ tag: x, color: x })), + // Markdown { tag: tags.heading, color: "#0e2bb9", fontWeight: 500 }, { tag: tags.heading1, color: "#0e2bb9", fontWeight: 500, fontSize: "1.5em" }, { tag: tags.heading2, color: "red", fontWeight: 400, fontSize: "1.4em" }, @@ -87,9 +89,16 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.heading4, color: "red", fontWeight: 400, fontSize: "1.1em" }, { tag: tags.heading5, color: "red", fontWeight: 400, fontSize: "1em" }, { tag: tags.heading6, color: "red", fontWeight: "bold", fontSize: "0.8em" }, - { tag: tags.url, color: "#48b685", fontDecoration: "underline" }, - { tag: tags.quote, color: "cyan", fontStyle: "italic" }, + { tag: tags.url, color: "#48b685", textDecoration: "underline" }, + { tag: tags.quote, color: "#444", fontStyle: "italic" }, { tag: tags.literal, color: "grey", fontWeight: 700 }, + // HTML + { tag: tags.tagName, color: "darkblue", fontWeight: 700 }, + { tag: tags.attributeName, color: "darkblue", fontWeight: 400 }, + { tag: tags.attributeValue, color: "orange", fontWeight: 700 }, + { tag: tags.angleBracket, color: "black", fontWeight: 700 }, + { tag: tags.content, color: "darkgrey", fontWeight: 400 }, + { tag: tags.documentMeta, color: "grey", fontStyle: "italic" }, ]) const getValue6 = (/** @type {EditorView} */ cm) => cm.state.doc.toString() @@ -396,7 +405,8 @@ export const CellInput = ({ EditorState.tabSize.of(4), indentUnit.of("\t"), julia_andrey(), - htmlLang(), + markdown(), + htmlLang(), //Provides tag closing! go_to_definition_plugin, pluto_autocomplete({ request_autocomplete: async ({ text }) => { diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index da85a27305..35aadc337e 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -48,7 +48,7 @@ import { markdown, html, htmlLanguage, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@c31a14d/dist/index.es.min.js" +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@46408f1/dist/index.es.min.js" const htmlParser = html().language.parser console.log(html(), htmlParser, markdown, markdown()) From 3e90fa95124fd9ee0a4c6b513ca543133331153d Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 04:12:38 +0300 Subject: [PATCH 06/71] Add js (not working) --- frontend/components/CellInput.js | 4 +++- frontend/imports/CodemirrorPlutoSetup.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 5177ca0028..cc2de71d04 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -45,6 +45,7 @@ import { StateField, StateEffect, html as htmlLang, + javascript, } from "../imports/CodemirrorPlutoSetup.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" @@ -406,7 +407,8 @@ export const CellInput = ({ indentUnit.of("\t"), julia_andrey(), markdown(), - htmlLang(), //Provides tag closing! + htmlLang(), //Provides tag closing!, + javascript(), go_to_definition_plugin, pluto_autocomplete({ request_autocomplete: async ({ text }) => { diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 35aadc337e..85b55bcb4e 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -48,7 +48,9 @@ import { markdown, html, htmlLanguage, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@46408f1/dist/index.es.min.js" + javascript, + javascriptLanguage, +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@e27d21e/dist/index.es.min.js" const htmlParser = html().language.parser console.log(html(), htmlParser, markdown, markdown()) @@ -142,4 +144,6 @@ export { NodeProp, markdown, html, + javascript, + javascriptLanguage, } From 94a751adf5dd277d32f6f4ff66039aaf77a83508 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 22:06:57 +0300 Subject: [PATCH 07/71] get/set api --- frontend/components/CellInput.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index bb2c9adddb..ee815e4798 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -421,8 +421,11 @@ export const CellInput = ({ // For use from useDropHandler // @ts-ignore - newcm.dom.CodeMirror = { + dom_node_ref.current.CodeMirror = newcm.dom.CodeMirror = { getValue: () => newcm.state.doc.toString(), + setValue: (value) => newcm.dispatch({ + changes: {from: 0, to: newcm.state.doc.length, insert: value} + }) } if (focus_after_creation) { @@ -492,7 +495,7 @@ export const CellInput = ({ }, [cm_forced_focus]) return html` - + <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} /> ` From 319297e74f262d1e5f1f4cddcafc7310be73f005 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 22:56:45 +0300 Subject: [PATCH 08/71] Broken again --- frontend/components/CellInput.js | 12 +++++++++--- frontend/imports/CodemirrorPlutoSetup.js | 18 +++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index cc2de71d04..bc4f34e3f1 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -44,6 +44,7 @@ import { indentUnit, StateField, StateEffect, + markdown, html as htmlLang, javascript, } from "../imports/CodemirrorPlutoSetup.js" @@ -54,7 +55,6 @@ import { drag_n_drop_plugin } from "./useDropHandler.js" import { cell_movement_plugin } from "./CellInput/cell_movement_plugin.js" import { pluto_paste_plugin } from "./CellInput/pluto_paste_plugin.js" import { bracketMatching } from "./CellInput/block_matcher_plugin.js" -import { markdown } from "../imports/CodemirrorPlutoSetup.js" export const pluto_syntax_colors = HighlightStyle.define([ /* The following three need a specific version of the julia parser, will add that later (still messing with it 😈) */ @@ -100,6 +100,9 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.angleBracket, color: "black", fontWeight: 700 }, { tag: tags.content, color: "darkgrey", fontWeight: 400 }, { tag: tags.documentMeta, color: "grey", fontStyle: "italic" }, + // CSS + { tag: tags.className, color: "grey", fontWeight: "bold" }, + ]) const getValue6 = (/** @type {EditorView} */ cm) => cm.state.doc.toString() @@ -443,8 +446,11 @@ export const CellInput = ({ // For use from useDropHandler // @ts-ignore - newcm.dom.CodeMirror = { + dom_node_ref.current.CodeMirror = newcm.dom.CodeMirror = { getValue: () => newcm.state.doc.toString(), + setValue: (value) => newcm.dispatch({ + changes: { from: 0, to: newcm.state.doc.length, insert: value }, + }) } if (focus_after_creation) { @@ -514,7 +520,7 @@ export const CellInput = ({ }, [cm_forced_focus]) return html` - + <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} /> ` diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 85b55bcb4e..4cbb4cc7f2 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -48,23 +48,26 @@ import { markdown, html, htmlLanguage, + markdownLanguage, javascript, javascriptLanguage, } from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@e27d21e/dist/index.es.min.js" -const htmlParser = html().language.parser -console.log(html(), htmlParser, markdown, markdown()) -const mdParser = markdown().language.parser +const htmlParser = htmlLanguage.parser +const mdParser = markdownLanguage.parser const wrapper = parseMixed((node, input) => { - console.log(node, input) - if (node.type.id !== 69 /* TemplateString */) { + if (node.type.id !== 69 && node.type.id !== 68 /* TemplateString */) { return null } - const tag = input.doc.text[0].replace(/\"/g, "") + //Looking for tag OR MacroIdentifier + const tagNode = node.node.prevSibling || node.node.parent.prevSibling + console.log(input.read(tagNode.from, tagNode.to)) + const tag = input.read(tagNode.from, tagNode.to) + console.log({...node}, {...input}, node.type.name, node.type.id, node.from, node.to) console.log(tag) let parser - if (tag === "html") { + if (tag === "html" || tag === "@htl") { parser = htmlParser } else if (tag === "md") { parser = mdParser @@ -95,6 +98,7 @@ const id = julia_andrey_original() id.language.parser = id.language.parser.configure({ wrap: wrapper }) const julia_andrey = () => id +console.log(id) export { EditorState, From 9e8c379f86516ef5c8420f55616f4d86131e6cd7 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 23:14:47 +0300 Subject: [PATCH 09/71] Remove quotes from parsed strings --- frontend/imports/CodemirrorPlutoSetup.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 4cbb4cc7f2..95b49399ff 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -61,17 +61,19 @@ const wrapper = parseMixed((node, input) => { } //Looking for tag OR MacroIdentifier const tagNode = node.node.prevSibling || node.node.parent.prevSibling - console.log(input.read(tagNode.from, tagNode.to)) + if (!tagNode){ + // If you can't find a tag node, something is broken in the julia syntax, + // so parse it as Julia. + return null + } const tag = input.read(tagNode.from, tagNode.to) console.log({...node}, {...input}, node.type.name, node.type.id, node.from, node.to) - console.log(tag) let parser if (tag === "html" || tag === "@htl") { parser = htmlParser } else if (tag === "md") { parser = mdParser - console.log("going for markdown") } else { return null } @@ -80,6 +82,7 @@ const wrapper = parseMixed((node, input) => { let from = node.from console.log(node.node.firstChild) for (let child = node.node.firstChild; child !== null; child = child?.nextSibling) { + console.log("Child: ",{... child}) overlay.push({ from, to: child.from }) from = child.to } @@ -90,6 +93,17 @@ const wrapper = parseMixed((node, input) => { overlay.push({ from, to: node.to }) + // TODO: replace $() from overlays - add placeholder?? + // Remove quotes from strings + if(node.type.id === 69){ // Triple Quote String + overlay[0].from += 3 + overlay[overlay.length - 1].to -= 3 + } + if(node.type.id === 68){ // Single quote string + overlay[0].from += 1 + overlay[overlay.length - 1].to -= 1 + } + console.log(">>> ", overlay.map(({from, to}) => input.read(from, to)).join("")) console.log(overlay) return { parser, overlay: overlay } }) From ce3ef17fea4f3a75cf0d076060dc40fa4c8f6a95 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Sun, 3 Oct 2021 23:44:23 +0300 Subject: [PATCH 10/71] Small Cleanup --- frontend/components/CellInput.js | 2 +- frontend/imports/CodemirrorPlutoSetup.js | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index bc4f34e3f1..71c544d4e3 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -408,7 +408,7 @@ export const CellInput = ({ drag_n_drop_plugin(on_drag_drop_events), EditorState.tabSize.of(4), indentUnit.of("\t"), - julia_andrey(), + julia_andrey, markdown(), htmlLang(), //Provides tag closing!, javascript(), diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 95b49399ff..f15d162b20 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -51,11 +51,12 @@ import { markdownLanguage, javascript, javascriptLanguage, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@e27d21e/dist/index.es.min.js" +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@93bb0a8bda7ab2e0771b6e6a2a7225e37f30ea5a/dist/index.es.min.js" const htmlParser = htmlLanguage.parser const mdParser = markdownLanguage.parser -const wrapper = parseMixed((node, input) => { + +const juliaWrapper = parseMixed((node, input) => { if (node.type.id !== 69 && node.type.id !== 68 /* TemplateString */) { return null } @@ -63,7 +64,7 @@ const wrapper = parseMixed((node, input) => { const tagNode = node.node.prevSibling || node.node.parent.prevSibling if (!tagNode){ // If you can't find a tag node, something is broken in the julia syntax, - // so parse it as Julia. + // so parse it as Julia. Probably wrong interpolation! return null } const tag = input.read(tagNode.from, tagNode.to) @@ -82,7 +83,6 @@ const wrapper = parseMixed((node, input) => { let from = node.from console.log(node.node.firstChild) for (let child = node.node.firstChild; child !== null; child = child?.nextSibling) { - console.log("Child: ",{... child}) overlay.push({ from, to: child.from }) from = child.to } @@ -103,16 +103,13 @@ const wrapper = parseMixed((node, input) => { overlay[0].from += 1 overlay[overlay.length - 1].to -= 1 } - console.log(">>> ", overlay.map(({from, to}) => input.read(from, to)).join("")) - console.log(overlay) + // console.log(">>> ", overlay.map(({from, to}) => input.read(from, to)).join("")) return { parser, overlay: overlay } }) -const id = julia_andrey_original() +const julia_andrey = julia_andrey_original() -id.language.parser = id.language.parser.configure({ wrap: wrapper }) -const julia_andrey = () => id -console.log(id) +julia_andrey.language.parser = julia_andrey.language.parser.configure({ wrap: juliaWrapper }) export { EditorState, From 423ffc32f7ab034be4a2c93f0ca0de8953fdfeb8 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Mon, 4 Oct 2021 00:58:44 +0300 Subject: [PATCH 11/71] OOps --- frontend/components/CellOutput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index 6f104b28f3..0e7842db7e 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -397,7 +397,7 @@ export let highlight = (code_element, language) => { defaultHighlightStyle.fallback, EditorState.tabSize.of(4), // TODO Other languages possibly? - language === "julia" ? julia_andrey() : null, + language === "julia" ? julia_andrey : null, EditorView.lineWrapping, EditorView.editable.of(false), ].filter((x) => x != null), From 86e7a3f3549562756270afa00b435bbca918e5eb Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Mon, 4 Oct 2021 10:35:02 +0200 Subject: [PATCH 12/71] re-implement notebook exclusive by file comments in CM6 --- frontend/components/Cell.js | 4 +++- frontend/components/CellInput.js | 13 +++++++++++-- frontend/components/Editor.js | 16 ++++++++++++++++ frontend/components/Notebook.js | 3 ++- frontend/editor.css | 27 +++++++++++++++++++++++++++ src/notebook/Cell.jl | 3 +++ src/notebook/Notebook.jl | 16 ++++++++++++++++ src/webserver/Dynamic.jl | 1 + 8 files changed, 79 insertions(+), 4 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 962fd0782d..35f41290f3 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -21,7 +21,7 @@ import { PlutoContext } from "../common/PlutoContext.js" * }} props * */ export const Cell = ({ - cell_input: { cell_id, code, code_folded, running_disabled }, + cell_input: { cell_id, code, code_folded, running_disabled, notebook_exclusive }, cell_result: { queued, running, runtime, errored, output, published_objects, depends_on_disabled_cells }, cell_dependencies, cell_input_local, @@ -115,6 +115,7 @@ export const Cell = ({ code_folded: class_code_folded, running_disabled: running_disabled, depends_on_disabled_cells: depends_on_disabled_cells, + notebook_exclusive: notebook_exclusive, show_input: show_input, drop_target: drag_active, saving_file: saving_file, @@ -187,6 +188,7 @@ export const Cell = ({ cell_id=${cell_id} notebook_id=${notebook_id} running_disabled=${running_disabled} + notebook_exclusive=${notebook_exclusive} /> <${RunArea} cell_id=${cell_id} diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index ee815e4798..88d9130efb 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -144,6 +144,7 @@ export const CellInput = ({ notebook_id, running_disabled, cell_dependencies, + notebook_exclusive, }) => { let pluto_actions = useContext(PlutoContext) @@ -169,6 +170,7 @@ export const CellInput = ({ }, [on_change]) ) + const toggle_notebook_exclusive = async () => await pluto_actions.toggle_notebook_exclusive(cell_id) useLayoutEffect(() => { const keyMapSubmit = () => { on_submit() @@ -305,6 +307,7 @@ export const CellInput = ({ { key: "Ctrl-Delete", run: keyMapDelete }, { key: "Backspace", run: keyMapBackspace }, { key: "Ctrl-Backspace", run: keyMapBackspace }, + { key: "Ctrl-e", run: toggle_notebook_exclusive }, ] let DOCS_UPDATER_VERBOSE = true @@ -496,12 +499,12 @@ export const CellInput = ({ return html` - <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} /> + <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} notebook_exclusive=${notebook_exclusive} toggle_notebook_exclusive=${toggle_notebook_exclusive}/> ` } -const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => { +const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, notebook_exclusive, toggle_notebook_exclusive}) => { const timeout = useRef(null) let pluto_actions = useContext(PlutoContext) const [open, setOpen] = useState(false) @@ -534,6 +537,12 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => ${running_disabled ? html`` : html``} ${running_disabled ? html`Enable cell` : html`Disable cell`} +
  • + ${notebook_exclusive ? html`` : html``} + ${notebook_exclusive ? html`Make open` : html`Make exclusive`} +
  • Coming soon…
  • ` : html``} diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 94b0cb99e5..85143d28f1 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -100,6 +100,7 @@ const first_true_key = (obj) => { * code: string, * code_folded: boolean, * running_disabled: boolean, + * notebook_exclusive: boolean, * }} */ @@ -357,6 +358,7 @@ export class Editor extends Component { code: code, code_folded: false, running_disabled: false, + notebook_exclusive: false, } }) @@ -415,6 +417,7 @@ export class Editor extends Component { code, code_folded: false, running_disabled: false, + notebook_exclusive: false, } notebook.cell_order = [...notebook.cell_order.slice(0, index), id, ...notebook.cell_order.slice(index, Infinity)] }) @@ -460,6 +463,12 @@ export class Editor extends Component { notebook.cell_inputs[cell_id].code_folded = newFolded }) }, + toggle_notebook_exclusive: async (cell_id) => { + const newVal = !this.state.notebook.cell_inputs[cell_id].notebook_exclusive + await update_notebook((notebook) => { + notebook.cell_inputs[cell_id].notebook_exclusive = newVal + }) + }, set_and_run_all_changed_remote_cells: () => { const changed = this.state.notebook.cell_order.filter( (cell_id) => @@ -923,6 +932,13 @@ patch: ${JSON.stringify( The notebook file saves every time you run a cell.` ) e.preventDefault() + } else if (e.key.toLowerCase() === "e" && has_ctrl_or_cmd_pressed(e)) { + const selected_cells = this.state.selected_cells + if (selected_cells.length > 0) { + e.preventDefault() + selected_cells.forEach((cell_id) => this.actions.toggle_notebook_exclusive(cell_id)) + } + } if (this.state.disable_ui && this.state.offer_binder) { diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index abb8cabce8..ac3a46b6f6 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -24,7 +24,7 @@ let CellMemo = ({ const selected_cells_diffable_primitive = (selected_cells || []).join("") const { body, last_run_timestamp, mime, persist_js_state, rootassignee } = cell_result?.output || {} const { queued, running, runtime, errored, depends_on_disabled_cells } = cell_result || {} - const { cell_id, code, code_folded, running_disabled } = cell_input || {} + const { cell_id, code, code_folded, running_disabled, notebook_exclusive } = cell_input || {} return useMemo(() => { return html` <${Cell} @@ -48,6 +48,7 @@ let CellMemo = ({ }, [ cell_id, running_disabled, + notebook_exclusive, depends_on_disabled_cells, queued, running, diff --git a/frontend/editor.css b/frontend/editor.css index 46cd1e6c6b..f9c8c6313d 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -859,6 +859,19 @@ pluto-cell.running_disabled > pluto-output { background-color: rgba(139, 139, 139, 0.25); } +pluto-cell.notebook_exclusive::after { + content: ''; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + z-index: -1; + right: -4.85px; + border-radius: 8px; + border-right: 5px solid #ccc; +} + .scroll_y { overflow-y: auto; max-height: 80vh; @@ -988,6 +1001,9 @@ pluto-cell.running_disabled > pluto-input .cm-editor { opacity: 0.3; } +pluto-cell.notebook_exclusive > pluto-input .CodeMirror { + border-top-right-radius: 4px; +} pluto-cell.running_disabled > pluto-input .cm-editor { background-color: rgba(139, 139, 139, 0.25); } @@ -1402,6 +1418,8 @@ pluto-input > button.delete_cell ul.input_context_menu { .disable_cell_icon, .delete_icon, .enable_cell_icon, +.enable_notebook_exclusive_icon, +.disable_notebook_exclusive_icon, .bandage_icon, .icon { --size: 17px; @@ -1429,6 +1447,15 @@ pluto-input > button.delete_cell ul.input_context_menu { background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle-outline.svg); } +.enable_notebook_exclusive_icon { + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.2/src/svg/enter-outline.svg); + background-size: 15px; +} +.disable_notebook_exclusive_icon { + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.2/src/svg/exit-outline.svg); + background-size: 15px; +} + pluto-input > button.delete_cell { /* top: 3px; */ /* left: -26px; */ diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index 6e881342fe..933f06e5ff 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -42,6 +42,8 @@ Base.@kwdef mutable struct Cell running_disabled::Bool=false depends_on_disabled_cells::Bool=false + + notebook_exclusive::Bool=false end Cell(cell_id, code) = Cell(cell_id=cell_id, code=code) @@ -55,6 +57,7 @@ function Base.convert(::Type{Cell}, cell::Dict) code=cell["code"], code_folded=cell["code_folded"], running_disabled=cell["running_disabled"], + notebook_exclusive=cell["notebook_exclusive"], ) end function Base.convert(::Type{UUID}, string::String) diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index 1adcc936b6..aa826880b3 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -91,6 +91,8 @@ const _order_delimiter = "# ╠═" const _order_delimiter_folded = "# ╟─" const _cell_suffix = "\n\n" +const _notebook_exclusive_prefix = "#=╠═╡ notebook_exclusive\n" +const _notebook_exclusive_suffix = "\n ╠═╡ notebook_exclusive =#" const _ptoml_cell_id = UUID(1) const _mtoml_cell_id = UUID(2) @@ -122,8 +124,15 @@ function save_notebook(io, notebook::Notebook) for c in cells_ordered println(io, _cell_id_delimiter, string(c.cell_id)) + + # We put the notebook exclusive prefix before the rest of the code + c.notebook_exclusive && print(io, _notebook_exclusive_prefix) + # write the cell code and prevent collisions with the cell delimiter print(io, replace(c.code, _cell_id_delimiter => "# ")) + + c.notebook_exclusive && print(io, _notebook_exclusive_suffix) + print(io, _cell_suffix) end @@ -211,10 +220,17 @@ function load_notebook_nobackup(io, path)::Notebook code_raw = String(readuntil(io, _cell_id_delimiter)) # change Windows line endings to Linux code_normalised = replace(code_raw, "\r\n" => "\n") + + # get the information if a cell is exclusive to this notebook + notebook_exclusive = startswith(code_normalised, _notebook_exclusive_prefix) + # remove the disabled on startup comments for further processing in Julia + code_normalised = replace(replace(code_normalised, _notebook_exclusive_prefix => ""), _notebook_exclusive_suffix => "") + # remove the cell suffix code = code_normalised[1:prevind(code_normalised, end, length(_cell_suffix))] read_cell = Cell(cell_id, code) + read_cell.notebook_exclusive = notebook_exclusive collected_cells[cell_id] = read_cell end end diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index 2858ca72d2..3f47d5e05e 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -107,6 +107,7 @@ function notebook_to_js(notebook::Notebook) "code" => cell.code, "code_folded" => cell.code_folded, "running_disabled" => cell.running_disabled, + "notebook_exclusive" => cell.notebook_exclusive, ) for (id, cell) in notebook.cells_dict), "cell_dependencies" => Dict{UUID,Dict{String,Any}}( From 7ea64ba03eda35b31ffa9c2fbe32d98e109fda25 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Mon, 4 Oct 2021 10:35:02 +0200 Subject: [PATCH 13/71] re-implement notebook exclusive by file comments in CM6 --- frontend/components/Cell.js | 4 +++- frontend/components/CellInput.js | 13 +++++++++++-- frontend/components/Editor.js | 17 +++++++++++++++++ frontend/components/Notebook.js | 3 ++- frontend/editor.css | 27 +++++++++++++++++++++++++++ src/notebook/Cell.jl | 3 +++ src/notebook/Notebook.jl | 16 ++++++++++++++++ src/webserver/Dynamic.jl | 1 + 8 files changed, 80 insertions(+), 4 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 962fd0782d..35f41290f3 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -21,7 +21,7 @@ import { PlutoContext } from "../common/PlutoContext.js" * }} props * */ export const Cell = ({ - cell_input: { cell_id, code, code_folded, running_disabled }, + cell_input: { cell_id, code, code_folded, running_disabled, notebook_exclusive }, cell_result: { queued, running, runtime, errored, output, published_objects, depends_on_disabled_cells }, cell_dependencies, cell_input_local, @@ -115,6 +115,7 @@ export const Cell = ({ code_folded: class_code_folded, running_disabled: running_disabled, depends_on_disabled_cells: depends_on_disabled_cells, + notebook_exclusive: notebook_exclusive, show_input: show_input, drop_target: drag_active, saving_file: saving_file, @@ -187,6 +188,7 @@ export const Cell = ({ cell_id=${cell_id} notebook_id=${notebook_id} running_disabled=${running_disabled} + notebook_exclusive=${notebook_exclusive} /> <${RunArea} cell_id=${cell_id} diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index ee815e4798..88d9130efb 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -144,6 +144,7 @@ export const CellInput = ({ notebook_id, running_disabled, cell_dependencies, + notebook_exclusive, }) => { let pluto_actions = useContext(PlutoContext) @@ -169,6 +170,7 @@ export const CellInput = ({ }, [on_change]) ) + const toggle_notebook_exclusive = async () => await pluto_actions.toggle_notebook_exclusive(cell_id) useLayoutEffect(() => { const keyMapSubmit = () => { on_submit() @@ -305,6 +307,7 @@ export const CellInput = ({ { key: "Ctrl-Delete", run: keyMapDelete }, { key: "Backspace", run: keyMapBackspace }, { key: "Ctrl-Backspace", run: keyMapBackspace }, + { key: "Ctrl-e", run: toggle_notebook_exclusive }, ] let DOCS_UPDATER_VERBOSE = true @@ -496,12 +499,12 @@ export const CellInput = ({ return html` - <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} /> + <${InputContextMenu} on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} running_disabled=${running_disabled} notebook_exclusive=${notebook_exclusive} toggle_notebook_exclusive=${toggle_notebook_exclusive}/> ` } -const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => { +const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, notebook_exclusive, toggle_notebook_exclusive}) => { const timeout = useRef(null) let pluto_actions = useContext(PlutoContext) const [open, setOpen] = useState(false) @@ -534,6 +537,12 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => ${running_disabled ? html`` : html``} ${running_disabled ? html`Enable cell` : html`Disable cell`} +
  • + ${notebook_exclusive ? html`` : html``} + ${notebook_exclusive ? html`Make open` : html`Make exclusive`} +
  • Coming soon…
  • ` : html``} diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 94b0cb99e5..e8960eede5 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -100,6 +100,7 @@ const first_true_key = (obj) => { * code: string, * code_folded: boolean, * running_disabled: boolean, + * notebook_exclusive: boolean, * }} */ @@ -274,6 +275,7 @@ export class Editor extends Component { code: code, code_folded: false, running_disabled: false, + notebook_exclusive: false, })) let index @@ -357,6 +359,7 @@ export class Editor extends Component { code: code, code_folded: false, running_disabled: false, + notebook_exclusive: false, } }) @@ -415,6 +418,7 @@ export class Editor extends Component { code, code_folded: false, running_disabled: false, + notebook_exclusive: false, } notebook.cell_order = [...notebook.cell_order.slice(0, index), id, ...notebook.cell_order.slice(index, Infinity)] }) @@ -460,6 +464,12 @@ export class Editor extends Component { notebook.cell_inputs[cell_id].code_folded = newFolded }) }, + toggle_notebook_exclusive: async (cell_id) => { + const newVal = !this.state.notebook.cell_inputs[cell_id].notebook_exclusive + await update_notebook((notebook) => { + notebook.cell_inputs[cell_id].notebook_exclusive = newVal + }) + }, set_and_run_all_changed_remote_cells: () => { const changed = this.state.notebook.cell_order.filter( (cell_id) => @@ -923,6 +933,13 @@ patch: ${JSON.stringify( The notebook file saves every time you run a cell.` ) e.preventDefault() + } else if (e.key.toLowerCase() === "e" && has_ctrl_or_cmd_pressed(e)) { + const selected_cells = this.state.selected_cells + if (selected_cells.length > 0) { + e.preventDefault() + selected_cells.forEach((cell_id) => this.actions.toggle_notebook_exclusive(cell_id)) + } + } if (this.state.disable_ui && this.state.offer_binder) { diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index abb8cabce8..ac3a46b6f6 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -24,7 +24,7 @@ let CellMemo = ({ const selected_cells_diffable_primitive = (selected_cells || []).join("") const { body, last_run_timestamp, mime, persist_js_state, rootassignee } = cell_result?.output || {} const { queued, running, runtime, errored, depends_on_disabled_cells } = cell_result || {} - const { cell_id, code, code_folded, running_disabled } = cell_input || {} + const { cell_id, code, code_folded, running_disabled, notebook_exclusive } = cell_input || {} return useMemo(() => { return html` <${Cell} @@ -48,6 +48,7 @@ let CellMemo = ({ }, [ cell_id, running_disabled, + notebook_exclusive, depends_on_disabled_cells, queued, running, diff --git a/frontend/editor.css b/frontend/editor.css index 46cd1e6c6b..f9c8c6313d 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -859,6 +859,19 @@ pluto-cell.running_disabled > pluto-output { background-color: rgba(139, 139, 139, 0.25); } +pluto-cell.notebook_exclusive::after { + content: ''; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + z-index: -1; + right: -4.85px; + border-radius: 8px; + border-right: 5px solid #ccc; +} + .scroll_y { overflow-y: auto; max-height: 80vh; @@ -988,6 +1001,9 @@ pluto-cell.running_disabled > pluto-input .cm-editor { opacity: 0.3; } +pluto-cell.notebook_exclusive > pluto-input .CodeMirror { + border-top-right-radius: 4px; +} pluto-cell.running_disabled > pluto-input .cm-editor { background-color: rgba(139, 139, 139, 0.25); } @@ -1402,6 +1418,8 @@ pluto-input > button.delete_cell ul.input_context_menu { .disable_cell_icon, .delete_icon, .enable_cell_icon, +.enable_notebook_exclusive_icon, +.disable_notebook_exclusive_icon, .bandage_icon, .icon { --size: 17px; @@ -1429,6 +1447,15 @@ pluto-input > button.delete_cell ul.input_context_menu { background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle-outline.svg); } +.enable_notebook_exclusive_icon { + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.2/src/svg/enter-outline.svg); + background-size: 15px; +} +.disable_notebook_exclusive_icon { + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.2/src/svg/exit-outline.svg); + background-size: 15px; +} + pluto-input > button.delete_cell { /* top: 3px; */ /* left: -26px; */ diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index 6e881342fe..933f06e5ff 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -42,6 +42,8 @@ Base.@kwdef mutable struct Cell running_disabled::Bool=false depends_on_disabled_cells::Bool=false + + notebook_exclusive::Bool=false end Cell(cell_id, code) = Cell(cell_id=cell_id, code=code) @@ -55,6 +57,7 @@ function Base.convert(::Type{Cell}, cell::Dict) code=cell["code"], code_folded=cell["code_folded"], running_disabled=cell["running_disabled"], + notebook_exclusive=cell["notebook_exclusive"], ) end function Base.convert(::Type{UUID}, string::String) diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index 1adcc936b6..aa826880b3 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -91,6 +91,8 @@ const _order_delimiter = "# ╠═" const _order_delimiter_folded = "# ╟─" const _cell_suffix = "\n\n" +const _notebook_exclusive_prefix = "#=╠═╡ notebook_exclusive\n" +const _notebook_exclusive_suffix = "\n ╠═╡ notebook_exclusive =#" const _ptoml_cell_id = UUID(1) const _mtoml_cell_id = UUID(2) @@ -122,8 +124,15 @@ function save_notebook(io, notebook::Notebook) for c in cells_ordered println(io, _cell_id_delimiter, string(c.cell_id)) + + # We put the notebook exclusive prefix before the rest of the code + c.notebook_exclusive && print(io, _notebook_exclusive_prefix) + # write the cell code and prevent collisions with the cell delimiter print(io, replace(c.code, _cell_id_delimiter => "# ")) + + c.notebook_exclusive && print(io, _notebook_exclusive_suffix) + print(io, _cell_suffix) end @@ -211,10 +220,17 @@ function load_notebook_nobackup(io, path)::Notebook code_raw = String(readuntil(io, _cell_id_delimiter)) # change Windows line endings to Linux code_normalised = replace(code_raw, "\r\n" => "\n") + + # get the information if a cell is exclusive to this notebook + notebook_exclusive = startswith(code_normalised, _notebook_exclusive_prefix) + # remove the disabled on startup comments for further processing in Julia + code_normalised = replace(replace(code_normalised, _notebook_exclusive_prefix => ""), _notebook_exclusive_suffix => "") + # remove the cell suffix code = code_normalised[1:prevind(code_normalised, end, length(_cell_suffix))] read_cell = Cell(cell_id, code) + read_cell.notebook_exclusive = notebook_exclusive collected_cells[cell_id] = read_cell end end diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index 2858ca72d2..3f47d5e05e 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -107,6 +107,7 @@ function notebook_to_js(notebook::Notebook) "code" => cell.code, "code_folded" => cell.code_folded, "running_disabled" => cell.running_disabled, + "notebook_exclusive" => cell.notebook_exclusive, ) for (id, cell) in notebook.cells_dict), "cell_dependencies" => Dict{UUID,Dict{String,Any}}( From e3040114c2f0f3f91f094319e299e4c5d5a6e5e1 Mon Sep 17 00:00:00 2001 From: disberd Date: Tue, 5 Oct 2021 09:08:02 +0200 Subject: [PATCH 14/71] Correctly handle textarea paste in CM6 The new CM6 document structure does not seem to put the text into an element with `"TEXTAREA"` as tag. Now it seems that instead the DOM element where the text is inserted is a simple DIV with class `cm-content`. --- frontend/common/KeyboardShortcuts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/common/KeyboardShortcuts.js b/frontend/common/KeyboardShortcuts.js index cd65f02f21..ea98c34be7 100644 --- a/frontend/common/KeyboardShortcuts.js +++ b/frontend/common/KeyboardShortcuts.js @@ -21,6 +21,6 @@ export let map_cmd_to_ctrl_on_mac = (keymap) => { } export let in_textarea_or_input = () => { - const { tagName } = document.activeElement - return tagName === "INPUT" || tagName === "TEXTAREA" + const { tagName, classList } = document.activeElement + return tagName === "INPUT" || tagName === "TEXTAREA" || classList.contains('cm-content') } From cf5f39805210e77ad7395fc53b7a2c534bd705d6 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 5 Oct 2021 13:16:58 +0300 Subject: [PATCH 15/71] Correct distribution of CM6 --- frontend/imports/CodemirrorPlutoSetup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index f15d162b20..46d10c277e 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -51,7 +51,7 @@ import { markdownLanguage, javascript, javascriptLanguage, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@93bb0a8bda7ab2e0771b6e6a2a7225e37f30ea5a/dist/index.es.min.js" +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@f9e13ec/dist/index.es.min.js" const htmlParser = htmlLanguage.parser const mdParser = markdownLanguage.parser From 2f3755d3020691fed4247508fe908bbd78623fd1 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Tue, 5 Oct 2021 14:53:52 +0200 Subject: [PATCH 16/71] Point static export to the files from this branch/repo --- src/notebook/Export.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/notebook/Export.jl b/src/notebook/Export.jl index 043fac2c1f..a0c441aae7 100644 --- a/src/notebook/Export.jl +++ b/src/notebook/Export.jl @@ -22,14 +22,16 @@ function generate_html(; original = read(project_relative_path("frontend", "editor.html"), String) - cdn_root = if pluto_cdn_root === nothing - if version === nothing - version = PLUTO_VERSION - end - "https://cdn.jsdelivr.net/gh/fonsp/Pluto.jl@$(something(cdn_version_override, string(PLUTO_VERSION)))/frontend/" - else - pluto_cdn_root - end + # cdn_root = if pluto_cdn_root === nothing + # if version === nothing + # version = PLUTO_VERSION + # end + # "https://cdn.jsdelivr.net/gh/fonsp/Pluto.jl@$(something(cdn_version_override, string(PLUTO_VERSION)))/frontend/" + # else + # pluto_cdn_root + # end + + cdn_root = "https://cdn.jsdelivr.net/gh/disberd/Pluto.jl@CM6-exclusive-old/frontend/" @debug "Using CDN for Pluto assets:" cdn_root From 965033f066a13ccb87feb973ed2da34cc6e0dd40 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 5 Oct 2021 15:56:38 +0300 Subject: [PATCH 17/71] Add support for SQL --- frontend/components/CellInput.js | 2 ++ frontend/imports/CodemirrorPlutoSetup.js | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 71c544d4e3..1d39688635 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -47,6 +47,7 @@ import { markdown, html as htmlLang, javascript, + sqlLang } from "../imports/CodemirrorPlutoSetup.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" @@ -412,6 +413,7 @@ export const CellInput = ({ markdown(), htmlLang(), //Provides tag closing!, javascript(), + //sqlLang, go_to_definition_plugin, pluto_autocomplete({ request_autocomplete: async ({ text }) => { diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 46d10c277e..511c76308f 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -51,30 +51,35 @@ import { markdownLanguage, javascript, javascriptLanguage, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@f9e13ec/dist/index.es.min.js" + sql, + PostgreSQL +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@32e0fbb/dist/index.es.min.js" const htmlParser = htmlLanguage.parser const mdParser = markdownLanguage.parser +const postgresParser = PostgreSQL.language.parser +const sqlLang = sql({config: {dialect: PostgreSQL}}) const juliaWrapper = parseMixed((node, input) => { - if (node.type.id !== 69 && node.type.id !== 68 /* TemplateString */) { + if (node.type.id < 68 && node.type.id > 71 /* Strings */) { return null } //Looking for tag OR MacroIdentifier - const tagNode = node.node.prevSibling || node.node.parent.prevSibling + const tagNode = node.node?.prevSibling || node.node?.parent?.prevSibling if (!tagNode){ // If you can't find a tag node, something is broken in the julia syntax, // so parse it as Julia. Probably wrong interpolation! return null } const tag = input.read(tagNode.from, tagNode.to) - console.log({...node}, {...input}, node.type.name, node.type.id, node.from, node.to) let parser if (tag === "html" || tag === "@htl") { parser = htmlParser } else if (tag === "md") { parser = mdParser + } else if (tag === "sql"){ + parser = postgresParser } else { return null } @@ -161,4 +166,5 @@ export { html, javascript, javascriptLanguage, + sqlLang } From 4b6d2919342dc5bb31e16d68ebe8d619f5c44d1f Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 5 Oct 2021 20:13:45 +0300 Subject: [PATCH 18/71] =?UTF-8?q?New=20colors=20=F0=9F=9F=A2=F0=9F=9F=A0?= =?UTF-8?q?=F0=9F=9F=A1=F0=9F=9F=A2=F0=9F=94=B5=F0=9F=9F=A3=F0=9F=9F=A4?= =?UTF-8?q?=F0=9F=94=B4=E2=AD=95=F0=9F=98=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/CellInput.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 1d39688635..4c744d0fb3 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -84,23 +84,23 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.invalid, color: "#000", background: "#ef6155" }, // Object.keys(tags).map((x) => ({ tag: x, color: x })), // Markdown - { tag: tags.heading, color: "#0e2bb9", fontWeight: 500 }, - { tag: tags.heading1, color: "#0e2bb9", fontWeight: 500, fontSize: "1.5em" }, - { tag: tags.heading2, color: "red", fontWeight: 400, fontSize: "1.4em" }, - { tag: tags.heading3, color: "red", fontWeight: 400, fontSize: "1.25em" }, - { tag: tags.heading4, color: "red", fontWeight: 400, fontSize: "1.1em" }, - { tag: tags.heading5, color: "red", fontWeight: 400, fontSize: "1em" }, - { tag: tags.heading6, color: "red", fontWeight: "bold", fontSize: "0.8em" }, + { tag: tags.heading, color: "#081e87", fontWeight: 500 }, + { tag: tags.heading1, color: "#081e87", fontWeight: 500, fontSize: "1.5em" }, + { tag: tags.heading2, color: "#081e87", fontWeight: 500, fontSize: "1.4em" }, + { tag: tags.heading3, color: "#081e87", fontWeight: 500, fontSize: "1.25em" }, + { tag: tags.heading4, color: "#081e87", fontWeight: 500, fontSize: "1.1em" }, + { tag: tags.heading5, color: "#081e87", fontWeight: 500, fontSize: "1em" }, + { tag: tags.heading6, color: "#081e87", fontWeight: "bold", fontSize: "0.8em" }, { tag: tags.url, color: "#48b685", textDecoration: "underline" }, { tag: tags.quote, color: "#444", fontStyle: "italic" }, - { tag: tags.literal, color: "grey", fontWeight: 700 }, + { tag: tags.literal, color: "#232227", fontWeight: 700 }, // HTML - { tag: tags.tagName, color: "darkblue", fontWeight: 700 }, - { tag: tags.attributeName, color: "darkblue", fontWeight: 400 }, - { tag: tags.attributeValue, color: "orange", fontWeight: 700 }, - { tag: tags.angleBracket, color: "black", fontWeight: 700 }, - { tag: tags.content, color: "darkgrey", fontWeight: 400 }, - { tag: tags.documentMeta, color: "grey", fontStyle: "italic" }, + { tag: tags.tagName, color: "#01654f", fontWeight: 600 }, + { tag: tags.attributeName, color: "#01654f", fontWeight: 400 }, + { tag: tags.attributeValue, color: "#01654f", fontWeight: 600 }, + { tag: tags.angleBracket, color: "#01654f", fontWeight: 600 }, + { tag: tags.content, color: "#232227", fontWeight: 400 }, + { tag: tags.documentMeta, color: "#232227", fontStyle: "italic" }, // CSS { tag: tags.className, color: "grey", fontWeight: "bold" }, From 7d3a28c160d13094b42dc34774eb6b693bc2a016 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 12 Oct 2021 17:19:25 +0200 Subject: [PATCH 19/71] Disable the julia parser on html and md string macros --- frontend/imports/CodemirrorPlutoSetup.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 511c76308f..c9f668b57a 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -74,10 +74,16 @@ const juliaWrapper = parseMixed((node, input) => { const tag = input.read(tagNode.from, tagNode.to) let parser - if (tag === "html" || tag === "@htl") { + if (tag === "@htl") { parser = htmlParser + } else if (tag === "html" ) { + return { + parser: htmlParser + } } else if (tag === "md") { - parser = mdParser + return { + parser: mdParser + } } else if (tag === "sql"){ parser = postgresParser } else { From 4b057a71689702f34ed984f89dbc40cbf5630ca4 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 12 Oct 2021 19:15:56 +0200 Subject: [PATCH 20/71] Fix issue with wrong overlays on macros called without a fn-like call --- frontend/components/CellInput.js | 2 +- frontend/imports/CodemirrorPlutoSetup.js | 32 +++++++++++------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 4c744d0fb3..5c3befd88f 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -331,7 +331,7 @@ export const CellInput = ({ { key: "Ctrl-Backspace", run: keyMapBackspace }, ] - let DOCS_UPDATER_VERBOSE = true + let DOCS_UPDATER_VERBOSE = false const docs_updater = EditorView.updateListener.of((update) => { if (!update.view.hasFocus) { return diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index c9f668b57a..1b0ce327c0 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -52,21 +52,21 @@ import { javascript, javascriptLanguage, sql, - PostgreSQL + PostgreSQL, } from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@32e0fbb/dist/index.es.min.js" const htmlParser = htmlLanguage.parser const mdParser = markdownLanguage.parser const postgresParser = PostgreSQL.language.parser -const sqlLang = sql({config: {dialect: PostgreSQL}}) +const sqlLang = sql({ config: { dialect: PostgreSQL } }) const juliaWrapper = parseMixed((node, input) => { if (node.type.id < 68 && node.type.id > 71 /* Strings */) { return null } - //Looking for tag OR MacroIdentifier + //Looking for tag OR MacroIdentifier const tagNode = node.node?.prevSibling || node.node?.parent?.prevSibling - if (!tagNode){ + if (!tagNode) { // If you can't find a tag node, something is broken in the julia syntax, // so parse it as Julia. Probably wrong interpolation! return null @@ -76,15 +76,15 @@ const juliaWrapper = parseMixed((node, input) => { if (tag === "@htl") { parser = htmlParser - } else if (tag === "html" ) { + } else if (tag === "html") { return { - parser: htmlParser + parser: htmlParser, } } else if (tag === "md") { return { - parser: mdParser + parser: mdParser, } - } else if (tag === "sql"){ + } else if (tag === "sql") { parser = postgresParser } else { return null @@ -92,29 +92,27 @@ const juliaWrapper = parseMixed((node, input) => { const overlay = [] //: { from: number, to: number }[] = []; let from = node.from - console.log(node.node.firstChild) for (let child = node.node.firstChild; child !== null; child = child?.nextSibling) { - overlay.push({ from, to: child.from }) + overlay.push({ from, to: child.to }) from = child.to } - if (overlay.length === 0) { + if (overlay.length === 0 || node.node.firstChild === null) { return { parser } } - overlay.push({ from, to: node.to }) - // TODO: replace $() from overlays - add placeholder?? // Remove quotes from strings - if(node.type.id === 69){ // Triple Quote String + if (node.type.id === 69) { + // Triple Quote String overlay[0].from += 3 overlay[overlay.length - 1].to -= 3 } - if(node.type.id === 68){ // Single quote string + if (node.type.id === 68) { + // Single quote string overlay[0].from += 1 overlay[overlay.length - 1].to -= 1 } - // console.log(">>> ", overlay.map(({from, to}) => input.read(from, to)).join("")) return { parser, overlay: overlay } }) @@ -172,5 +170,5 @@ export { html, javascript, javascriptLanguage, - sqlLang + sqlLang, } From 16401a6f2eb5653c1a9b6879dcd09434e5683c44 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Thu, 14 Oct 2021 01:00:04 +0200 Subject: [PATCH 21/71] Add support for python (@py_str), @javascript and mermaid --- frontend/components/CellInput.js | 14 +++++----- frontend/imports/CodemirrorPlutoSetup.js | 34 +++++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 5c3befd88f..e96c07827c 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -47,7 +47,8 @@ import { markdown, html as htmlLang, javascript, - sqlLang + sqlLang, + python, } from "../imports/CodemirrorPlutoSetup.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" @@ -103,7 +104,6 @@ export const pluto_syntax_colors = HighlightStyle.define([ { tag: tags.documentMeta, color: "#232227", fontStyle: "italic" }, // CSS { tag: tags.className, color: "grey", fontWeight: "bold" }, - ]) const getValue6 = (/** @type {EditorView} */ cm) => cm.state.doc.toString() @@ -331,7 +331,7 @@ export const CellInput = ({ { key: "Ctrl-Backspace", run: keyMapBackspace }, ] - let DOCS_UPDATER_VERBOSE = false + let DOCS_UPDATER_VERBOSE = true const docs_updater = EditorView.updateListener.of((update) => { if (!update.view.hasFocus) { return @@ -413,6 +413,7 @@ export const CellInput = ({ markdown(), htmlLang(), //Provides tag closing!, javascript(), + python(), //sqlLang, go_to_definition_plugin, pluto_autocomplete({ @@ -450,9 +451,10 @@ export const CellInput = ({ // @ts-ignore dom_node_ref.current.CodeMirror = newcm.dom.CodeMirror = { getValue: () => newcm.state.doc.toString(), - setValue: (value) => newcm.dispatch({ - changes: { from: 0, to: newcm.state.doc.length, insert: value }, - }) + setValue: (value) => + newcm.dispatch({ + changes: { from: 0, to: newcm.state.doc.length, insert: value }, + }), } if (focus_after_creation) { diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 1b0ce327c0..c832717d80 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -53,17 +53,32 @@ import { javascriptLanguage, sql, PostgreSQL, -} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@32e0fbb/dist/index.es.min.js" + python, + pythonLanguage, +} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@c241428/dist/index.es.min.js" const htmlParser = htmlLanguage.parser const mdParser = markdownLanguage.parser const postgresParser = PostgreSQL.language.parser const sqlLang = sql({ config: { dialect: PostgreSQL } }) +const pythonParser = pythonLanguage.parser const juliaWrapper = parseMixed((node, input) => { - if (node.type.id < 68 && node.type.id > 71 /* Strings */) { + console.log(node.type.id, node.type.name, ["TripleString", "String", "CommandString"].includes(node.type.name)) + if (!["TripleString", "String", "CommandString"].includes(node.type.name)) { + console.log(node.name, "NOPE") return null } + const offset = node.name === "TripleString" ? 3 : 1 + const defaultOverlay = [{ from: node.from + offset, to: node.to - offset }] + + if (defaultOverlay[0].from >= defaultOverlay[0].to) { + console.log(JSON.stringify(defaultOverlay), "returning") + return null + } + console.log("not returning") + + console.log(JSON.stringify(defaultOverlay), input.read(defaultOverlay[0].from, defaultOverlay[0].to)) //Looking for tag OR MacroIdentifier const tagNode = node.node?.prevSibling || node.node?.parent?.prevSibling if (!tagNode) { @@ -79,10 +94,22 @@ const juliaWrapper = parseMixed((node, input) => { } else if (tag === "html") { return { parser: htmlParser, + overlay: defaultOverlay, } - } else if (tag === "md") { + } else if (tag === "md" || tag === "mermaid") { return { parser: mdParser, + overlay: defaultOverlay, + } + } else if (tag === "@javascript") { + return { + parser: javascriptLanguage.parser, + overlay: defaultOverlay, + } + } else if (tag === "py") { + return { + parser: pythonParser, + overlay: defaultOverlay, } } else if (tag === "sql") { parser = postgresParser @@ -171,4 +198,5 @@ export { javascript, javascriptLanguage, sqlLang, + python, } From 18a807b82af99e463948c81937d1185655612759 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Thu, 14 Oct 2021 01:15:34 +0200 Subject: [PATCH 22/71] Fix big nested interpolations --- frontend/imports/CodemirrorPlutoSetup.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index c832717d80..744d6ea123 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -120,26 +120,26 @@ const juliaWrapper = parseMixed((node, input) => { const overlay = [] //: { from: number, to: number }[] = []; let from = node.from for (let child = node.node.firstChild; child !== null; child = child?.nextSibling) { - overlay.push({ from, to: child.to }) + overlay.push({ from, to: child.from }) from = child.to } if (overlay.length === 0 || node.node.firstChild === null) { return { parser } } - + overlay.push({ from, to: node.to }) // TODO: replace $() from overlays - add placeholder?? // Remove quotes from strings - if (node.type.id === 69) { + if (node.type.name === "TripleString") { // Triple Quote String overlay[0].from += 3 overlay[overlay.length - 1].to -= 3 - } - if (node.type.id === 68) { + } else { // Single quote string overlay[0].from += 1 overlay[overlay.length - 1].to -= 1 } + console.log(JSON.stringify(overlay)) return { parser, overlay: overlay } }) From 82e27ac4aac7d9b61abcd151ae2de7622ab7273f Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Thu, 14 Oct 2021 01:16:27 +0200 Subject: [PATCH 23/71] Clean up console.logs --- frontend/imports/CodemirrorPlutoSetup.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js index 744d6ea123..f1783b786b 100644 --- a/frontend/imports/CodemirrorPlutoSetup.js +++ b/frontend/imports/CodemirrorPlutoSetup.js @@ -64,21 +64,16 @@ const sqlLang = sql({ config: { dialect: PostgreSQL } }) const pythonParser = pythonLanguage.parser const juliaWrapper = parseMixed((node, input) => { - console.log(node.type.id, node.type.name, ["TripleString", "String", "CommandString"].includes(node.type.name)) if (!["TripleString", "String", "CommandString"].includes(node.type.name)) { - console.log(node.name, "NOPE") return null } const offset = node.name === "TripleString" ? 3 : 1 const defaultOverlay = [{ from: node.from + offset, to: node.to - offset }] if (defaultOverlay[0].from >= defaultOverlay[0].to) { - console.log(JSON.stringify(defaultOverlay), "returning") return null } - console.log("not returning") - console.log(JSON.stringify(defaultOverlay), input.read(defaultOverlay[0].from, defaultOverlay[0].to)) //Looking for tag OR MacroIdentifier const tagNode = node.node?.prevSibling || node.node?.parent?.prevSibling if (!tagNode) { @@ -139,7 +134,6 @@ const juliaWrapper = parseMixed((node, input) => { overlay[0].from += 1 overlay[overlay.length - 1].to -= 1 } - console.log(JSON.stringify(overlay)) return { parser, overlay: overlay } }) From 971ae0fbef62e7eec36d15e911beb15ce00bf45b Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 20 Oct 2021 14:59:41 +0200 Subject: [PATCH 24/71] Put back the setValue to the cell --- frontend/components/CellInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 30310d2032..cce31ef681 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -457,7 +457,7 @@ export const CellInput = ({ // For use from useDropHandler // @ts-ignore - newcm.dom.CodeMirror = { + dom_node_ref.current.CodeMirror = newcm.dom.CodeMirror = { getValue: () => getValue6(newcm), setValue: (x) => setValue6(newcm, x), } From 31d892ae5a8db3019f082316cea81e26ca009735 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 3 Nov 2021 15:40:02 +0100 Subject: [PATCH 25/71] Add paste image --- .../components/CellInput/pluto_paste_plugin.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/frontend/components/CellInput/pluto_paste_plugin.js b/frontend/components/CellInput/pluto_paste_plugin.js index 6dd8a11948..2bd3f37123 100644 --- a/frontend/components/CellInput/pluto_paste_plugin.js +++ b/frontend/components/CellInput/pluto_paste_plugin.js @@ -13,6 +13,22 @@ export let pluto_paste_plugin = ({ pluto_actions, cell_id }) => { // Prevent this event from reaching the Editor-level paste handler event.stopPropagation() + const cdata = event.clipboardData + if (cdata.files.length > 0) { /* Clipboard is a file, we want to capture the file and save it on the computer */ + event.preventDefault() + let myBlob = cdata.files[0] + let ftype,fext + [ftype, fext] = myBlob.type.split("/") /* Separate image type and extension */ + // Create the href to download the image + let link = document.createElement("a") + link.href = URL.createObjectURL(myBlob) /* Create URL from the Blob */ + let fname = cell_id + "." + fext /* Create the file name from t he cell_id */ + link.download = fname + link.click() // Downlaod the file + view.dom.CodeMirror.setValue(`show_pasted_image(\"${fname}\")`) + return true + } + const topaste = event.clipboardData.getData("text/plain") const deserializer = detect_deserializer(topaste, false) if (deserializer == null) { From a531895650e24f46594c5f64c9d0c3bba51a14b4 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 15 Nov 2021 22:12:03 +0100 Subject: [PATCH 26/71] =?UTF-8?q?=F0=9F=98=B5=E2=80=8D=F0=9F=92=AB=20Allow?= =?UTF-8?q?=20cyclic=20recursive=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analysis/Errors.jl | 7 ++----- src/analysis/topological_order.jl | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/analysis/Errors.jl b/src/analysis/Errors.jl index 00e741fc07..513d8eefe8 100644 --- a/src/analysis/Errors.jl +++ b/src/analysis/Errors.jl @@ -7,11 +7,8 @@ struct CyclicReferenceError <: ReactivityError syms::Set{Symbol} end -function CyclicReferenceError(topology::NotebookTopology, cycle::Cell...) - referenced_during_cycle = union((topology.nodes[c].references for c in cycle)...) - assigned_during_cycle = union((topology.nodes[c].definitions ∪ topology.nodes[c].funcdefs_without_signatures for c in cycle)...) - - CyclicReferenceError(referenced_during_cycle ∩ assigned_during_cycle) +function CyclicReferenceError(topology::NotebookTopology, cycle::AbstractVector{Cell}) + CyclicReferenceError(cyclic_variables(topology, cycle)) end struct MultipleDefinitionsError <: ReactivityError diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index 7affc7d3f1..9814507a69 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -15,13 +15,17 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots elseif cell in entries currently_in = setdiff(entries, exits) cycle = currently_in[findfirst(isequal(cell), currently_in):end] - for cell in cycle - errable[cell] = CyclicReferenceError(topology, cycle...) + + if !cycle_is_among_functions(topology, cycle) + for cell in cycle + errable[cell] = CyclicReferenceError(topology, cycle) + end + return end - return + else + push!(entries, cell) end - push!(entries, cell) assigners = where_assigned(notebook, topology, cell) if !allow_multiple_defs && length(assigners) > 1 for c in assigners @@ -115,6 +119,22 @@ function is_assigned_anywhere(notebook::Notebook, topology::NotebookTopology, sy end end +function cyclic_variables(topology::NotebookTopology, cycle::AbstractVector{Cell})::Set{Symbol} + referenced_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].references for c in cycle)...) + assigned_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].definitions ∪ topology.nodes[c].funcdefs_without_signatures for c in cycle)...) + + referenced_during_cycle ∩ assigned_during_cycle +end + +function cycle_is_among_functions(topology::NotebookTopology, cycle::AbstractVector{Cell})::Bool + cyclics = cyclic_variables(topology, cycle) + + all( + any(s ∈ topology.nodes[c].funcdefs_without_signatures for c in cycle) + for s in cyclics + ) +end + """Assigns a number to a cell - cells with a lower number might run first. From b13fb67539afc1247637fd11ddfe185123e03df8 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 15 Nov 2021 22:35:12 +0100 Subject: [PATCH 27/71] All tests pass yay --- test/React.jl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/React.jl b/test/React.jl index 5518b43fd3..5c428d7c75 100644 --- a/test/React.jl +++ b/test/React.jl @@ -685,15 +685,15 @@ import Distributed update_run!(🍭, notebook, notebook.cells[6:end]) - @test_broken noerror(notebook.cells[6]; verbose=false) - @test_broken noerror(notebook.cells[7]; verbose=false) - @test_broken noerror(notebook.cells[8]; verbose=false) - @test_broken noerror(notebook.cells[9]; verbose=false) - @test_broken noerror(notebook.cells[10]; verbose=false) + @test noerror(notebook.cells[6]) + @test noerror(notebook.cells[7]) + @test noerror(notebook.cells[8]) + @test noerror(notebook.cells[9]) + @test noerror(notebook.cells[10]) @test noerror(notebook.cells[11]) @test noerror(notebook.cells[12]) - @test_broken noerror(notebook.cells[13]; verbose=false) - @test_broken noerror(notebook.cells[14]; verbose=false) + @test noerror(notebook.cells[13]) + @test noerror(notebook.cells[14]) @test noerror(notebook.cells[15]) @test noerror(notebook.cells[16]) @test noerror(notebook.cells[17]) @@ -701,11 +701,11 @@ import Distributed @test noerror(notebook.cells[19]) @test noerror(notebook.cells[20]) @test noerror(notebook.cells[21]) - @test_broken noerror(notebook.cells[22]; verbose=false) - @test_broken noerror(notebook.cells[23]; verbose=false) + @test noerror(notebook.cells[22]) + @test noerror(notebook.cells[23]) @test noerror(notebook.cells[24]) - @test_broken noerror(notebook.cells[25]; verbose=false) - @test_broken noerror(notebook.cells[26]; verbose=false) + @test noerror(notebook.cells[25]) + @test noerror(notebook.cells[26]) @assert length(notebook.cells) == 26 From 8a696193e6668baac90302e9478eb6d9907fd5c2 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Nov 2021 21:27:59 +0100 Subject: [PATCH 28/71] soft edges --- src/analysis/ExpressionExplorer.jl | 18 +++++----- src/analysis/ReactiveNode.jl | 6 ++-- test/React.jl | 55 +++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/analysis/ExpressionExplorer.jl b/src/analysis/ExpressionExplorer.jl index 213f1f5144..b466191383 100644 --- a/src/analysis/ExpressionExplorer.jl +++ b/src/analysis/ExpressionExplorer.jl @@ -530,25 +530,27 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState innerscopestate.inglobalscope = false funcname, innersymstate = explore_funcdef!(funcroot, innerscopestate) + + if length(funcname) == 1 + push!(scopestate.definedfuncs, funcname[end]) + push!(scopestate.hiddenglobals, funcname[end]) + elseif length(funcname) > 1 + push!(symstate.references, funcname[end - 1]) # reference the module of the extended function + push!(scopestate.hiddenglobals, funcname[end - 1]) + end + # Macro are called using @funcname, but defined with funcname. We need to change that in our scopestate # (The `!= 0` is for when the function named couldn't be parsed) if ex.head == :macro && length(funcname) != 0 setdiff!(innerscopestate.hiddenglobals, funcname) funcname = Symbol[Symbol("@$(funcname[1])")] - push!(innerscopestate.hiddenglobals, funcname...) + push!(innerscopestate.hiddenglobals, only(funcname)) end union!(innersymstate, explore!(Expr(:block, ex.args[2:end]...), innerscopestate)) funcnamesig = FunctionNameSignaturePair(funcname, canonalize(funcroot)) - if length(funcname) == 1 - push!(scopestate.definedfuncs, funcname[end]) - push!(scopestate.hiddenglobals, funcname[end]) - elseif length(funcname) > 1 - push!(symstate.references, funcname[end - 1]) # reference the module of the extended function - end - if will_assign_global(funcname, scopestate) symstate.funcdefs[funcnamesig] = innersymstate else diff --git a/src/analysis/ReactiveNode.jl b/src/analysis/ReactiveNode.jl index a572f2a469..3df0a33fc3 100644 --- a/src/analysis/ReactiveNode.jl +++ b/src/analysis/ReactiveNode.jl @@ -55,12 +55,12 @@ function ReactiveNode(symstate::SymbolsState) push!(result.funcdefs_without_signatures, join_funcname_parts(namesig.name)) generated_names = generate_funcnames(namesig.name) - new_sigs = map(name -> FunctionNameSignaturePair(name, namesig.canonicalized_head), generated_names) - union!(result.funcdefs_with_signatures, new_sigs) + # new_sigs = map(name -> FunctionNameSignaturePair(name, namesig.canonicalized_head), generated_names) + # union!(result.funcdefs_with_signatures, new_sigs) generated_names_syms = Set{Symbol}(join_funcname_parts.(generated_names)) - union!(result.funcdefs_without_signatures, generated_names_syms) + union!(result.soft_definitions, generated_names_syms) filter!(!∈(generated_names_syms), result.references) # don't reference defined functions (simulated recursive calls) end diff --git a/test/React.jl b/test/React.jl index 5c428d7c75..2807adcb6d 100644 --- a/test/React.jl +++ b/test/React.jl @@ -374,13 +374,66 @@ import Distributed fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) - @test :conj ∈ notebook.topology.nodes[notebook.cells[3]].funcdefs_without_signatures + @test :conj ∈ notebook.topology.nodes[notebook.cells[3]].soft_definitions @test :conj ∈ notebook.topology.nodes[notebook.cells[1]].references @test notebook.cells[1].output.body == "200" WorkspaceManager.unmake_workspace((🍭, notebook)) 🍭.options.evaluation.workspace_use_distributed = false end + + @testset "Function use inv in its def but also has a method on inv" begin + notebook = Notebook(Cell.([ + """ + struct MyStruct + s + + MyStruct(x) = new(inv(x)) + end + """, + """ + Base.inv(s::MyStruct) = inv(s.s) + """, + "MyStruct(1.) |> inv" + ])) + cell(idx) = notebook.cells[idx] + fakeclient.connected_notebook = notebook + update_run!(🍭, notebook, notebook.cells) + + @test cell(1) |> noerror + @test cell(2) |> noerror + @test cell(3) |> noerror + end + + @testset "Reactive methods definitions" begin + notebook = Notebook(Cell.([ + raw""" + Base.sqrt(s::String) = "sqrt($s)" + """, + """ + string((sqrt("🍕"), rand())) + """, + "", + ])) + cell(idx) = notebook.cells[idx] + fakeclient.connected_notebook = notebook + update_run!(🍭, notebook, notebook.cells) + + output_21 = cell(2).output.body + @test contains(output_21, "sqrt(🍕)") + + setcode(cell(3), """ + Base.sqrt(x::Int) = sqrt(Float64(x)^2) + """) + update_run!(🍭, notebook, cell(3)) + + output_22 = cell(2).output.body + @test cell(3) |> noerror + @test cell(2) |> noerror + @test cell(1) |> noerror + @test output_21 != output_22 # cell2 re-run + @test contains(output_22, "sqrt(🍕)") + end @testset "Multiple methods across cells" begin notebook = Notebook([ From ac331678d9db764a359acd6a3f4d55fb323aa3e2 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 15 Nov 2021 22:54:20 +0100 Subject: [PATCH 29/71] make soft edges break on dependencies cycle --- src/analysis/Errors.jl | 2 +- src/analysis/ReactiveNode.jl | 8 ++-- src/analysis/topological_order.jl | 67 ++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/analysis/Errors.jl b/src/analysis/Errors.jl index 513d8eefe8..446ed475a1 100644 --- a/src/analysis/Errors.jl +++ b/src/analysis/Errors.jl @@ -48,4 +48,4 @@ function relay_reactivity_error!(cell::Cell, error::Exception) cell.published_objects = Dict{String,Any}() cell.runtime = nothing cell.errored = true -end \ No newline at end of file +end diff --git a/src/analysis/ReactiveNode.jl b/src/analysis/ReactiveNode.jl index 3df0a33fc3..927474db7d 100644 --- a/src/analysis/ReactiveNode.jl +++ b/src/analysis/ReactiveNode.jl @@ -55,12 +55,12 @@ function ReactiveNode(symstate::SymbolsState) push!(result.funcdefs_without_signatures, join_funcname_parts(namesig.name)) generated_names = generate_funcnames(namesig.name) - - # new_sigs = map(name -> FunctionNameSignaturePair(name, namesig.canonicalized_head), generated_names) - # union!(result.funcdefs_with_signatures, new_sigs) - generated_names_syms = Set{Symbol}(join_funcname_parts.(generated_names)) + + # add the generated names so that they are added as soft definitions + # this means that they will not be used if a cycle is created union!(result.soft_definitions, generated_names_syms) + filter!(!∈(generated_names_syms), result.references) # don't reference defined functions (simulated recursive calls) end diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index 9814507a69..2bdc5754fc 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -1,3 +1,10 @@ +abstract type ChildExplorationResult end + +struct Ok <: ChildExplorationResult end +struct Cycle <: ChildExplorationResult + cycled_cells::Vector{Cell} +end + "Return a `TopologicalOrder` that lists the cells to be evaluated in a single reactive run, in topological order. Includes the given roots." function topological_order(notebook::Notebook, topology::NotebookTopology, roots::Array{Cell,1}; allow_multiple_defs=false)::TopologicalOrder entries = Cell[] @@ -5,13 +12,13 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots errable = Dict{Cell,ReactivityError}() # https://xkcd.com/2407/ - function dfs(cell::Cell) + function dfs(cell::Cell)::ChildExplorationResult if cell in exits - return + return Ok() elseif haskey(errable, cell) - return + return Ok() elseif length(entries) > 0 && entries[end] == cell - return # a cell referencing itself is legal + return Ok() # a cell referencing itself is legal elseif cell in entries currently_in = setdiff(entries, exits) cycle = currently_in[findfirst(isequal(cell), currently_in):end] @@ -20,12 +27,16 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots for cell in cycle errable[cell] = CyclicReferenceError(topology, cycle) end - return + return Cycle(cycle) end else push!(entries, cell) end + # used for cleanups of wrong cycles + current_entries_num = length(entries) + current_exits_num = length(exists) + assigners = where_assigned(notebook, topology, cell) if !allow_multiple_defs && length(assigners) > 1 for c in assigners @@ -35,15 +46,41 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots referencers = where_referenced(notebook, topology, cell) |> Iterators.reverse for c in (allow_multiple_defs ? referencers : union(assigners, referencers)) if c != cell - dfs(c) + child_result = dfs(c) + + # No cycle for this child or the cycle has no soft edges + if child_result isa Ok || cell ∉ child_result.cycled_cells + continue + end + + # Can we cleanup the cycle from here or is it caused by a parent cell ? + # if the edge to the child cell is composed of soft assigments only then we can try to "break" + # it else we bubble the result up to the parent until it is + # either out of the cycle or a soft-edge is found + if !is_soft_edge(topology, cell, c) + # Cleanup all entries & child exits + deleteat!(entries, current_entries_num+1:length(entries)) + deleteat!(exits, current_exits_num+1:length(exits)) + return child_result + end + + # Cancel exploring this child (c) + # 1. Cleanup the errables + for cycled_cell in child_result.cycled_cells + delete!(errable, cycled_cell) + end + deleteat!(entries, length(entries)) # 2. Remove the current child (c) + + continue # the cycle was created by us so we can keep exploring other childs end end push!(exits, cell) + Ok() end # we first move cells to the front if they call `import` or `using` - # we use MergeSort because it is a stable sort: leaves cells in order if they are in the same category - prelim_order_1 = sort(roots, alg=MergeSort, by=c -> cell_precedence_heuristic(topology, c)) + # we use MergeSort because it is a stable sort: leaves cells in order if they are in the same category + prelim_order_1 = sort(roots, alg=MergeSort, by=c -> cell_precedence_heuristic(topology, c)) # reversing because our search returns reversed order prelim_order_2 = Iterators.reverse(prelim_order_1) dfs.(prelim_order_2) @@ -62,7 +99,6 @@ end Base.collect(notebook_topo_order::TopologicalOrder) = union(notebook_topo_order.runnable, keys(notebook_topo_order.errable)) - function disjoint(a::Set, b::Set) !any(x in a for x in b) end @@ -79,6 +115,17 @@ function where_referenced(notebook::Notebook, topology::NotebookTopology, to_com end end +"Returns whether or not the edge between two cells is composed only of \"soft\"-definitions" +function is_soft_edge(topology::NotebookTopology, parent_cell::Cell, child_cell::Cell) + hard_definitions = union(topology.nodes[parent_cell].definitions, topology.nodes[parent_cell].funcdefs_without_signatures) + soft_definitions = topology.nodes[parent_cell].soft_definitions + + child_references = topology.nodes[child_cell].references + + disjoint(hard_definitions, child_references) && !disjoint(soft_definitions, child_references) +end + + "Return the cells that also assign to any variable or method defined by the given cell. If more than one cell is returned (besides the given cell), then all of them should throw a `MultipleDefinitionsError`. Non-recursive: only direct dependencies are found." function where_assigned(notebook::Notebook, topology::NotebookTopology, myself::Cell)::Array{Cell,1} self = topology.nodes[myself] @@ -121,7 +168,7 @@ end function cyclic_variables(topology::NotebookTopology, cycle::AbstractVector{Cell})::Set{Symbol} referenced_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].references for c in cycle)...) - assigned_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].definitions ∪ topology.nodes[c].funcdefs_without_signatures for c in cycle)...) + assigned_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].definitions ∪ topology.nodes[c].soft_definitions ∪ topology.nodes[c].funcdefs_without_signatures for c in cycle)...) referenced_during_cycle ∩ assigned_during_cycle end From 2608772660dddbecfe4ec005af57983497b13607 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 15 Nov 2021 21:35:17 +0100 Subject: [PATCH 30/71] exits --- src/analysis/topological_order.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index 2bdc5754fc..a393b042ee 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -35,7 +35,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots # used for cleanups of wrong cycles current_entries_num = length(entries) - current_exits_num = length(exists) + current_exits_num = length(exits) assigners = where_assigned(notebook, topology, cell) if !allow_multiple_defs && length(assigners) > 1 From dc753a6f975e7040f2ea53c85ea61fba144b796d Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 15 Nov 2021 22:38:03 +0100 Subject: [PATCH 31/71] add tests --- test/React.jl | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/React.jl b/test/React.jl index 2807adcb6d..f824eeef11 100644 --- a/test/React.jl +++ b/test/React.jl @@ -381,7 +381,7 @@ import Distributed WorkspaceManager.unmake_workspace((🍭, notebook)) 🍭.options.evaluation.workspace_use_distributed = false end - + @testset "Function use inv in its def but also has a method on inv" begin notebook = Notebook(Cell.([ """ @@ -405,6 +405,71 @@ import Distributed @test cell(3) |> noerror end + @testset "More challenging reactivity of extended function" begin + notebook = Notebook(Cell.([ + "Base.inv(s::String) = s", + """ + struct MyStruct + x + MyStruct(s::String) = new(inv(s)) + end + """, + "Base.inv(ms::MyStruct) = inv(ms.x)", + "MyStruct(\"hoho\")", + "a = MyStruct(\"blahblah\")", + "inv(a)", + ])) + cell(idx) = notebook.cells[idx] + fakeclient.connected_notebook = notebook + update_run!(🍭, notebook, notebook.cells) + + @test all(noerror, notebook.cells) + @test notebook.cells[end].output.body == "\"blahblah\"" + + setcode(cell(1), "Base.inv(s::String) = s * \"suffix\"") + update_run!(🍭, notebook, cell(1)) + + @test all(noerror, notebook.cells) + @test notebook.cells[end].output.body == "\"blahblahsuffixsuffix\"" # 2 invs, 1 in constructor, 1 in inv(::MyStruct) + + setcode(cell(3), "Base.inv(ms::MyStruct) = ms.x") # remove inv in inv(::MyStruct) + update_run!(🍭, notebook, cell(3)) + + @test all(noerror, notebook.cells) + @test notebook.cells[end].output.body == "\"blahblahsuffix\"" # only one inv + end + + @testset "multiple cells cycle" begin + notebook = Notebook(Cell.([ + "a = inv(1)", + "b = a", + "c = b", + "Base.inv(x::Float64) = a", + "d = Float64(c)", + ])) + fakeclient.connected_notebook = notebook + update_run!(🍭, notebook, notebook.cells) + + @test all(noerror, notebook.cells) + @test notebook.cells[end].output.body == "1.0" # a + end + + @testset "one cell in two different cycles where one is not a real cycle" begin + notebook = Notebook(Cell.([ + "x = inv(1) + z", + "y = x", + "z = y", + "Base.inv(::Float64) = y", + "inv(1.0)", + ])) + fakeclient.connected_notebook = notebook + update_run!(🍭, notebook, notebook.cells) + + @test notebook.cells[end].errored == true + @test contains(notebook.cells[1].output.body[:msg], "Cyclic") + @test contains(notebook.cells[end].output.body[:msg], "UndefVarError: y") # this is an UndefVarError and not a CyclicError + end + @testset "Reactive methods definitions" begin notebook = Notebook(Cell.([ raw""" From ce0816c55852d734a546f4f73cb68d93e52981fa Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 16 Nov 2021 00:22:01 +0100 Subject: [PATCH 32/71] cleanup --- test/React.jl | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/test/React.jl b/test/React.jl index f824eeef11..e5d3c89799 100644 --- a/test/React.jl +++ b/test/React.jl @@ -403,6 +403,12 @@ import Distributed @test cell(1) |> noerror @test cell(2) |> noerror @test cell(3) |> noerror + + # Empty and run cells to remove the Base overloads that we created, just to be sure + setcode.(notebook.cells, [""]) + update_run!(🍭, notebook, notebook.cells) + + WorkspaceManager.unmake_workspace((🍭, notebook)) end @testset "More challenging reactivity of extended function" begin @@ -437,6 +443,11 @@ import Distributed @test all(noerror, notebook.cells) @test notebook.cells[end].output.body == "\"blahblahsuffix\"" # only one inv + + # Empty and run cells to remove the Base overloads that we created, just to be sure + setcode.(notebook.cells, [""]) + update_run!(🍭, notebook, notebook.cells) + WorkspaceManager.unmake_workspace((🍭, notebook)) end @testset "multiple cells cycle" begin @@ -466,8 +477,12 @@ import Distributed update_run!(🍭, notebook, notebook.cells) @test notebook.cells[end].errored == true - @test contains(notebook.cells[1].output.body[:msg], "Cyclic") - @test contains(notebook.cells[end].output.body[:msg], "UndefVarError: y") # this is an UndefVarError and not a CyclicError + @test occursinerror("Cyclic", notebook.cells[1]) + @test occursinerror("UndefVarError: y", notebook.cells[end]) # this is an UndefVarError and not a CyclicError + + setcode.(notebook.cells, [""]) + update_run!(🍭, notebook, notebook.cells) + WorkspaceManager.unmake_workspace((🍭, notebook)) end @testset "Reactive methods definitions" begin @@ -498,6 +513,10 @@ import Distributed @test cell(1) |> noerror @test output_21 != output_22 # cell2 re-run @test contains(output_22, "sqrt(🍕)") + + setcode.(notebook.cells, [""]) + update_run!(🍭, notebook, notebook.cells) + WorkspaceManager.unmake_workspace((🍭, notebook)) end @testset "Multiple methods across cells" begin @@ -748,6 +767,14 @@ import Distributed # 25 Cell("Base.digits(x::ArgumentError) = Base.digits() + Base.isconst()") Cell("Base.isconst(x::InterruptException) = digits()") + + # 27 + Cell("f(x) = g(x-1)") + Cell("g(x) = h(x-1)") + Cell("h(x) = i(x-1)") + Cell("i(x) = j(x-1)") + Cell("j(x) = (x > 0) ? f(x-1) : :done") + Cell("f(8)") ]) fakeclient.connected_notebook = notebook @@ -824,8 +851,17 @@ import Distributed @test noerror(notebook.cells[24]) @test noerror(notebook.cells[25]) @test noerror(notebook.cells[26]) - - @assert length(notebook.cells) == 26 + + ## + @test noerror(notebook.cells[27]) + @test noerror(notebook.cells[28]) + @test noerror(notebook.cells[29]) + @test noerror(notebook.cells[30]) + @test noerror(notebook.cells[31]) + @test noerror(notebook.cells[32]) + @test notebook.cells[32].output.body == ":done" + + @assert length(notebook.cells) == 32 # Empty and run cells to remove the Base overloads that we created, just to be sure setcode.(notebook.cells, [""]) From 28eddb4b8c6b2ff4d8b6e2adc073800d3122fb4d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 16 Nov 2021 16:41:59 +0100 Subject: [PATCH 33/71] important --- src/analysis/topological_order.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index a393b042ee..6a166461d0 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -12,7 +12,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots errable = Dict{Cell,ReactivityError}() # https://xkcd.com/2407/ - function dfs(cell::Cell)::ChildExplorationResult + function bfs(cell::Cell)::ChildExplorationResult if cell in exits return Ok() elseif haskey(errable, cell) @@ -46,7 +46,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots referencers = where_referenced(notebook, topology, cell) |> Iterators.reverse for c in (allow_multiple_defs ? referencers : union(assigners, referencers)) if c != cell - child_result = dfs(c) + child_result = bfs(c) # No cycle for this child or the cycle has no soft edges if child_result isa Ok || cell ∉ child_result.cycled_cells @@ -83,7 +83,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots prelim_order_1 = sort(roots, alg=MergeSort, by=c -> cell_precedence_heuristic(topology, c)) # reversing because our search returns reversed order prelim_order_2 = Iterators.reverse(prelim_order_1) - dfs.(prelim_order_2) + bfs.(prelim_order_2) ordered = reverse(exits) TopologicalOrder(topology, setdiff(ordered, keys(errable)), errable) end From 3bb4be9a71d70bfae3db8e6ee816d2a47502d0af Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 21 Nov 2021 21:42:33 +0100 Subject: [PATCH 34/71] don't re-enter already explored cell --- src/analysis/topological_order.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index 14cc2c6e14..fac9b28c00 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -29,10 +29,12 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots end return Cycle(cycle) end - else - push!(entries, cell) + + return Ok() end + push!(entries, cell) + # used for cleanups of wrong cycles current_entries_num = length(entries) current_exits_num = length(exits) From 3e365c818233502d69b7a8c82b06ae1131f66f13 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 25 Nov 2021 22:42:11 +0100 Subject: [PATCH 35/71] lets goo --- src/analysis/topological_order.jl | 13 ++++++++----- test/React.jl | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index fac9b28c00..d9bafd6cee 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -22,7 +22,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots elseif cell in entries currently_in = setdiff(entries, exits) cycle = currently_in[findfirst(isequal(cell), currently_in):end] - + if !cycle_is_among_functions(topology, cycle) for cell in cycle errable[cell] = CyclicReferenceError(topology, cycle) @@ -33,12 +33,12 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots return Ok() end - push!(entries, cell) - # used for cleanups of wrong cycles current_entries_num = length(entries) current_exits_num = length(exits) + push!(entries, cell) + assigners = where_assigned(notebook, topology, cell) if !allow_multiple_defs && length(assigners) > 1 for c in assigners @@ -55,7 +55,7 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots continue end - # Can we cleanup the cycle from here or is it caused by a parent cell ? + # Can we cleanup the cycle from here or is it caused by a parent cell? # if the edge to the child cell is composed of soft assigments only then we can try to "break" # it else we bubble the result up to the parent until it is # either out of the cycle or a soft-edge is found @@ -71,7 +71,10 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots for cycled_cell in child_result.cycled_cells delete!(errable, cycled_cell) end - deleteat!(entries, length(entries)) # 2. Remove the current child (c) + # 2. Remove the current child (c) from the entries if it was just added + if entries[end] == c + pop!(entries) + end continue # the cycle was created by us so we can keep exploring other childs end diff --git a/test/React.jl b/test/React.jl index e5d3c89799..775d8feb82 100644 --- a/test/React.jl +++ b/test/React.jl @@ -519,6 +519,32 @@ import Distributed WorkspaceManager.unmake_workspace((🍭, notebook)) end + @testset "Two inter-twined cycles" begin + notebook = Notebook(Cell.([ + """ + struct A + x + A(x) = A(inv(x)) + end + """, + "Base.inv(::A) = A(1)", + """ + struct B + x + B(x) = B(inv(x)) + end + """, + "Base.inv(::B) = B(1)", + ])) + update_run!(🍭, notebook, notebook.cells) + + @test all(noerror, notebook.cells) + + setcode.(notebook.cells, [""]) + update_run!(🍭, notebook, notebook.cells) + WorkspaceManager.unmake_workspace((🍭, notebook)) + end + @testset "Multiple methods across cells" begin notebook = Notebook([ Cell("a(x) = 1"), From afd99c15d134d3abfc76c619acd6efe473a635a3 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 25 Nov 2021 23:26:47 +0100 Subject: [PATCH 36/71] only help when really needed --- src/evaluation/Run.jl | 7 +++++-- test/React.jl | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 2495e694be..ce29454303 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -247,15 +247,18 @@ end collect_implicit_usings(topology::NotebookTopology, cell::Cell) = ExpressionExplorer.collect_implicit_usings(topology.codes[cell].module_usings_imports) "Returns the set of macros names defined by this cell" -defined_macros(topology::NotebookTopology, cell::Cell) = filter(is_macro_identifier, topology.nodes[cell].funcdefs_without_signatures) +defined_macros(topology::NotebookTopology, cell::Cell) = defined_macros(topology.nodes[cell]) +defined_macros(node::ReactiveNode) = filter(is_macro_identifier, node.funcdefs_without_signatures) "Tells whether or not a cell can 'unlock' the resolution of other cells" function can_help_resolve_cells(topology::NotebookTopology, cell::Cell) cell_code = topology.codes[cell] cell_node = topology.nodes[cell] + macros = defined_macros(cell_node) + !isempty(cell_code.module_usings_imports.imports) || # <-- TODO(paul): check explicitely for `import Pkg: @macro` instead of any imports !isempty(cell_code.module_usings_imports.usings) || - any(is_macro_identifier, cell_node.funcdefs_without_signatures) + (!isempty(macros) && any(calls -> !disjoint(calls, macros), topology.nodes[c].macrocalls for c in topology.unresolved_cells)) end # Sorry couldn't help myself - DRAL diff --git a/test/React.jl b/test/React.jl index 775d8feb82..3542c6d927 100644 --- a/test/React.jl +++ b/test/React.jl @@ -522,9 +522,12 @@ import Distributed @testset "Two inter-twined cycles" begin notebook = Notebook(Cell.([ """ - struct A - x - A(x) = A(inv(x)) + begin + struct A + x + A(x) = A(inv(x)) + end + rand() end """, "Base.inv(::A) = A(1)", @@ -539,6 +542,13 @@ import Distributed update_run!(🍭, notebook, notebook.cells) @test all(noerror, notebook.cells) + output_1 = notebook.cells[begin].output.body + + update_run!(🍭, notebook, notebook.cells[2]) + + @test noerror(notebook.cells[1]) + @test notebook.cells[1].output.body == output_1 + @test noerror(notebook.cells[2]) setcode.(notebook.cells, [""]) update_run!(🍭, notebook, notebook.cells) From 3c6dac8523a7eab7e45dbeb8185febce94fa7f20 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 25 Nov 2021 23:42:48 +0100 Subject: [PATCH 37/71] simpler --- src/evaluation/Run.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index ce29454303..3047b9c634 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -256,9 +256,8 @@ function can_help_resolve_cells(topology::NotebookTopology, cell::Cell) cell_node = topology.nodes[cell] macros = defined_macros(cell_node) - !isempty(cell_code.module_usings_imports.imports) || # <-- TODO(paul): check explicitely for `import Pkg: @macro` instead of any imports !isempty(cell_code.module_usings_imports.usings) || - (!isempty(macros) && any(calls -> !disjoint(calls, macros), topology.nodes[c].macrocalls for c in topology.unresolved_cells)) + (!isempty(macros) && any(calls -> !disjoint(calls, macros), topology.nodes[c].macrocalls for c in topology.unresolved_cells)) end # Sorry couldn't help myself - DRAL From 0756e84f632c14601ef56d2cc7af3a511c2d221b Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 26 Nov 2021 00:05:35 +0100 Subject: [PATCH 38/71] huh too simple --- src/evaluation/Run.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 3047b9c634..c04db0202c 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -248,7 +248,7 @@ collect_implicit_usings(topology::NotebookTopology, cell::Cell) = ExpressionExpl "Returns the set of macros names defined by this cell" defined_macros(topology::NotebookTopology, cell::Cell) = defined_macros(topology.nodes[cell]) -defined_macros(node::ReactiveNode) = filter(is_macro_identifier, node.funcdefs_without_signatures) +defined_macros(node::ReactiveNode) = filter(is_macro_identifier, node.funcdefs_without_signatures) ∪ filter(is_macro_identifier, node.definitions) # macro definitions can come from imports "Tells whether or not a cell can 'unlock' the resolution of other cells" function can_help_resolve_cells(topology::NotebookTopology, cell::Cell) From 203a34541c347ddb3bb0b2e09fc6ba6b0e89afa0 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 26 Nov 2021 11:29:06 +0100 Subject: [PATCH 39/71] Comment out using Markdown and InteractiveUtils --- src/notebook/Notebook.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index cb7e13c88e..bae841d67c 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -110,8 +110,8 @@ function save_notebook(io, notebook::Notebook) println(io, "# ", PLUTO_VERSION_STR) # Anything between the version string and the first UUID delimiter will be ignored by the notebook loader. println(io, "") - println(io, "using Markdown") - println(io, "using InteractiveUtils") + println(io, "# using Markdown") + println(io, "# using InteractiveUtils") # Super Advanced Code Analysis™ to add the @bind macro to the saved file if it's used somewhere. if any(occursin("@bind", c.code) for c in notebook.cells) println(io, "") From cbc111429ea62bc2ef480d59a810bc98bb220e77 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 26 Nov 2021 11:53:21 +0100 Subject: [PATCH 40/71] Optional exclusive_flag val as second argument of toggle_notebook_exclusive --- frontend/components/Editor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index d06e07b65f..0be71ca8e3 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -470,8 +470,8 @@ export class Editor extends Component { notebook.cell_inputs[cell_id].code_folded = newFolded }) }, - toggle_notebook_exclusive: async (cell_id) => { - const newVal = !this.state.notebook.cell_inputs[cell_id].notebook_exclusive + toggle_notebook_exclusive: async (cell_id, value) => { + const newVal = value ?? !this.state.notebook.cell_inputs[cell_id].notebook_exclusive await update_notebook((notebook) => { notebook.cell_inputs[cell_id].notebook_exclusive = newVal }) From 3d4117a8c5095a454078a45005928d8e9a57d56c Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 26 Nov 2021 12:10:19 +0100 Subject: [PATCH 41/71] Automatically make certain cells exclusive --- frontend/components/Editor.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 0be71ca8e3..d769d326c4 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -32,6 +32,12 @@ import { slider_server_actions, nothing_actions } from "../common/SliderServerCl import { ProgressBar } from "./ProgressBar.js" import { IsolatedCell } from "./Cell.js" +const exclusive_regexps = [ + /^md"/, + /@benchmark/, + /@test/, + /ToC\(.*\)/ +] const default_path = "..." const DEBUG_DIFFING = false let pending_local_updates = 0 @@ -491,6 +497,13 @@ export class Editor extends Component { await update_notebook((notebook) => { for (let cell_id of cell_ids) { if (this.state.cell_inputs_local[cell_id]) { + const code = this.state.cell_inputs_local[cell_id].code + for (const re of exclusive_regexps) { + if (re.test(code)) { + this.actions.toggle_notebook_exclusive(cell_id, true) + break + } + } notebook.cell_inputs[cell_id].code = this.state.cell_inputs_local[cell_id].code } } From 04ac2fcd924bab7655355f530f9c4e35959bb9da Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 26 Nov 2021 13:02:58 +0100 Subject: [PATCH 42/71] Added some more exclusion conditions --- frontend/components/Editor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index d769d326c4..eac9a51ac4 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -36,6 +36,10 @@ const exclusive_regexps = [ /^md"/, /@benchmark/, /@test/, + /@code_warntype/, + /@code_native/, + /@code_llvm/, + /@code_lowered/, /ToC\(.*\)/ ] const default_path = "..." From e885173e206fa71da96f7709b211149405740345 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 26 Nov 2021 14:05:39 +0100 Subject: [PATCH 43/71] Added cells starting with `let` to exclusion --- frontend/components/Editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index eac9a51ac4..0d2e993901 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -40,7 +40,8 @@ const exclusive_regexps = [ /@code_native/, /@code_llvm/, /@code_lowered/, - /ToC\(.*\)/ + /ToC\(.*\)/, + /^let/ ] const default_path = "..." const DEBUG_DIFFING = false From 557b6ae60030b151d48ecd6533054788cca4f8fc Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 26 Nov 2021 18:20:53 +0100 Subject: [PATCH 44/71] ex explorer --- src/analysis/ExpressionExplorer.jl | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/analysis/ExpressionExplorer.jl b/src/analysis/ExpressionExplorer.jl index 2c76afd962..297b549b8c 100644 --- a/src/analysis/ExpressionExplorer.jl +++ b/src/analysis/ExpressionExplorer.jl @@ -531,31 +531,27 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState funcname, innersymstate = explore_funcdef!(funcroot, innerscopestate) - if length(funcname) == 1 - push!(scopestate.definedfuncs, funcname[end]) - push!(scopestate.hiddenglobals, funcname[end]) - elseif length(funcname) > 1 - push!(symstate.references, funcname[end - 1]) # reference the module of the extended function - push!(scopestate.hiddenglobals, funcname[end - 1]) - end - # Macro are called using @funcname, but defined with funcname. We need to change that in our scopestate # (The `!= 0` is for when the function named couldn't be parsed) if ex.head == :macro && length(funcname) != 0 - setdiff!(innerscopestate.hiddenglobals, funcname) funcname = Symbol[Symbol("@$(funcname[1])")] push!(innerscopestate.hiddenglobals, only(funcname)) + elseif length(funcname) == 1 + push!(scopestate.definedfuncs, funcname[end]) + push!(scopestate.hiddenglobals, funcname[end]) + elseif length(funcname) > 1 + push!(symstate.references, funcname[end - 1]) # reference the module of the extended function + push!(scopestate.hiddenglobals, funcname[end - 1]) end union!(innersymstate, explore!(Expr(:block, ex.args[2:end]...), innerscopestate)) - funcnamesig = FunctionNameSignaturePair(funcname, canonalize(funcroot)) if will_assign_global(funcname, scopestate) symstate.funcdefs[funcnamesig] = innersymstate else # The function is not defined globally. However, the function can still modify the global scope or reference globals, e.g. - + # let # function f(x) # global z = x + a From cbaadd8962a0e1e817a6c492829b3cdfa9726ac9 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sat, 27 Nov 2021 17:44:48 +0100 Subject: [PATCH 45/71] Make excluded macro regexps match the exact macro name --- frontend/components/Editor.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 0d2e993901..2e0a7f418e 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -34,12 +34,12 @@ import { IsolatedCell } from "./Cell.js" const exclusive_regexps = [ /^md"/, - /@benchmark/, - /@test/, - /@code_warntype/, - /@code_native/, - /@code_llvm/, - /@code_lowered/, + /@benchmark[( ]/, + /@test[( ]/, + /@code_warntype[( ]/, + /@code_native[( ]/, + /@code_llvm[( ]/, + /@code_lowered[( ]/, /ToC\(.*\)/, /^let/ ] From 7b7637b0b67bd4b43cdf85b0c855b7675a712d4c Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Tue, 30 Nov 2021 17:07:24 +0100 Subject: [PATCH 46/71] Added auto-hide of cells that contains md header --- frontend/components/Editor.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 6ee78bcbc9..a94a787c58 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -508,10 +508,15 @@ export class Editor extends Component { const code = this.state.cell_inputs_local[cell_id].code for (const re of exclusive_regexps) { if (re.test(code)) { - this.actions.toggle_notebook_exclusive(cell_id, true) + // this.actions.toggle_notebook_exclusive(cell_id, true) + notebook.cell_inputs[cell_id].notebook_exclusive = true break } } + // Check if it's just markdown header and eventually hide the cell + if (/^md"""\n#/.test(code)) { + notebook.cell_inputs[cell_id].code_folded = true + } notebook.cell_inputs[cell_id].code = this.state.cell_inputs_local[cell_id].code } } From dc3b248f458a4af724ae028ecd93cc9349154f1f Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Tue, 7 Dec 2021 12:21:22 +0100 Subject: [PATCH 47/71] add dictbonds filters for excluding cells --- frontend/components/Editor.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index a94a787c58..d3303753d0 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -36,6 +36,11 @@ const exclusive_regexps = [ /^md"/, /@benchmark[( ]/, /@test[( ]/, + /@addbond[( ]/, + /@bondsdict[( ]/, + /@showbonds[( ]/, + /@getbond[( ]/, + /@getbond_reactive[( ]/, /@code_warntype[( ]/, /@code_native[( ]/, /@code_llvm[( ]/, From bf152812c9db62e7047cf4db9bbe0d557177b80b Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Mon, 24 Jan 2022 15:58:50 +0100 Subject: [PATCH 48/71] Update frontend location --- src/notebook/Export.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notebook/Export.jl b/src/notebook/Export.jl index 04128b18af..bc4d260301 100644 --- a/src/notebook/Export.jl +++ b/src/notebook/Export.jl @@ -42,7 +42,7 @@ function generate_html(; # pluto_cdn_root # end - cdn_root = "https://cdn.jsdelivr.net/gh/disberd/Pluto.jl@CM6-exclusive-old/frontend/" + cdn_root = "https://cdn.jsdelivr.net/gh/disberd/Pluto.jl@main/frontend/" @debug "Using CDN for Pluto assets:" cdn_root From 51e522acf20d6faa42d95bded1405d728e06fe08 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Tue, 25 Jan 2022 11:05:54 +0100 Subject: [PATCH 49/71] re-enable html mixed parsing --- frontend/components/CellInput/mixedParsers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/CellInput/mixedParsers.js b/frontend/components/CellInput/mixedParsers.js index a5ea41eb5a..f5023d40a1 100644 --- a/frontend/components/CellInput/mixedParsers.js +++ b/frontend/components/CellInput/mixedParsers.js @@ -122,9 +122,9 @@ const juliaWrapper = parseMixed((node, input) => { const julia_andrey = (config) => { const julia = julia_andrey_original(config) - /* We need to revert this due to https://github.com/fonsp/Pluto.jl/issues/1800 - fix is WIP - julia.language.parser = julia.language.parser.configure({ wrap: juliaWrapper }) - */ + // We need to revert this due to https://github.com/fonsp/Pluto.jl/issues/1800 - fix is WIP + julia.language.parser = julia.language.parser.configure({ wrap: juliaWrapper }) + // return julia } From e03d730b09c106dfdef30eff95400e5a42e55844 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Tue, 25 Jan 2022 11:32:11 +0100 Subject: [PATCH 50/71] Make notebook-exclusive decoration theme-aware --- frontend/dark_color.css | 3 +++ frontend/editor.css | 2 +- frontend/light_color.css | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/dark_color.css b/frontend/dark_color.css index 2b686f0d37..e67d0ff5b4 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -186,6 +186,9 @@ /* docs binding */ --docs-binding-bg: #323431; + + /* notebook exclusive border color */ + --exclusive-border-color: #888; } body:not(.disable_ui):not(.more-specificity) { /* background by SVGBackgrounds.com */ diff --git a/frontend/editor.css b/frontend/editor.css index 69454c4375..b728871608 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -1017,7 +1017,7 @@ pluto-cell.notebook_exclusive::after { z-index: -1; right: -4.85px; border-radius: 8px; - border-right: 5px solid #ccc; + border-right: 5px solid var(--exclusive-border-color); } .scroll_y { diff --git a/frontend/light_color.css b/frontend/light_color.css index b28956de9b..d92f62327b 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -187,5 +187,8 @@ --index-text-color: hsl(0, 0, 60); --index-clickable-text-color: hsl(0, 0, 30); --docs-binding-bg: #8383830a; + + /* notebook exclusive border color */ + --exclusive-border-color: #ccc; } } From f659cf912773ce11d12a6185f75af9c16a827334 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 5 Aug 2022 16:38:18 +0200 Subject: [PATCH 51/71] Add function to update disabled_cells_dependency --- src/evaluation/Run.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 6349ebf813..20d8922a1e 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -711,4 +711,15 @@ function update_skipped_cells_dependency!(notebook::Notebook, topology::Notebook for cell in setdiff(notebook.cells, indirectly_skipped) cell.depends_on_skipped_cells = false end +end + +function update_disabled_cells_dependency!(notebook::Notebook, topology::NotebookTopology=notebook.topology) + disabled_cells = filter(is_disabled, notebook.cells) + indirectly_disabled = collect(topological_order(topology, disabled_cells)) + for cell in indirectly_disabled + cell.depends_on_disabled_cells = true + end + for cell in setdiff(notebook.cells, indirectly_disabled) + cell.depends_on_disabled_cells = false + end end \ No newline at end of file From 897604e112594412c1ed50189ae4067ec6e3dea8 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 5 Aug 2022 16:39:10 +0200 Subject: [PATCH 52/71] update disabled/skipped dependency inside load_notebook --- src/notebook/saving and loading.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/notebook/saving and loading.jl b/src/notebook/saving and loading.jl index abc9209577..2c3021f4be 100644 --- a/src/notebook/saving and loading.jl +++ b/src/notebook/saving and loading.jl @@ -338,6 +338,9 @@ function load_notebook(path::String; disable_writing_notebook_files::Bool=false) loaded = load_notebook_nobackup(path) # Analyze cells so that the initial save is in topological order loaded.topology = updated_topology(loaded.topology, loaded, loaded.cells) |> static_resolve_topology + # We update cell dependency on skip_as_script and disabled to avoid removing block comments on the file. See https://github.com/fonsp/Pluto.jl/issues/2182 + update_disabled_cells_dependency!(loaded) + update_skipped_cells_dependency!(loaded) update_dependency_cache!(loaded) disable_writing_notebook_files || save_notebook(loaded) From 5b590621125eb1b58a3464f566dc3a3093b1e111 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Fri, 5 Aug 2022 16:45:24 +0200 Subject: [PATCH 53/71] add a test --- test/Notebook.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Notebook.jl b/test/Notebook.jl index 24ee129a0c..f771491803 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -285,6 +285,13 @@ end @test !isdefined(m, :skipped_var) @test !isdefined(m, :dependent_var) @test m.non_skipped_var == 15 + + # Test that `load_notebook` doesn't break commented out cells + load_notebook(nb.path) + m = ingredients(nb.path) + @test !isdefined(m, :skipped_var) + @test !isdefined(m, :dependent_var) + @test m.non_skipped_var == 15 nb.cells[1].metadata["skip_as_script"] = false update_skipped_cells_dependency!(nb) @@ -294,6 +301,7 @@ end @test m.skipped_var == 10 @test m.non_skipped_var == 15 @test m.dependent_var == 11 + WorkspaceManager.unmake_workspace((🍭, nb); verbose=false) end From cd521f8a97714da7081b39163ef53db223b9161f Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sat, 6 Aug 2022 19:36:31 +0200 Subject: [PATCH 54/71] append macrocall cell_id inside macro expr --- src/runner/PlutoRunner.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index d2d7bfc2c6..1c01802949 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -253,6 +253,23 @@ module CantReturnInPluto replace_returns_with_error_in_interpolation(ex) = ex end +# This is taken basically from fix_linenumbernodes! in Parse.jl +function append_macrocellid_linenumbernodes!(ex::Expr, cell_id) + for (i, a) in enumerate(ex.args) + if a isa Expr + append_macrocellid_linenumbernodes!(a, cell_id) + elseif a isa LineNumberNode + file = string(a.file) + if endswith(file, cell_id) + # We already have the correct cell_id in this LineNumberNode + continue + end + # We append to the LineNumberNode file #@#==# + cell_id + ex.args[i] = LineNumberNode(a.line, Symbol(file * "#@#==#$(cell_id)")) + end + end +end +append_macrocellid_linenumbernodes!(::Any, cell_id) = nothing function try_macroexpand(mod, cell_uuid, expr) # Remove the precvious cached expansion, so when we error somewhere before we update, @@ -271,6 +288,7 @@ function try_macroexpand(mod, cell_uuid, expr) expanded_expr = macroexpand(mod, expr_not_toplevel)::Expr elapsed_ns = time_ns() - elapsed_ns + append_macrocellid_linenumbernodes!(expanded_expr, string(cell_uuid)) # Removes baked in references to the module this was macroexpanded in. # Fix for https://github.com/fonsp/Pluto.jl/issues/1112 expr_without_return = CantReturnInPluto.replace_returns_with_error(expanded_expr)::Expr From 2c67ae55c712aa4cc356aac982bc21b350999f5a Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sun, 7 Aug 2022 12:52:46 +0200 Subject: [PATCH 55/71] Revert "Add function to update disabled_cells_dependency" This reverts commit f659cf912773ce11d12a6185f75af9c16a827334. --- src/evaluation/Run.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 20d8922a1e..6349ebf813 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -711,15 +711,4 @@ function update_skipped_cells_dependency!(notebook::Notebook, topology::Notebook for cell in setdiff(notebook.cells, indirectly_skipped) cell.depends_on_skipped_cells = false end -end - -function update_disabled_cells_dependency!(notebook::Notebook, topology::NotebookTopology=notebook.topology) - disabled_cells = filter(is_disabled, notebook.cells) - indirectly_disabled = collect(topological_order(topology, disabled_cells)) - for cell in indirectly_disabled - cell.depends_on_disabled_cells = true - end - for cell in setdiff(notebook.cells, indirectly_disabled) - cell.depends_on_disabled_cells = false - end end \ No newline at end of file From bd887055a43dfcac29805907ccc7835b745dc1a4 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sun, 7 Aug 2022 12:52:46 +0200 Subject: [PATCH 56/71] Revert "update disabled/skipped dependency inside load_notebook" This reverts commit 897604e112594412c1ed50189ae4067ec6e3dea8. --- src/notebook/saving and loading.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/notebook/saving and loading.jl b/src/notebook/saving and loading.jl index 2c3021f4be..abc9209577 100644 --- a/src/notebook/saving and loading.jl +++ b/src/notebook/saving and loading.jl @@ -338,9 +338,6 @@ function load_notebook(path::String; disable_writing_notebook_files::Bool=false) loaded = load_notebook_nobackup(path) # Analyze cells so that the initial save is in topological order loaded.topology = updated_topology(loaded.topology, loaded, loaded.cells) |> static_resolve_topology - # We update cell dependency on skip_as_script and disabled to avoid removing block comments on the file. See https://github.com/fonsp/Pluto.jl/issues/2182 - update_disabled_cells_dependency!(loaded) - update_skipped_cells_dependency!(loaded) update_dependency_cache!(loaded) disable_writing_notebook_files || save_notebook(loaded) From 6eaa0fe824f62d2a42643aefeded7a13e996559d Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sun, 7 Aug 2022 12:52:46 +0200 Subject: [PATCH 57/71] Revert "add a test" This reverts commit 5b590621125eb1b58a3464f566dc3a3093b1e111. --- test/Notebook.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/Notebook.jl b/test/Notebook.jl index f771491803..24ee129a0c 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -285,13 +285,6 @@ end @test !isdefined(m, :skipped_var) @test !isdefined(m, :dependent_var) @test m.non_skipped_var == 15 - - # Test that `load_notebook` doesn't break commented out cells - load_notebook(nb.path) - m = ingredients(nb.path) - @test !isdefined(m, :skipped_var) - @test !isdefined(m, :dependent_var) - @test m.non_skipped_var == 15 nb.cells[1].metadata["skip_as_script"] = false update_skipped_cells_dependency!(nb) @@ -301,7 +294,6 @@ end @test m.skipped_var == 10 @test m.non_skipped_var == 15 @test m.dependent_var == 11 - WorkspaceManager.unmake_workspace((🍭, nb); verbose=false) end From 3b3e08b79814efc961f07a63a7742ab867762d36 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 10 Aug 2022 15:39:38 +0200 Subject: [PATCH 58/71] First implementation of custom unregistered/dev packages --- frontend/components/Editor.js | 1 + frontend/components/Popup.js | 32 ++++++++++++- src/notebook/Notebook.jl | 1 + src/notebook/saving and loading.jl | 4 +- src/packages/Packages.jl | 52 +++++++++++++++++---- src/packages/PkgCompat.jl | 75 +++++++++++++++++++++++++++++- src/webserver/Dynamic.jl | 18 +++++++ 7 files changed, 170 insertions(+), 13 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 3cd6ed7780..a1ec64c6e9 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -176,6 +176,7 @@ const first_true_key = (obj) => { * restart_recommended_msg: string?, * restart_required_msg: string?, * installed_versions: { [pkg_name: string]: string }, + * installed_pkgstrs: { [pkg_name: string]: string }, * terminal_outputs: { [pkg_name: string]: string }, * busy_packages: string[], * instantiated: boolean, diff --git a/frontend/components/Popup.js b/frontend/components/Popup.js index e33e128ffb..64cdf12a05 100644 --- a/frontend/components/Popup.js +++ b/frontend/components/Popup.js @@ -12,6 +12,7 @@ import { useDebouncedTruth } from "./RunArea.js" // This funny thing is a way to tell parcel to bundle these files.. // Eventually I'll write a plugin that is able to parse html`...`, but this is it for now. // https://parceljs.org/languages/javascript/#url-dependencies +export const terminal_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/terminal-outline.svg", import.meta.url) export const arrow_up_circle_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/arrow-up-circle-outline.svg", import.meta.url) export const document_text_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/document-text-outline.svg", import.meta.url) export const help_circle_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/help-circle-outline.svg", import.meta.url) @@ -146,17 +147,33 @@ const PkgPopup = ({ notebook, recent_event, clear_recent_event }) => { const [showterminal, set_showterminal] = useState(false) - const busy = recent_event != null && ((notebook.nbpkg?.busy_packages ?? []).includes(recent_event.package_name) || !(notebook.nbpkg?.instantiated ?? true)) + const package_name = recent_event?.package_name + const default_pkg_str = `add ${recent_event.package_name}` + const pkg_str = (notebook.nbpkg?.installed_pkgstrs ?? {}).hasOwnProperty(package_name) ? notebook.nbpkg?.installed_pkgstrs[package_name] : default_pkg_str + + const busy = recent_event != null && ((notebook.nbpkg?.busy_packages ?? []).includes(package_name) || !(notebook.nbpkg?.instantiated ?? true)) const debounced_busy = useDebouncedTruth(busy, 2) useEffect(() => { set_showterminal(debounced_busy) }, [debounced_busy]) - const terminal_value = notebook.nbpkg?.terminal_outputs == null ? "Loading..." : notebook.nbpkg?.terminal_outputs[recent_event?.package_name] ?? "" + const terminal_value = notebook.nbpkg?.terminal_outputs == null ? "Loading..." : notebook.nbpkg?.terminal_outputs[package_name] ?? "" const showupdate = pkg_status?.offer_update ?? false + async function try_update_pkgstr(message, _default) { + const new_pkg_str = prompt(message, _default) ?? pkg_str + const result = await pluto_actions.send( + "pkg_str", + { package_name: recent_event.package_name, pkg_str: new_pkg_str }, + { notebook_id: notebook.notebook_id } + ) + if (result.message.errored) { + try_update_pkgstr(result.message.message, new_pkg_str) + } + } + //
    ${recent_event?.package_name}
    return html` { > ${pkg_status?.hint ?? "Loading..."}
    + { + e.preventDefault() + try_update_pkgstr("Write the custom string to install the package", pkg_str) + }} + >💻 haskey(notebook.nbpkg_installed_pkgstrs_cache, x) || PkgCompat.package_exists(x), added) if !isempty(to_add) @debug to_add @@ -158,6 +158,7 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new withinteractive(false) do # 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. notebook.nbpkg_ctx = PkgCompat.clear_auto_compat_entries(notebook.nbpkg_ctx) + pkg_operations = group_pkg_operations(notebook, to_add) try for tier in [ @@ -169,10 +170,9 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new used_tier = tier try - Pkg.add(notebook.nbpkg_ctx, [ - Pkg.PackageSpec(name=p) - for p in to_add - ]; preserve=used_tier) + for (func, pkgspec_vec) in pkg_operations + func(notebook.nbpkg_ctx, pkgspec_vec; preserve = used_tier) + end break catch e @@ -451,12 +451,26 @@ function update_nbpkg(session, notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.U end end -nbpkg_cache(ctx::Union{Nothing,PkgContext}) = ctx === nothing ? Dict{String,String}() : Dict{String,String}( - x => string(PkgCompat.get_manifest_version(ctx, x)) for x in keys(PkgCompat.project(ctx).dependencies) -) +function nbpkg_cache(ctx::Union{Nothing,PkgContext}) + caches = if ctx === nothing + (Dict{String,String}(), Dict{String,String}()) + else + ks = keys(PkgCompat.project(ctx).dependencies) + versions = Dict{String,String}( + x => string(PkgCompat.get_manifest_version(ctx, x)) for x in ks + ) + pkgstrs = Dict{String,String}( + x => string(PkgCompat.get_manifest_pkgstr(ctx, x)) for x in ks + ) + (versions, pkgstrs) + end + return caches +end function update_nbpkg_cache!(notebook::Notebook) - notebook.nbpkg_installed_versions_cache = nbpkg_cache(notebook.nbpkg_ctx) + versions, pkgstrs = nbpkg_cache(notebook.nbpkg_ctx) + notebook.nbpkg_installed_versions_cache = versions + notebook.nbpkg_installed_pkgstrs_cache = pkgstrs notebook end @@ -537,3 +551,23 @@ function stoplistening(listener::IOListener) trigger(listener) end end + +function group_pkg_operations(notebook::Notebook, pkg_names) + pkgstr_cache = notebook.nbpkg_installed_pkgstrs_cache + out = [] + to_add = Pkg.PackageSpec[] + to_develop = Pkg.PackageSpec[] + for name in pkg_names + pkgspec, func = if haskey(pkgstr_cache, name) + PkgCompat.parse_pkgstr(pkgstr_cache[name]) + else + # The package has no custom string, so we create the default one + Pkg.PackageSpec(;name), Pkg.add + end + func === Pkg.add && push!(to_add, pkgspec) + func === Pkg.develop && push!(to_develop, pkgspec) + end + !isempty(to_add) && push!(out, (Pkg.add, to_add)) + !isempty(to_develop) && push!(out, (Pkg.develop, to_develop)) + return out +end \ No newline at end of file diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 000cb3a8f6..6803fafdeb 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -3,7 +3,7 @@ module PkgCompat export package_versions, package_completions import Pkg -import Pkg.Types: VersionRange +import Pkg.Types: VersionRange, PackageEntry import ..Pluto @@ -415,6 +415,79 @@ function get_manifest_version(ctx::PkgContext, package_name::AbstractString) end end +""" +Find a package in the manifest given its name, and return the equivalent string to be used inside the PkgREPL to install the same version of the package. Returns `nothing` if not found. +""" +function get_manifest_pkgstr(ctx::PkgContext, package_name::AbstractString) + if is_stdlib(package_name) + "add $package_name" + else + # Here we want the PackageEntry rather than the PackageInfo returned by `_get_manifest_entry`. This is because you lose the eventual git subdir information from the PackageInfo + entry = select(e -> e.name === package_name, values(ctx.env.manifest.deps)) + entry === nothing ? nothing : to_pkgstr(entry) + end +end + +""" +Given a `PackageEntry` from the Manifest, returns the string that would install the same package version if used inside the PkgREPL +""" +function to_pkgstr(pkg::PackageEntry) + func = "add" + name = pkg.name + if pkg.path !== nothing + # This is a developed package + func = "develop" + name = pkg.path + elseif pkg.repo.source !== nothing + # This is a package pointing to a github repo, either local or at an url + name = pkg.repo.source + pkg.repo.subdir !== nothing && (name *= ":$(pkg.repo.subdir)") + pkg.repo.rev !== nothing && (name *= "#$(pkg.repo.rev)") + else + # This is a normal registered package + pkg.version !== nothing && (name *= "@v$(pkg.version)") + end + return "$func $name" +end + +""" +Given an intended `package_name` and `pkg_str`, verify that `pkg_str` is a valid string to install the intended package using the PkgREPL +""" +function validate_pkgstr(package_name::String, pkg_str::String) + pkgspec, func = parse_pkgstr(pkg_str) + # We check that the package name is consistent with the provided command + pkgspec.name !== nothing && pkgspec.name !== package_name && error("The package name extracted from the command ($(pkgspec.name)) differs from the provided one ($(package_name)). Please correct the command") + # For the moment we skip checking the names of packages proided as URLs + + # We only support add and develop as commands for the moment + func ∉ (Pkg.add, Pkg.develop) && error("Only add and develop commands are currently supported, please modify your command") + # If nothing else failed, we just return an empty string + return pkgspec, func +end + +""" +Take a string and extract the Pkg.API function `func` and PackageSpec `pkg_spec` that would be executed by pasting `pkg_str` in the PkgREPL +""" +function parse_pkgstr(pkg_str::String) + statements = try + Pkg.REPLMode.parse(pkg_str) + catch + error("The provided command is not valid, please modify your command") + end + # We check that no more than 1 statement come out of the parsing + length(statements) > 1 && error("The provided command includes multiple statements, please provide a single statement") + command = Pkg.REPLMode.Command(statements[1]) + # We check for single package + args = command.arguments + length(args) > 1 && error("The provided commands adds multiple packages, please only provide a command for $package_name") + # We extract and pre-process the package_spec + pkgspec = args[1] + Pkg.API.handle_package_input!(pkgspec) + # We also extract the extracted function from the command + func = command.spec.api + return pkgspec, func +end + ### # WRITING COMPAT ENTRIES ### diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index e8bf3dbfcd..21dbd9ed3b 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -157,6 +157,7 @@ function notebook_to_js(notebook::Notebook) "restart_required_msg" => notebook.nbpkg_restart_required_msg, # TODO: cache this "installed_versions" => ctx === nothing ? Dict{String,String}() : notebook.nbpkg_installed_versions_cache, + "installed_pkgstrs" => ctx === nothing ? Dict{String,String}() : notebook.nbpkg_installed_pkgstrs_cache, "terminal_outputs" => notebook.nbpkg_terminal_outputs, "busy_packages" => notebook.nbpkg_busy_packages, "instantiated" => notebook.nbpkg_ctx_instantiated, @@ -512,3 +513,20 @@ responses[:pkg_update] = function response_pkg_update(🙋::ClientRequest) update_nbpkg(🙋.session, 🙋.notebook) putclientupdates!(🙋.session, 🙋.initiator, UpdateMessage(:🦆, Dict(), nothing, nothing, 🙋.initiator)) end + +responses[:pkg_str] = function response_pkg_str(🙋::ClientRequest) + require_notebook(🙋) + package_name = 🙋.body["package_name"] + pkg_str = 🙋.body["pkg_str"] + # We validate the pkgstr + to_send = try + PkgCompat.validate_pkgstr(package_name, pkg_str) + # If no error happen, we update the notebook and send the changes + 🙋.notebook.nbpkg_installed_pkgstrs_cache[package_name] = pkg_str + send_notebook_changes!(🙋 |> without_initiator) + Dict() + catch e + Dict("errored" => true, "message" => e.msg) + end + putclientupdates!(🙋.session, 🙋.initiator, UpdateMessage(:💻, to_send, nothing, nothing, 🙋.initiator)) +end From 4a083a65bfe148366c7a55f071322e409fa8327c Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 10 Aug 2022 16:06:55 +0200 Subject: [PATCH 59/71] Correctly handle jump to link in stacktraces --- frontend/components/ErrorMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js index 0ef5344eed..173243ee9b 100644 --- a/frontend/components/ErrorMessage.js +++ b/frontend/components/ErrorMessage.js @@ -4,7 +4,7 @@ import { html, useContext, useState } from "../imports/Preact.js" const StackFrameFilename = ({ frame, cell_id }) => { const sep_index = frame.file.indexOf("#==#") if (sep_index != -1) { - const frame_cell_id = frame.file.substr(sep_index + 4) + const frame_cell_id = frame.file.substr(sep_index + 4, 36) const a = html` { From 1850d6e23be6dc30a2cf29adaf06312ef9e1b326 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 10 Aug 2022 17:27:46 +0200 Subject: [PATCH 60/71] Fix compatibility with julia 1.6 --- src/packages/Packages.jl | 4 ++-- src/packages/PkgCompat.jl | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index 8b9e20c7bf..e020280a60 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -555,8 +555,8 @@ end function group_pkg_operations(notebook::Notebook, pkg_names) pkgstr_cache = notebook.nbpkg_installed_pkgstrs_cache out = [] - to_add = Pkg.PackageSpec[] - to_develop = Pkg.PackageSpec[] + to_add = Pkg.Types.PackageSpec[] + to_develop = Pkg.Types.PackageSpec[] for name in pkg_names pkgspec, func = if haskey(pkgstr_cache, name) PkgCompat.parse_pkgstr(pkgstr_cache[name]) diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 6803fafdeb..794a510a9c 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -480,9 +480,8 @@ function parse_pkgstr(pkg_str::String) # We check for single package args = command.arguments length(args) > 1 && error("The provided commands adds multiple packages, please only provide a command for $package_name") - # We extract and pre-process the package_spec + # We extract and the package_spec pkgspec = args[1] - Pkg.API.handle_package_input!(pkgspec) # We also extract the extracted function from the command func = command.spec.api return pkgspec, func From a197936e56e714830abaca3de3a526c362a0f99b Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sat, 13 Aug 2022 16:18:36 +0200 Subject: [PATCH 61/71] Attach hljs to the window --- frontend/components/Editor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 3cd6ed7780..058e0332ae 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -3,6 +3,8 @@ import * as preact from "../imports/Preact.js" import immer, { applyPatches, produceWithPatches } from "../imports/immer.js" import _ from "../imports/lodash.js" +import hljs from "../imports/highlightjs.js" + import { empty_notebook_state, set_disable_ui_css } from "../editor.js" import { create_pluto_connection } from "../common/PlutoConnection.js" import { init_feedback } from "../common/Feedback.js" @@ -1213,7 +1215,9 @@ patch: ${JSON.stringify( }) } - componentDidMount() { + componentDidMount() { + //@ts-ignore + window.hljs = hljs if (this.state.static_preview) { this.setState({ initializing: false, From 64c4d697a5589d96e44599aaaffb0026a6e6b6e0 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Mon, 15 Aug 2022 15:16:34 +0200 Subject: [PATCH 62/71] 2nd implementation of advanced PkgManager Created a custom struct with various package informations that is cached in the notebook. --- frontend/components/Editor.js | 11 ++- frontend/components/PkgStatusMark.js | 50 +++++++----- frontend/components/Popup.js | 2 +- frontend/editor.css | 12 +++ src/notebook/Notebook.jl | 7 +- src/notebook/saving and loading.jl | 5 +- src/packages/Packages.jl | 83 ++++++++++++------- src/packages/PkgCompat.jl | 115 ++++++++++++++++++--------- src/webserver/Dynamic.jl | 13 ++- 9 files changed, 198 insertions(+), 100 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index a1ec64c6e9..8d7066524f 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -169,14 +169,21 @@ const first_true_key = (obj) => { * @property {number} precedence_heuristic */ +/** + * @typedef PkgData + * @property {string} installed_version + * @property {string} pkg_str The equivalent PkgREPL string to be used to install the package + * @property {boolean} has_custom_pkg_str True if the package in question has a non-standard pkg_str, where standard is `add $(package_name)` + * @property {boolean} is_dev True if the package has en added with Pkg.develop rather than Pkg.add + */ + /** * @typedef NotebookPkgData * @type {{ * enabled: boolean, * restart_recommended_msg: string?, * restart_required_msg: string?, - * installed_versions: { [pkg_name: string]: string }, - * installed_pkgstrs: { [pkg_name: string]: string }, + * installed_packages: { [pkg_name: string]: PkgData }, * terminal_outputs: { [pkg_name: string]: string }, * busy_packages: string[], * instantiated: boolean, diff --git a/frontend/components/PkgStatusMark.js b/frontend/components/PkgStatusMark.js index 3083b42839..a7d3229968 100644 --- a/frontend/components/PkgStatusMark.js +++ b/frontend/components/PkgStatusMark.js @@ -25,6 +25,8 @@ const can_update = (installed, available) => { * @property {string?} chosen_version * @property {boolean} busy * @property {boolean} offer_update + * @property {boolean} custom_pkg_str + * @property {boolean} dev_pkg */ /** @@ -41,8 +43,11 @@ export const package_status = ({ nbpkg, package_name, available_versions, is_dis let hint_raw = "error" let hint = html`error` let offer_update = false - const chosen_version = nbpkg?.installed_versions[package_name] ?? null + const pkg_data = nbpkg?.installed_packages[package_name] + const chosen_version = pkg_data?.installed_version ?? null const busy = (nbpkg?.busy_packages ?? []).includes(package_name) || !(nbpkg?.instantiated ?? true) + const custom_pkg_str = pkg_data?.has_custom_pkg_str ?? false + const dev_pkg = pkg_data?.is_dev ?? false if (is_disable_pkg) { const f_name = package_name @@ -62,9 +67,14 @@ export const package_status = ({ nbpkg, package_name, available_versions, is_dis is installing...` } else { status = "installed" - hint_raw = `${package_name} (v${chosen_version}) is installed in the notebook.` + let custom_str = dev_pkg + ? "\nThis package has been installed with a custom command and is tracking a path for development." + : custom_pkg_str + ? "\nThis package has been installed using a custom command." + : "" + hint_raw = `${package_name} (v${chosen_version}) is installed in the notebook.${custom_str}` hint = html`
    ${package_name} v${chosen_version}
    - is installed in the notebook.` + is installed in the notebook.${custom_str ? html`${custom_str}` : ""}` offer_update = can_update(chosen_version, available_versions) } } @@ -72,9 +82,10 @@ export const package_status = ({ nbpkg, package_name, available_versions, is_dis if (available_versions != null && _.isArray(available_versions)) { if (available_versions.length === 0) { status = "not_found" - hint_raw = `The package "${package_name}" could not be found in the registry. Did you make a typo?` + hint_raw = `The package "${package_name}" could not be found in the registry. Did you make a typo?\nIf you want to install an un-registered package, use the specific button in the PkgPopup` hint = html`The package "${package_name}" could not be found in the registry. -
    Did you make a typo?
    ` +
    Did you make a typo?
    + If you want to install an un-registered package, use the button with the console icon.` } else { status = "will_be_installed" hint_raw = `${package_name} (v${_.last(available_versions)}) will be installed in the notebook when you run this cell.` @@ -84,7 +95,7 @@ export const package_status = ({ nbpkg, package_name, available_versions, is_dis } } - return { status, hint, hint_raw, available_versions, chosen_version, busy, offer_update } + return { status, hint, hint_raw, available_versions, chosen_version, busy, offer_update, custom_pkg_str, dev_pkg } } /** @@ -105,26 +116,27 @@ export const PkgStatusMark = ({ package_name, pluto_actions, notebook_id, nbpkg }) }, [package_name]) - const { status, hint_raw } = package_status({ + const { status, hint_raw, custom_pkg_str, dev_pkg } = package_status({ nbpkg: nbpkg, package_name: package_name, is_disable_pkg: false, available_versions, }) + let class_str = + status === "busy" + ? "busy" + : status === "installed" + ? "installed" + : status === "not_found" + ? "not_found" + : status === "will_be_installed" + ? "will_be_installed" + : "" + custom_pkg_str && (class_str += " custom_pkg_str") + dev_pkg && (class_str += " dev_pkg") return html` - +