From 8f854a210fc86a3c135575995e51d81e77e69062 Mon Sep 17 00:00:00 2001 From: Paolo Bignardi Date: Sat, 15 Jan 2022 01:05:17 +0100 Subject: [PATCH 001/821] Dark mode (#1661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Panagiotis Georgakopoulos Co-authored-by: Παναγιώτης Γεωργακόπουλος Co-authored-by: Fons van der Plas --- frontend/components/CellInput.js | 45 +-- frontend/components/ExportBanner.js | 2 +- frontend/components/FilePicker.js | 3 +- frontend/dark_color.css | 187 ++++++++++++ frontend/editor.css | 444 +++++++++++++++++----------- frontend/editor.html | 7 +- frontend/error.jl.html | 4 +- frontend/highlightjs.css | 72 +++++ frontend/index.css | 47 +-- frontend/index.html | 4 +- frontend/light_color.css | 182 ++++++++++++ frontend/sample.html | 4 +- frontend/treeview.css | 27 +- src/runner/PlutoRunner.jl | 17 +- 14 files changed, 805 insertions(+), 240 deletions(-) create mode 100644 frontend/dark_color.css create mode 100644 frontend/highlightjs.css create mode 100644 frontend/light_color.css diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index f931161a54..8822a8349f 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -57,29 +57,30 @@ import { cl } from "../common/ClassTable.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 😈) */ // Symbol - { tag: tags.literal, color: "#5e7ad3", fontWeight: 700 }, - { tag: tags.macroName, color: "#5668a4", fontWeight: 700 }, + { tag: tags.literal, color: "var(--cm-builtin-color)", fontWeight: 700 }, + { tag: tags.macroName, color: "var(--cm-var-color)", fontWeight: 700 }, // `nothing` I guess... Any others? - { tag: tags.standard(tags.variableName), color: "#5e7ad3", fontWeight: 700 }, - - { tag: tags.bool, color: "#5e7ad3", fontWeight: 700 }, - - { tag: tags.keyword, color: "#fc6" }, - { tag: tags.comment, color: "#e96ba8", fontStyle: "italic" }, - { tag: tags.atom, color: "#815ba4" }, - { tag: tags.number, color: "#815ba4" }, - { tag: tags.bracket, color: "#48b685" }, - { tag: tags.keyword, color: "#ef6155" }, - { tag: tags.string, color: "#da5616" }, - { tag: tags.variableName, color: "#5668a4", fontWeight: 700 }, + { tag: tags.standard(tags.variableName), color: "var(--cm-builtin-color)", fontWeight: 700 }, + + { tag: tags.bool, color: "var(--cm-builtin-color)", fontWeight: 700 }, + + { tag: tags.keyword, color: "var(--cm-keyword-color)" }, + { tag: tags.comment, color: "var(--cm-comment-color)", fontStyle: "italic" }, + { tag: tags.atom, color: "var(--cm-atom-color)" }, + { tag: tags.number, color: "var(--cm-number-color)" }, + // { tag: tags.property, color: "#48b685" }, + // { tag: tags.attribute, color: "#48b685" }, + { tag: tags.keyword, color: "var(--cm-keyword-color)" }, + { tag: tags.string, color: "var(--cm-string-color)" }, + { tag: tags.variableName, color: "var(--cm-var-color)", fontWeight: 700 }, // { tag: tags.variable2, color: "#06b6ef" }, - { tag: tags.definition(tags.variableName), color: "#f99b15" }, - { tag: tags.bracket, color: "#41323f" }, - { tag: tags.brace, color: "#41323f" }, - { 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 })), + { tag: tags.definition(tags.variableName), color: "var(--cm-def-color)" }, + { tag: tags.bracket, color: "var(--cm-bracket-color)" }, + { tag: tags.brace, color: "var(--cm-bracket-color)" }, + { tag: tags.tagName, color: "var(--cm-tag-color)" }, + { tag: tags.link, color: "var(--cm-link-color)" }, + { tag: tags.invalid, color: "var(--cm-error-color)", background: "var(--cm-error-bg-color)" }, + // ...Object.keys(tags).map((x) => ({ tag: x, color: x })), // Markdown { tag: tags.heading, color: "#081e87", fontWeight: 500 }, { tag: tags.heading1, color: "#081e87", fontWeight: 500, fontSize: "1.5em" }, @@ -348,12 +349,14 @@ export const CellInput = ({ // TODO remove me //@ts-ignore window.tags = tags + const usesDarkTheme = window.matchMedia("(prefers-color-scheme: dark)").matches const newcm = (newcm_ref.current = new EditorView({ /** Migration #0: New */ state: EditorState.create({ doc: local_code, extensions: [ + EditorView.theme({}, { dark: usesDarkTheme }), // Compartments coming from react state/props nbpkg_compartment, used_variables_compartment, diff --git a/frontend/components/ExportBanner.js b/frontend/components/ExportBanner.js index eaf9fd9702..f56f727a25 100644 --- a/frontend/components/ExportBanner.js +++ b/frontend/components/ExportBanner.js @@ -41,7 +41,7 @@ export const ExportBanner = ({ onClose, notebookfile_url, notebookexport_url, st
An .html file for your web page, or to share online.
window.print()}> -
<${Square} fill="#3D6117" /> Static PDF
+
<${Square} fill="#619b3d" /> Static PDF
A static .pdf file for print or email.
diff --git a/frontend/error.jl.html b/frontend/error.jl.html index ade78d481d..2a17418a2a 100644 --- a/frontend/error.jl.html +++ b/frontend/error.jl.html @@ -9,7 +9,9 @@ console.log("Pluto.jl, by Fons van der Plas (https://github.com/fonsp) and Mikołaj Bochenski (https://github.com/malyvsen) 🌈"); - + + + diff --git a/frontend/highlightjs.css b/frontend/highlightjs.css new file mode 100644 index 0000000000..7ae6ec2dac --- /dev/null +++ b/frontend/highlightjs.css @@ -0,0 +1,72 @@ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; +} +code.hljs { + padding: 3px 5px; +} +.hljs { + color: var(--cm-editor-text-color); +} +.hljs-comment, +.hljs-quote { + color: var(--cm-comment-color); + font-style: italic; +} +.hljs-doctag, +.hljs-formula, +.hljs-keyword { + color: var(--cm-keyword-color); +} +.hljs-deletion, +.hljs-name, +.hljs-section, +.hljs-selector-tag, +.hljs-subst { + color: var(--cm-var2-color); +} +.hljs-literal { + color: var(--cm-builtin-color); +} +.hljs-addition, +.hljs-attribute, +.hljs-meta .hljs-string, +.hljs-regexp, +.hljs-string { + color: var(--cm-string-color); +} +.hljs-attr, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-selector-pseudo, +.hljs-template-variable, +.hljs-type, +.hljs-variable { + color: var(--cm-var-color); +} +.hljs-number { + color: var(--cm-number-color); +} +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-symbol, +.hljs-title { + color: var(--cm-link-color); +} +.hljs-built_in, +.hljs-class .hljs-title, +.hljs-title.class_ { + color: var(--cm-var2-color); +} +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: 700; +} +.hljs-link { + text-decoration: underline; +} diff --git a/frontend/index.css b/frontend/index.css index e82c80d106..65448bd097 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -4,6 +4,9 @@ @import url("vollkorn.css"); @import url("juliamono.css"); +@import url("light_color.css"); +@import url("dark_color.css"); + * { box-sizing: border-box; } @@ -32,9 +35,9 @@ body.error #title { letter-spacing: 0.08em; font-weight: 500; font-family: "Vollkorn", serif; - color: hsl(0, 0%, 26%); + color: var(--pluto-output-h-color); margin: 0px; - border-bottom: solid 2px hsl(0, 0%, 88%); + border-bottom: solid 2px var(--rule-color); } #title h2 { @@ -46,6 +49,7 @@ body.error #title { margin-bottom: -0.27em; /* margin-right: -1.5em; */ margin-left: 0.1em; + filter: var(--image-filters); } body { @@ -53,7 +57,7 @@ body { position: absolute; width: 100%; min-height: 100%; - background: white; + background: var(--main-bg-color); } main { @@ -62,7 +66,7 @@ main { margin-top: 20vh; text-align: left; font-family: "Roboto Mono", monospace; - color: hsl(0, 0%, 60%); + color: var(--index-text-color); } body.error main { @@ -74,7 +78,7 @@ body.error main { } p { - color: hsl(0, 0%, 30%); + color: var(--index-clickable-text-color); } ul { @@ -89,7 +93,7 @@ li { a { color: inherit; - color: hsl(0, 0%, 30%); + color: var(--index-clickable-text-color); } /* input { @@ -118,8 +122,8 @@ pluto-filepicker .cm-editor { font-size: 0.75rem; letter-spacing: 1px; background: none; - color: #6f6f6f; - border: 2px solid #b2b2b2; + color: var(--nav-filepicker-color); + border: 2px solid var(--nav-filepicker-border-color); border-radius: 3px; border-right: none; border-top-right-radius: 0; @@ -135,10 +139,10 @@ pluto-filepicker .cm-scroller::-webkit-scrollbar { pluto-filepicker button { margin: 0px; - background: #6c8489; + background: var(--footer-filepicker-focus-color); border-radius: 3px; - border: 2px solid #6c8489; - color: white; + border: 2px solid var(--nav-filepicker-focus-color); + color: var(--ui-button-color); /* border: none; */ font-family: "Roboto Mono", monospace; font-weight: 600; @@ -152,7 +156,7 @@ pluto-filepicker button { } .cm-editor .cm-tooltip { - border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid var(--cm-editor-tooltip-border-color); box-shadow: 3px 3px 4px rgb(0 0 0 / 20%); border-radius: 4px; } @@ -178,8 +182,8 @@ pluto-filepicker button { } .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] { - color: white; - background: #16659d; + color: var(--cm-editor-li-aria-selected-color); + background: var(--cm-editor-li-aria-selected-bg-color); } .cm-editor .cm-completionIcon { @@ -198,7 +202,7 @@ pluto-filepicker button { .cm-tooltip.cm-tooltip-autocomplete { padding: 0; margin-left: -1.5em; - background: white; + background: var(--autocomplete-menu-bg-color); } .cm-tooltip-autocomplete li.file.new:before { @@ -244,7 +248,7 @@ body.nosessions ul#new ~ * { background: none; cursor: pointer; /* color: hsl(204, 86%, 35%); */ - color: black; + color: var(--ui-button-color); } #recent button > span::after { @@ -257,21 +261,24 @@ body.nosessions ul#new ~ * { } #recent li.running button > span::after { - background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle.svg"); + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle.svg); + filter: var(--image-filters); } #recent li.recent button > span::after { - background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-forward-circle-outline.svg"); + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-forward-circle-outline.svg); + filter: var(--image-filters); } #recent li.transitioning button > span::after { - background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-horizontal-outline.svg"); + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-horizontal-outline.svg); + filter: var(--image-filters); } loading-bar { height: 6px; width: 100vw; - background-color: #b1c9dd; + background-color: var(--loading-grad-color-1); position: fixed; top: 0px; display: none; diff --git a/frontend/index.html b/frontend/index.html index 5710112c8c..f0b5871cfd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9,7 +9,9 @@ console.log("Pluto.jl, by Fons van der Plas (https://github.com/fonsp) and Mikołaj Bochenski (https://github.com/malyvsen) 🌈"); - + + + diff --git a/frontend/light_color.css b/frontend/light_color.css new file mode 100644 index 0000000000..4c2707aa2c --- /dev/null +++ b/frontend/light_color.css @@ -0,0 +1,182 @@ +@media (prefers-color-scheme: light) { + :root { + --image-filters: none; + --out-of-focus-opacity: 0.25; + + /* Color scheme */ + --main-bg-color: white; + --rule-color: rgba(0, 0, 0, 0.15); + --kbd-border-color: #dfdfdf; + --header-bg-color: white; + --header-border-color: rgba(0, 0, 0, 0.1); + --ui-button-color: #2a2a2b; + --cursor-color: black; + --normal-cell: 0, 0, 0; + --code-differs: 160, 130, 28; + --error-color: 240, 168, 168; + + /*Cells*/ + --normal-cell-color: rgba(var(--normal-cell), 0.1); + --dark-normal-cell-color: rgba(var(--normal-cell), 0.2); + --selected-cell-color: rgba(40, 78, 189, 0.4); + --code-differs-cell-color: rgba(var(--code-differs), 0.68); + --error-cell-color: rgba(var(--error-color), 0.7); + --bright-error-cell-color: rgb(var(--error-color)); + --light-error-cell-color: rgba(var(--error-color), 0.05); + + /*Export styling*/ + --export-bg-color: rgb(60, 67, 101); + --export-color: rgba(255, 255, 255, 0.7); + --export-card-bg-color: rgba(255, 255, 255, 0.8); + --export-card-title-color: rgba(0, 0, 0, 0.7); + --export-card-text-color: rgba(0, 0, 0, 0.5); + --export-card-shadow-color: #00000029; + + /*Pluto output styling */ + --pluto-schema-types-color: rgba(0, 0, 0, 0.4); + --pluto-schema-types-border-color: rgba(0, 0, 0, 0.2); + --pluto-output-color: hsl(0, 0%, 25%); + --pluto-output-h-color: hsl(0, 0%, 12%); + --pluto-output-bg-color: white; + --a-underline: #00000059; + --blockquote-color: #555; + --blockquote-bg: #f2f2f2; + --admonition-title-color: white; + --admonition-bg-color: rgba(68, 149, 28, 0.2); + --admonition-focus-color: rgb(158, 200, 137); + --admonition-note-main-color: rgba(50, 115, 200, 0.2); + --admonition-note-focus-color: rgb(148, 182, 226); + --admonition-warn-main-color: rgba(162, 148, 30, 0.2); + --admonition-warn-focus-color: rgb(207, 199, 138); + --admonition-danger-main-color: rgba(200, 67, 50, 0.2); + --admonition-danger-focus-color: rgb(226, 157, 148); + --footnote-border-color: rgba(23, 115, 119, 0.15); + --table-border-color: rgba(0, 0, 0, 0.2); + --table-bg-hover-color: rgba(159, 158, 224, 0.15); + --pluto-tree-color: rgb(0 0 0 / 38%); + + /*pluto cell styling*/ + --disabled-cell-bg-color: rgba(139, 139, 139, 0.25); + --selected-cell-bg-color: rgba(40, 78, 189, 0.24); + --hover-scrollbar-color-1: rgba(0, 0, 0, 0.15); + --hover-scrollbar-color-2: rgba(0, 0, 0, 0.05); + + /* Pluto shoulders */ + --shoulder-hover-bg-color: rgba(0, 0, 0, 0.05); + + /*Top navbar styling*/ + --nav-h1-text-color: black; + --nav-filepicker-color: #6f6f6f; + --nav-filepicker-border-color: #b2b2b2; + --nav-process-status-bg-color: white; + --nav-process-status-color: var(--pluto-output-h-color); + + /*header*/ + --restart-recc-header-color: rgba(114, 192, 255, 0.56); + --restart-req-header-color: rgba(170, 41, 32, 0.56); + --dead-process-header-color: rgb(230 88 46 / 38%); + --loading-header-color: hsla(290, 10%, 80%, 0.5); + --disconnected-header-color: rgba(255, 169, 114, 0.56); + --binder-loading-header-color: hsl(51deg 64% 90% / 50%); + + /*loading bar*/ + --loading-grad-color-1: #f1dba9; + --loading-grad-color-2: #d7d7d0; + + /*saveall container*/ + --overlay-button-bg: #ffffff; + --overlay-button-border: #f3f2f2; + + /*input_context_menu*/ + --input-context-menu-border-color: rgba(0, 0, 0, 0.1); + --input-context-menu-bg-color: white; + --input-context-menu-soon-color: #55555544; + --input-context-menu-hover-bg-color: rgba(0, 0, 0, 0.1); + --input-context-menu-li-color: #6b6a6a; + + /*Pkg status*/ + --pkg-popup-bg: white; + --pkg-popup-border-color: #f0e4ee; + --pkg-popup-buttons-bg-color: white; + --black: black; + --white: white; + --pkg-terminal-bg-color: #232433; + --pkg-terminal-border-color: #c3c3c3; + + /* run area*/ + --pluto-runarea-bg-color: hsl(0, 0, 97%); + --pluto-runarea-span-color: hsl(353, 5%, 64%); + + /*drop ruler*/ + --dropruler-bg-color: rgba(0, 0, 0, 0.5); + + /* jlerror */ + --jlerror-header-color: #4f1616; + --jlerror-mark-bg-color: rgb(243 243 243); + --jlerror-a-bg-color: #f5efd9; + --jlerror-a-border-left-color: #704141; + --jlerror-mark-color: black; + + /* helpbox */ + --helpbox-bg-color: white; + --helpbox-box-shadow-color: #00000010; + --helpbox-header-bg-color: #eef1f7; + --helpbox-header-color: hsl(230, 14%, 11%); + --helpbox-notfound-header-color: rgb(139, 139, 139); + --helpbox-text-color: black; + --code-section-bg-color: whitesmoke; + --code-section-bg-color: #dbdbdb; + + /*footer*/ + --footer-color: #333333; + --footer-bg-color: #d7dcd3; + --footer-atag-color: black; + --footer-input-border-color: #818181; + --footer-filepicker-button-color: white; + --footer-filepicker-focus-color: #896c6c; + --footnote-border-color: rgba(23, 115, 119, 0.15); + + /* undo delete cell*/ + --undo-delete-box-shadow-color: #0083; + + /*codemirror hints*/ + --cm-editor-tooltip-border-color: rgba(0, 0, 0, 0.2); + --cm-editor-li-aria-selected-bg-color: #16659d; + --cm-editor-li-aria-selected-color: white; + --cm-editor-li-notexported-color: rgba(0, 0, 0, 0.5); + --code-background: hsla(46, 90%, 98%, 1); + --cm-code-differs-gutters-color: rgba(214, 172, 35, 0.2); + --cm-line-numbers-color: #8d86875e; + --cm-selection-background: hsl(214deg 100% 73% / 48%); + --cm-selection-background-blurred: hsl(214deg 0% 73% / 48%); + + /* code highlighting */ + --cm-editor-text-color: #41323f; + --cm-comment-color: #e96ba8; + --cm-atom-color: #815ba4; + --cm-number-color: #815ba4; + --cm-property-color: #48b685; + --cm-keyword-color: #ef6155; + --cm-string-color: #da5616; + --cm-var-color: #5668a4; + --cm-var2-color: #06b6ef; + --cm-builtin-color: #5e7ad3; + --cm-def-color: #f99b15; + --cm-bracket-color: #41323f; + --cm-tag-color: #ef6155; + --cm-link-color: #815ba4; + --cm-error-bg-color: #ef6155; + --cm-error-color: #f7f7f7; + --cm-matchingBracket-color: black; + --cm-matchingBracket-bg-color: #1b4bbb21; + --cm-placeholder-text-color: rgba(0, 0, 0, 0.2); + + /*autocomplete menu*/ + --autocomplete-menu-bg-color: white; + + /* Landing colors */ + --index-text-color: hsl(0, 0, 60); + --index-clickable-text-color: hsl(0, 0, 30); + --docs-binding-bg: #8383830a; + } +} diff --git a/frontend/sample.html b/frontend/sample.html index faf7ca189d..e7fb445210 100644 --- a/frontend/sample.html +++ b/frontend/sample.html @@ -9,7 +9,9 @@ console.log("Pluto.jl, by Fons van der Plas (https://github.com/fonsp) and Mikołaj Bochenski (https://github.com/malyvsen) 🌈"); - + + + diff --git a/frontend/treeview.css b/frontend/treeview.css index 1059ecedec..4a4448b8ae 100644 --- a/frontend/treeview.css +++ b/frontend/treeview.css @@ -12,7 +12,7 @@ pluto-tree-pair { font-size: 0.75rem; } pluto-tree { - color: hsl(0, 0%, 25%, 0.7); + color: var(--pluto-tree-color); white-space: pre; cursor: pointer; } @@ -49,7 +49,8 @@ pluto-tree > pluto-tree-prefix::before { bottom: -2px; opacity: 0.5; cursor: pointer; - background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-down-outline.svg"); + background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-down-outline.svg); + filter: var(--image-filters); } pluto-tree.collapsed pluto-tree > pluto-tree-prefix::before { @@ -62,7 +63,7 @@ pluto-tree.collapsed > pluto-tree-prefix::before { pluto-tree p-r > p-v { display: inline-flex; - color: hsl(0, 0%, 25%, 1); + color: var(--pluto-output-color); } pluto-tree.collapsed pluto-tree-items.Array > p-r > p-k, @@ -184,6 +185,7 @@ pluto-tree-more::before { height: 1em; width: 1em; opacity: 0.5; + filter: var(--image-filters); background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-vertical.svg"); } @@ -242,7 +244,7 @@ jlerror { } jlerror > header { - color: #330000; + color: var(--jlerror-header-color); } jlerror > header > p { margin-block-end: 0.2em; @@ -251,17 +253,18 @@ jlerror > header > p:first-child { font-weight: bold; } jlerror > section > ol > li > mark { - background: #f3f2f1; + background: var(--jlerror-mark-bg-color); border-radius: 6px; + color: var(--jlerror-mark-color); font-family: JuliaMono, monospace; font-variant-ligatures: none; } jlerror > section > ol > li > em > a { - background: #f5efd9; + background: var(--jlerror-a-bg-color); border-radius: 4px; padding: 1px 7px; text-decoration: none; - border-left: 3px solid #9a7575; + border-left: 3px solid var(--jlerror-a-border-left-color); } jlerror > section > ol > li > span { opacity: 0.8; @@ -278,15 +281,15 @@ table.pluto-table td { } table.pluto-table .schema-types { - color: rgba(0, 0, 0, 0.4); + color: var(--pluto-schema-types-color); font-family: "JuliaMono", monospace; font-size: 0.75rem; opacity: 0; } table.pluto-table .schema-types th { - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - background-color: white; + border-bottom: 1px solid var(--pluto-schema-types-border-color); + background-color: var(--main-bg-color); height: 2rem; } @@ -306,7 +309,7 @@ table.pluto-table .schema-types th:first-child { table.pluto-table .schema-names th, table.pluto-table .schema-types th:first-child { - background-color: white; + background-color: var(--main-bg-color); position: sticky; top: calc(0.25rem - var(--pluto-cell-spacing)); height: 2rem; @@ -318,7 +321,7 @@ table.pluto-table thead:hover .schema-names th { } table.pluto-table tbody th:first-child { - background-color: white; + background-color: var(--main-bg-color); position: sticky; left: -10px; /* padding-left of pluto-output*/ } diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index 688d6d5855..57534f7f63 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -912,21 +912,8 @@ end function format_output(binding::Base.Docs.Binding; context=default_iocontext) try (""" -
- $(binding.var) +
+ $(binding.var) $(repr(MIME"text/html"(), Base.Docs.doc(binding)))
""", MIME"text/html"()) From 404bfcea6e8e66a89b8c43ca0d780b972cf5acc2 Mon Sep 17 00:00:00 2001 From: Rik Huijzer Date: Mon, 17 Jan 2022 14:14:50 +0100 Subject: [PATCH 002/821] Use newer keyword arg syntax (#1823) --- src/evaluation/Run.jl | 6 +++--- src/notebook/Export.jl | 2 +- src/packages/PkgCompat.jl | 2 +- src/webserver/Dynamic.jl | 4 ++-- src/webserver/SessionActions.jl | 4 ++-- src/webserver/Static.jl | 4 ++-- test/Bonds.jl | 5 +---- test/Configuration.jl | 4 ++-- test/webserver.jl | 4 ++-- 9 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 5850a495ce..aa3cd8a794 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -92,7 +92,7 @@ function run_reactive!(session::ServerSession, notebook::Notebook, old_topology: to_delete_funcs = union!(to_delete_funcs, defined_functions(new_topology, new_errable)...) to_reimport = union!(Set{Expr}(), map(c -> new_topology.codes[c].module_usings_imports.usings, setdiff(notebook.cells, to_run))...) - deletion_hook((session, notebook), old_workspace_name, nothing, to_delete_vars, to_delete_funcs, to_reimport; to_run = to_run) # `deletion_hook` defaults to `WorkspaceManager.move_vars` + deletion_hook((session, notebook), old_workspace_name, nothing, to_delete_vars, to_delete_funcs, to_reimport; to_run) # `deletion_hook` defaults to `WorkspaceManager.move_vars` delete!.([notebook.bonds], to_delete_vars) @@ -136,7 +136,7 @@ function run_reactive!(session::ServerSession, notebook::Notebook, old_topology: update_dependency_cache!(notebook) save_notebook(session, notebook) - return run_reactive!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook = deletion_hook, user_requested_run = user_requested_run, already_in_run = true, already_run = to_run[1:i]) + return run_reactive!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook, user_requested_run, already_in_run = true, already_run = to_run[1:i]) elseif !isempty(implicit_usings) new_soft_definitions = WorkspaceManager.collect_soft_definitions((session, notebook), implicit_usings) notebook.topology = new_new_topology = with_new_soft_definitions(new_topology, cell, new_soft_definitions) @@ -145,7 +145,7 @@ function run_reactive!(session::ServerSession, notebook::Notebook, old_topology: update_dependency_cache!(notebook) save_notebook(session, notebook) - return run_reactive!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook = deletion_hook, user_requested_run = user_requested_run, already_in_run = true, already_run = to_run[1:i]) + return run_reactive!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook, user_requested_run, already_in_run = true, already_run = to_run[1:i]) end end diff --git a/src/notebook/Export.jl b/src/notebook/Export.jl index 6770d5a37f..5fb1a2c7fe 100644 --- a/src/notebook/Export.jl +++ b/src/notebook/Export.jl @@ -90,5 +90,5 @@ function generate_html(notebook; kwargs...)::String end # We don't set `notebook_id_js` because this is generated by the server, the option is only there for funky setups. - generate_html(; statefile_js=statefile_js, notebookfile_js=notebookfile_js, kwargs...) + generate_html(; statefile_js, notebookfile_js, kwargs...) end diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 414f8170d9..6fce2c0593 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -192,7 +192,7 @@ 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) + Pkg.instantiate(ctx; update_registry) else Pkg.instantiate(ctx) end diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index 7860f35033..e82cb17134 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -297,9 +297,9 @@ responses[:update_notebook] = function response_update_notebook(🙋::ClientRequ (mutator, matches, rest) = trigger_resolver(effects_of_changed_state, patch.path) current_changes = if isempty(rest) && applicable(mutator, matches...) - mutator(matches...; request=🙋, patch=patch) + mutator(matches...; request=🙋, patch) else - mutator(matches..., rest...; request=🙋, patch=patch) + mutator(matches..., rest...; request=🙋, patch) end push!(changes, current_changes...) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index eb02ba29f1..94cba17888 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -52,9 +52,9 @@ function open(session::ServerSession, path::AbstractString; run_async=true, comp c.queued = session.options.evaluation.run_notebook_on_load end - update_save_run!(session, nb, nb.cells; run_async=run_async, prerender_text=true) + update_save_run!(session, nb, nb.cells; run_async, prerender_text=true) - add(session, nb; run_async=run_async) + add(session, nb; run_async) try_event_call(session, OpenNotebookEvent(nb)) nb end diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index f9f2057fa5..a42fd080af 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -170,10 +170,10 @@ function http_router_for(session::ServerSession) function try_launch_notebook_response(action::Function, path_or_url::AbstractString; title="", advice="", home_url="./", as_redirect=true, action_kwargs...) try nb = action(session, path_or_url; action_kwargs...) - notebook_response(nb; home_url=home_url, as_redirect=as_redirect) + notebook_response(nb; home_url, as_redirect) catch e if e isa SessionActions.NotebookIsRunningException - notebook_response(e.notebook; home_url=home_url, as_redirect=as_redirect) + notebook_response(e.notebook; home_url, as_redirect) else error_response(500, title, advice, sprint(showerror, e, stacktrace(catch_backtrace()))) end diff --git a/test/Bonds.jl b/test/Bonds.jl index cb698a5bf3..f934ec903e 100644 --- a/test/Bonds.jl +++ b/test/Bonds.jl @@ -183,10 +183,7 @@ import Distributed function set_bond_value(name, value, is_first_value=false) notebook.bonds[name] = Dict("value" => value) - Pluto.set_bond_values_reactive(; - session=🍭, - notebook=notebook, - bound_sym_names=[name], + Pluto.set_bond_values_reactive(; session=🍭, notebook, bound_sym_names=[name], is_first_values=[is_first_value], run_async=false, ) diff --git a/test/Configuration.jl b/test/Configuration.jl index 9cd5bd739b..720427fbfe 100644 --- a/test/Configuration.jl +++ b/test/Configuration.jl @@ -46,8 +46,8 @@ end @testset "Authentication" begin port = 1238 - options = Pluto.Configuration.from_flat_kwargs(; port=port, launch_browser=false, workspace_use_distributed=false) - 🍭 = Pluto.ServerSession(; options=options) + options = Pluto.Configuration.from_flat_kwargs(; port, launch_browser=false, workspace_use_distributed=false) + 🍭 = Pluto.ServerSession(; options) fakeclient = ClientSession(:fake, nothing) 🍭.connected_clients[fakeclient.id] = fakeclient host = 🍭.options.server.host diff --git a/test/webserver.jl b/test/webserver.jl index 6d8a26d2af..d63f5e9b68 100644 --- a/test/webserver.jl +++ b/test/webserver.jl @@ -19,8 +19,8 @@ using Pluto.WorkspaceManager: WorkspaceManager, poll # without notebook at startup - options = Pluto.Configuration.from_flat_kwargs(; port=port, launch_browser=false, workspace_use_distributed=false, require_secret_for_access=false, require_secret_for_open_links=false) - 🍭 = Pluto.ServerSession(; options=options) + options = Pluto.Configuration.from_flat_kwargs(; port, launch_browser=false, workspace_use_distributed=false, require_secret_for_access=false, require_secret_for_open_links=false) + 🍭 = Pluto.ServerSession(; options) server_task = @async Pluto.run(🍭) @test poll(5) do server_running() From ffb38d24521ea7264a019d0a35bf69706be4106c Mon Sep 17 00:00:00 2001 From: Zihua Wu Date: Mon, 17 Jan 2022 22:42:36 +0800 Subject: [PATCH 003/821] Add support for JULIA_NUM_THREADS=auto (fixes #1813) (#1814) --- src/Configuration.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Configuration.jl b/src/Configuration.jl index b3db3ebabb..bbc863bc9f 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -143,7 +143,14 @@ end function default_number_of_threads() env_value = get(ENV, "JULIA_NUM_THREADS", "") - all(isspace, env_value) ? roughly_the_number_of_physical_cpu_cores() : parse(Int, env_value) + + all(isspace, env_value) ? + roughly_the_number_of_physical_cpu_cores() : + try + parse(Int, env_value) + catch + env_value + end end function roughly_the_number_of_physical_cpu_cores() From 665d99a885603bd0066b2443647805eb88fd20a3 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 17 Jan 2022 21:27:47 +0100 Subject: [PATCH 004/821] Logging in notebook (#437) Co-authored-by: Panagiotis Georgakopoulos Co-authored-by: Paul Co-authored-by: Michiel Dral --- frontend/components/Cell.js | 24 +- frontend/components/CellInput.js | 44 +- .../components/CellInput/highlight_line.js | 54 + frontend/components/Editor.js | 22 +- frontend/components/Logs.js | 162 +++ frontend/components/Notebook.js | 14 + frontend/components/TreeView.js | 2 +- frontend/dark_color.css | 21 +- frontend/editor.css | 252 +++- frontend/light_color.css | 21 +- sample/logging.jl | 96 ++ sample/test_logging.jl | 1179 +++++++++++++++++ src/Pluto.jl | 1 + src/evaluation/Run.jl | 60 +- src/evaluation/Throttled.jl | 49 + src/evaluation/WorkspaceManager.jl | 38 +- src/notebook/Cell.jl | 2 + src/runner/PlutoRunner.jl | 26 +- src/webserver/AppendonlyMarkers.jl | 178 +++ src/webserver/Dynamic.jl | 6 + src/webserver/Firebasey.jl | 272 ++-- 21 files changed, 2276 insertions(+), 247 deletions(-) create mode 100644 frontend/components/CellInput/highlight_line.js create mode 100644 frontend/components/Logs.js create mode 100644 sample/logging.jl create mode 100644 sample/test_logging.jl create mode 100644 src/evaluation/Throttled.jl create mode 100644 src/webserver/AppendonlyMarkers.jl diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 7305cb0dc5..fb593ad253 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -1,7 +1,9 @@ +import _ from "../imports/lodash.js" import { html, useState, useEffect, useMemo, useRef, useContext, useLayoutEffect } from "../imports/Preact.js" import { CellOutput } from "./CellOutput.js" import { CellInput } from "./CellInput.js" +import { Logs } from "./Logs.js" import { RunArea, useDebouncedTruth } from "./RunArea.js" import { cl } from "../common/ClassTable.js" import { PlutoContext } from "../common/PlutoContext.js" @@ -41,7 +43,7 @@ const useCellApi = (node_ref, published_object_keys, pluto_actions) => { * */ export const Cell = ({ cell_input: { cell_id, code, code_folded, running_disabled }, - cell_result: { queued, running, runtime, errored, output, published_object_keys, depends_on_disabled_cells }, + cell_result: { queued, running, runtime, errored, output, logs, published_object_keys, depends_on_disabled_cells }, cell_dependencies, cell_input_local, notebook_id, @@ -64,6 +66,17 @@ export const Cell = ({ const variables = Object.keys(notebook?.cell_dependencies?.[cell_id]?.downstream_cells_map || {}) // cm_forced_focus is null, except when a line needs to be highlighted because it is part of a stack trace const [cm_forced_focus, set_cm_forced_focus] = useState(null) + const [cm_highlighted_line, set_cm_highlighted_line] = useState(null) + const [show_logs, set_show_logs] = useState(true) + + const any_logs = useMemo(() => !_.isEmpty(logs), [logs]) + + useEffect(() => { + if (!any_logs) { + set_show_logs(true) + } + }, [any_logs]) + useEffect(() => { const focusListener = (e) => { if (e.detail.cell_id === cell_id) { @@ -107,6 +120,7 @@ export const Cell = ({ // during the initial page load, force_hide_input === true, so that cell outputs render fast, and codemirrors are loaded after let show_input = !force_hide_input && (errored || class_code_differs || !class_code_folded) + const [line_heights, set_line_heights] = useState([15]) const node_ref = useRef(null) const disable_input_ref = useRef(disable_input) @@ -131,6 +145,7 @@ export const Cell = ({ running_disabled: running_disabled, depends_on_disabled_cells: depends_on_disabled_cells, show_input: show_input, + shrunk: Object.values(logs).length > 0, hooked_up: output?.has_pluto_hook_features ?? false, })} id=${cell_id} @@ -197,11 +212,18 @@ export const Cell = ({ }} on_update_doc_query=${on_update_doc_query} on_focus_neighbor=${on_focus_neighbor} + on_line_heights=${set_line_heights} nbpkg=${nbpkg} cell_id=${cell_id} notebook_id=${notebook_id} running_disabled=${running_disabled} + any_logs=${any_logs} + show_logs=${show_logs} + set_show_logs=${set_show_logs} + cm_highlighted_line=${cm_highlighted_line} + set_cm_highlighted_line=${set_cm_highlighted_line} /> + ${show_logs ? html`<${Logs} logs=${Object.values(logs)} line_heights=${line_heights} set_cm_highlighted_line=${set_cm_highlighted_line} />` : null} <${RunArea} cell_id=${cell_id} running_disabled=${running_disabled} diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 8822a8349f..f626c535bd 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -1,4 +1,5 @@ import { html, useState, useEffect, useLayoutEffect, useRef, useContext, useMemo } from "../imports/Preact.js" +import observablehq_for_myself from "../common/SetupCellEnvironment.js" import _ from "../imports/lodash.js" import { utf8index_to_ut16index } from "../common/UnicodeTools.js" @@ -53,6 +54,7 @@ import { cell_movement_plugin, prevent_holding_a_key_from_doing_things_across_ce import { pluto_paste_plugin } from "./CellInput/pluto_paste_plugin.js" import { bracketMatching } from "./CellInput/block_matcher_plugin.js" import { cl } from "../common/ClassTable.js" +import { HighlightLineFacet, highlightLinePlugin } from "./CellInput/highlight_line.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 😈) */ @@ -157,11 +159,16 @@ export const CellInput = ({ on_change, on_update_doc_query, on_focus_neighbor, + on_line_heights, nbpkg, cell_id, notebook_id, running_disabled, cell_dependencies, + any_logs, + show_logs, + set_show_logs, + cm_highlighted_line, variables_in_all_notebook, }) => { let pluto_actions = useContext(PlutoContext) @@ -174,6 +181,7 @@ export const CellInput = ({ let nbpkg_compartment = useCompartment(newcm_ref, NotebookpackagesFacet.of(nbpkg)) let used_variables_compartment = useCompartment(newcm_ref, UsedVariablesFacet.of(variables_in_all_notebook)) + let highlighted_line_compartment = useCompartment(newcm_ref, HighlightLineFacet.of(cm_highlighted_line)) let editable_compartment = useCompartment(newcm_ref, EditorState.readOnly.of(disable_input)) let on_change_compartment = useCompartment( @@ -359,6 +367,7 @@ export const CellInput = ({ EditorView.theme({}, { dark: usesDarkTheme }), // Compartments coming from react state/props nbpkg_compartment, + highlighted_line_compartment, used_variables_compartment, editable_compartment, @@ -479,6 +488,21 @@ export const CellInput = ({ view.focus() }) } + + // @ts-ignore + const lines_wrapper_dom_node = dom_node_ref.current.querySelector("div.cm-content") + const lines_wrapper_resize_observer = new ResizeObserver(() => { + const line_nodes = lines_wrapper_dom_node.children + const tops = _.map(line_nodes, (c) => c.offsetTop) + const diffs = tops.slice(1).map((y, i) => y - tops[i]) + const heights = [...diffs, 15] + on_line_heights(heights) + }) + + lines_wrapper_resize_observer.observe(lines_wrapper_dom_node) + return () => { + lines_wrapper_resize_observer.unobserve(lines_wrapper_dom_node) + } }, []) // Effect to apply "remote_code" to the cell when it changes... @@ -542,12 +566,20 @@ 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} + any_logs=${any_logs} + show_logs=${show_logs} + set_show_logs=${set_show_logs} + /> ` } -const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => { +const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, any_logs, show_logs, set_show_logs }) => { const timeout = useRef(null) let pluto_actions = useContext(PlutoContext) const [open, setOpen] = useState(false) @@ -564,6 +596,7 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled }) => // we also 'run' the cell if it is disabled, this will make the backend propage the disabled state to dependent cells await run_cell() } + const toggle_logs = () => set_show_logs(!show_logs) return html` From 9172f8b31e2125ce83b58058bfd530dfddf0e230 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 19 Jan 2022 18:57:11 +0100 Subject: [PATCH 013/821] =?UTF-8?q?=F0=9F=90=8C=20Fix=20#787?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/webserver/WebServer.jl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index be49b83689..4a6e2a2551 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -244,11 +244,20 @@ function run(session::ServerSession, pluto_router) end end end - + + server_running() = try + HTTP.get("http://$(hostIP):$(port)/ping"; status_exception=false, retry=false, connect_timeout=10, readtimeout=10).status == 200 + catch + false + end + address = pretty_address(session, hostIP, port) - println() - if session.options.server.launch_browser && open_in_default_browser(address) + if session.options.server.launch_browser && ( + # Wait for the server to start up before opening the browser. We have a 5 second grace period for allowing the connection, and then 10 seconds for the server to write data. + WorkspaceManager.poll(server_running, 5.0, 1.0) && + open_in_default_browser(address) + ) println("Opening $address in your default browser... ~ have fun!") else println("Go to $address in your browser to start writing ~ have fun!") From 82907a28f4dfb118eb6da5dfc480fabce0d75c13 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 19 Jan 2022 18:58:25 +0100 Subject: [PATCH 014/821] Updated Pkg compat for update_registries --- src/packages/Packages.jl | 2 +- src/packages/PkgCompat.jl | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index 60735abd26..a785deddba 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -200,7 +200,7 @@ function sync_nbpkg_core(notebook::Notebook; on_terminal_output::Function=((args pushfirst!(LOAD_PATH, env_dir) # update registries if this is the first time - PkgCompat.update_registries(notebook.nbpkg_ctx) + PkgCompat.update_registries(; force=false) # instantiate without forcing registry update PkgCompat.instantiate(notebook.nbpkg_ctx; update_registry=false) diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 6fce2c0593..8fcc3b54eb 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -175,17 +175,18 @@ function refresh_registry_cache() _parsed_registries[] = _get_registries() end -const _updated_registries_compat = Ref(false) +# ⚠️✅ Internal API with fallback +const _updated_registries_compat = @static if isdefined(Pkg, :UPDATED_REGISTRY_THIS_SESSION) && Pkg.UPDATED_REGISTRY_THIS_SESSION isa Ref{Bool} + Pkg.UPDATED_REGISTRY_THIS_SESSION +else + Ref(false) +end -# ⚠️✅ 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 +# ✅ Public API +function update_registries(; force::Bool=false) + if force || !_updated_registries_compat[] + _updated_registries_compat[] = true + Pkg.Registry.update() end end From ea5e04fcceb0351df3bac8a87faf73f2bf0d912c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 19 Jan 2022 19:03:10 +0100 Subject: [PATCH 015/821] Pkg compat tweak --- src/packages/PkgCompat.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 8fcc3b54eb..22aa74deb1 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -185,8 +185,12 @@ end # ✅ Public API function update_registries(; force::Bool=false) if force || !_updated_registries_compat[] - _updated_registries_compat[] = true Pkg.Registry.update() + try + refresh_registry_cache() + catch + end + _updated_registries_compat[] = true end end From 42c09318151aa635c0aaf4906b506144ac47f415 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 19 Jan 2022 20:01:17 +0100 Subject: [PATCH 016/821] Update registry before resolve --- src/packages/Packages.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index a785deddba..8f80168b10 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -360,6 +360,7 @@ function update_nbpkg_core(notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.UPLEV 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 + PkgCompat.update_registries(;force=false) try Pkg.resolve(notebook.nbpkg_ctx) catch e From 78b2428dc38d7b77edc06a40f8aa7ac37c0910c8 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 19 Jan 2022 21:30:51 +0100 Subject: [PATCH 017/821] whitespace --- src/evaluation/WorkspaceManager.jl | 16 +++++++--------- src/packages/PkgCompat.jl | 8 ++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index c2313126ae..fc57e0ca2f 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -22,12 +22,12 @@ Base.@kwdef mutable struct Workspace end "These expressions get evaluated whenever a new `Workspace` process is created." -const process_preamble = [ - :(ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 0)), - :(include($(project_relative_path("src", "runner", "Loader.jl")))), - :(ENV["GKSwstype"] = "nul"), - :(ENV["JULIA_REVISE_WORKER_ONLY"] = "1"), -] +const process_preamble = quote + ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 0) + include($(project_relative_path("src", "runner", "Loader.jl"))) + ENV["GKSwstype"] = "nul" + ENV["JULIA_REVISE_WORKER_ONLY"] = "1" +end const workspaces = Dict{UUID,Promise{Workspace}}() @@ -217,9 +217,7 @@ function create_workspaceprocess(;compiler_options=CompilerOptions())::Integer $(Distributed_expr).addprocs(1; exeflags=$(_convert_to_flags(compiler_options))) |> first end) - for expr in process_preamble - Distributed.remotecall_eval(Main, [pid], expr) - end + Distributed.remotecall_eval(Main, [pid], process_preamble) # so that we NEVER break the workspace with an interrupt 🤕 @async Distributed.remotecall_eval(Main, [pid], diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 22aa74deb1..9d5d812385 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -194,6 +194,11 @@ function update_registries(; force::Bool=false) end end + +### +# Instantiate +### + # ⚠️✅ Internal API with fallback function instantiate(ctx; update_registry::Bool) @static if hasmethod(Pkg.instantiate, Tuple{}, (:update_registry,)) @@ -204,6 +209,9 @@ function instantiate(ctx; update_registry::Bool) end +### +# Standard Libraries +### # (⚠️ Internal API with fallback) _stdlibs() = try From 7a0019135d187eadd6e5c79a88c2cd11733ec584 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Wed, 19 Jan 2022 23:58:30 +0200 Subject: [PATCH 018/821] Quickfix weird bug with empty logs on empty notebook --- frontend/components/Notebook.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index 71d52cd01b..bfc0caa650 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -25,13 +25,13 @@ let CellMemo = ({ }) => { const selected_cells_diffable_primitive = (selected_cells || []).join("") const { body, last_run_timestamp, mime, persist_js_state, rootassignee } = cell_result?.output || {} - const logs = cell_result?.logs ?? [] + const cell_result_nonemptylogs = useMemo(() => ({ ...cell_result, logs: cell_result?.logs ?? {} }), [cell_result]) const { queued, running, runtime, errored, depends_on_disabled_cells } = cell_result || {} const { cell_id, code, code_folded, running_disabled } = cell_input || {} return useMemo(() => { return html` <${Cell} - cell_result=${cell_result} + cell_result=${cell_result_nonemptylogs} cell_dependencies=${cell_dependencies} cell_input=${cell_input} cell_input_local=${cell_input_local} @@ -63,7 +63,7 @@ let CellMemo = ({ mime, persist_js_state, rootassignee, - logs, + cell_result_nonemptylogs, code, code_folded, cell_input_local, From 873276b2e8f8b94191baf53666d0d0e78a0d9761 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Thu, 20 Jan 2022 00:18:25 +0200 Subject: [PATCH 019/821] Cleanup previous fix --- frontend/components/Notebook.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index bfc0caa650..2c6677ec48 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -25,13 +25,12 @@ let CellMemo = ({ }) => { const selected_cells_diffable_primitive = (selected_cells || []).join("") const { body, last_run_timestamp, mime, persist_js_state, rootassignee } = cell_result?.output || {} - const cell_result_nonemptylogs = useMemo(() => ({ ...cell_result, logs: cell_result?.logs ?? {} }), [cell_result]) - const { queued, running, runtime, errored, depends_on_disabled_cells } = cell_result || {} + const { queued, running, runtime, errored, depends_on_disabled_cells, logs } = cell_result || {} const { cell_id, code, code_folded, running_disabled } = cell_input || {} return useMemo(() => { return html` <${Cell} - cell_result=${cell_result_nonemptylogs} + cell_result=${cell_result} cell_dependencies=${cell_dependencies} cell_input=${cell_input} cell_input_local=${cell_input_local} @@ -63,7 +62,7 @@ let CellMemo = ({ mime, persist_js_state, rootassignee, - cell_result_nonemptylogs, + logs, code, code_folded, cell_input_local, From 4e07d336c10d5b42dbb657af114d372831daf674 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 20 Jan 2022 00:14:23 +0100 Subject: [PATCH 020/821] color tweaks --- frontend/dark_color.css | 2 +- sample/JavaScript.jl | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/dark_color.css b/frontend/dark_color.css index 66ca2fbbbf..7acf203f34 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -52,7 +52,7 @@ --jl-debug-accent-color: hsl(283deg 59% 69%); --table-border-color: rgba(255, 255, 255, 0.2); --table-bg-hover-color: rgba(193, 192, 235, 0.15); - --pluto-tree-color: rgba(191, 191, 191, 0.507); + --pluto-tree-color: rgb(209 207 207 / 61%); /*pluto cell styling*/ --disabled-cell-bg-color: rgba(139, 139, 139, 0.25); diff --git a/sample/JavaScript.jl b/sample/JavaScript.jl index cfa36d2273..343e240778 100644 --- a/sample/JavaScript.jl +++ b/sample/JavaScript.jl @@ -136,6 +136,7 @@ currentScript.previousElementSibling.innerText = "Hello from JavaScript!" .blue-background { padding: .5em; background: lightblue; + color: black; } @@ -284,6 +285,7 @@ my_data = [ .join("text") .attr("x", d => d.coordinate[0]) .attr("y", d => d.coordinate[1]) + .style("fill", "red") .text(d => d.name) return svg @@ -763,7 +765,7 @@ details(md""" - ``` - """, "Show with syntax highlighting") - -# ╔═╡ d12b98df-8c3f-4620-ba3c-2f3dadac521b -details(md""" - ```htmlmixed - - ``` - """, "Show with syntax highlighting") - -# ╔═╡ 94561cb1-2325-49b6-8b22-943923fdd91b -details(md""" - ```htmlmixed - - - - ``` - """, "Show with syntax highlighting") - -# ╔═╡ b0c246ed-b871-461b-9541-280e49b49136 -details(md""" -```htmlmixed -
- - - -
-``` -""", "Show with syntax highlighting") - -# ╔═╡ d121e085-c69b-490f-b315-c11a9abd57a6 -details(md""" - ```htmlmixed - - ``` - """, "Show with syntax highlighting") - -# ╔═╡ d4bdc4fe-2af8-402f-950f-2afaf77c62de -details(md""" - ```htmlmixed - - ``` - """, "Show with syntax highlighting") - -# ╔═╡ e910982c-8508-4729-a75d-8b5b847918b6 -details(md""" -```htmlmixed - - - -``` -""", "Show with syntax highlighting") - -# ╔═╡ 05d28aa2-9622-4e62-ab39-ca4c7dde6eb4 -details(md""" - ```htmlmixed - - ``` - """, "Show with syntax highlighting") - # ╔═╡ cc318a19-316f-4fd9-8436-fb1d42f888a3 demo_img = let url = "https://user-images.githubusercontent.com/6933510/116753174-fa40ab80-aa06-11eb-94d7-88f4171970b2.jpeg" @@ -1244,7 +1016,6 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╠═571613a1-6b4b-496d-9a68-aac3f6a83a4b # ╟─168e13f7-2ff2-4207-be56-e57755041d36 # ╠═28ae1424-67dc-4b76-a172-1185cc76cb59 -# ╟─93abe0dc-f041-475f-9ef7-d8ee4408414b # ╟─ea39c63f-7466-4015-a66c-08bd9c716343 # ╟─8b082f9a-073e-4112-9422-4087850fc89e # ╟─d70a3a02-ef3a-450f-bf5a-4a0d7f6262e2 @@ -1270,17 +1041,14 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╟─7afbf8ef-e91c-45b9-bf22-24201cbb4828 # ╠═b226da72-9512-4d14-8582-2f7787c25028 # ╠═a6fd1f7b-a8fc-420d-a8bb-9f549842ad3e -# ╟─d12b98df-8c3f-4620-ba3c-2f3dadac521b # ╟─965f3660-6ec4-4a86-a2a2-c167dbe9315f # ╠═01ce31a9-6856-4ee7-8bce-7ce635167457 # ╠═00d97588-d591-4dad-9f7d-223c237deefd # ╠═21f57310-9ceb-423c-a9ce-5beb1060a5a3 -# ╟─94561cb1-2325-49b6-8b22-943923fdd91b # ╟─7d9d6c28-131a-4b2a-84f8-5c085f387e85 # ╟─0866afc2-fd42-42b7-a572-9d824cf8b83b # ╟─75e1a973-7ef0-4ac5-b3e2-5edb63577927 # ╠═e8d8a60e-489b-467a-b49c-1fa844807751 -# ╟─b0c246ed-b871-461b-9541-280e49b49136 # ╠═9346d8e2-9ba0-4475-a21f-11bdd018bc60 # ╠═7822fdb7-bee6-40cc-a089-56bb32d77fe6 # ╟─701de4b8-42d3-46a3-a399-d7761dccd83d @@ -1291,28 +1059,26 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╟─f18b98f7-1e0f-4273-896f-8a667d15605b # ╟─d83d57e2-4787-4b8d-8669-64ed73d79e73 # ╟─077c95cf-2a1b-459f-830e-c29c11a2c5cc +# ╟─80511436-e41f-4913-8a30-d9e113cfaf71 # ╟─8388a833-d535-4cbd-a27b-de323cea60e8 # ╟─4cf27df3-6a69-402e-a71c-26538b2a52e7 # ╟─5721ad33-a51a-4a91-adb2-0915ea0efa13 # ╠═c857bb4b-4cf4-426e-b340-592cf7700434 -# ╟─d121e085-c69b-490f-b315-c11a9abd57a6 +# ╟─fc8984c8-4668-418a-b258-a1718809470c # ╠═846354c8-ba3b-4be7-926c-d3c9cc9add5f # ╟─a33c7d7a-8071-448e-abd6-4e38b5444a3a # ╠═91f3dab8-5521-44a0-9890-8d988a994076 # ╠═dcaae662-4a4f-4dd3-8763-89ea9eab7d43 -# ╟─d4bdc4fe-2af8-402f-950f-2afaf77c62de # ╟─e77cfefc-429d-49db-8135-f4604f6a9f0b # ╠═2d5689f5-1d63-4b8b-a103-da35933ad26e # ╠═6dd221d1-7fd8-446e-aced-950512ea34bc # ╠═0a9d6e2d-3a41-4cd5-9a4e-a9b76ed89fa9 # ╟─0962d456-1a76-4b0d-85ff-c9e7dc66621d # ╠═bf9b36e8-14c5-477b-a54b-35ba8e415c77 -# ╟─e910982c-8508-4729-a75d-8b5b847918b6 # ╟─781adedc-2da7-4394-b323-e508d614afae # ╟─de789ad1-8197-48ae-81b2-a21ec2340ae0 # ╠═85483b28-341e-4ed6-bb1e-66c33613725e # ╠═9e37c18c-3ebb-443a-9663-bb4064391d6e -# ╟─05d28aa2-9622-4e62-ab39-ca4c7dde6eb4 # ╠═3266f9e6-42ad-4103-8db3-b87d2c315290 # ╟─ebec177c-4c33-45a4-bdbd-f16944631aff # ╟─da7091f5-8ba2-498b-aa8d-bbf3b4505b81 From cb0a11e3d3a575340de3a8a40bcbbd73ee5200bd Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Sat, 5 Feb 2022 01:07:57 +0100 Subject: [PATCH 090/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b7d5afb27b..a3e6f0ed1f 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.17.7" +version = "0.17.8" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 098155978c61b8e2be60949f4820513049f3d76e Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Sat, 5 Feb 2022 01:39:01 +0100 Subject: [PATCH 091/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a3e6f0ed1f..916ed19f26 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.17.8" +version = "0.18.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 8ea6eabfb499bebd6551f36ea5c30afc926fe388 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Sat, 5 Feb 2022 01:51:55 +0100 Subject: [PATCH 092/821] More loading progress in terminal --- src/webserver/WebServer.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 05c30982a7..d20c57d45a 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -115,7 +115,13 @@ function run(session::ServerSession) Base.invokelatest(run, session, pluto_router) end +const is_first_run = Ref(true) + function run(session::ServerSession, pluto_router) + if is_first_run[] + is_first_run[] = false + @info "Loading..." + end if VERSION < v"1.6.2" @info "Pluto is running on an old version of Julia ($(VERSION)) that is no longer supported. Visit https://julialang.org/downloads/ for more information about upgrading Julia." @@ -277,6 +283,7 @@ function run(session::ServerSession, pluto_router) # Start this in the background, so that the first notebook launch (which will trigger registry update) will be faster @asynclog withtoken(pkg_token) do PkgCompat.update_registries(; force=false) + println(" Updating registry done ✓") end shutdown_server[] = () -> @sync begin From 10747db7ed512c6b3a9881c5cdb2a4daadea766d Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 10 Feb 2022 20:58:59 +0100 Subject: [PATCH 093/821] make `GlobalRef` aliases local to the cell (#1904) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make `GlobalRef` aliases local to the cell Otherwise, they "leak" inside the workspace and are set to `nothing` in the next reactive run. This means that if you use a hook defined in the same notebook, even Base functions can become `nothing` 👻. * one func --- src/runner/PlutoRunner.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index ae33b2081f..fc6dfb3e23 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -144,6 +144,7 @@ function globalref_to_workspaceref(expr) # Create new lines to assign to the replaced names of the global refs. # This way the expression explorer doesn't care (it just sees references to variables outside of the workspace), # and the variables don't get overwriten by local assigments to the same name (because we have special names). + (mutable_ref_list .|> ref -> :(local $(ref[2])))..., map(mutable_ref_list) do ref # I can just do Expr(:isdefined, ref[1]) here, but it feels better to macroexpand, # because it's more obvious what's going on, and when they ever change the ast, we're safe :D From ae1ec105c12b2229dd5efa0d6320e17e63fc26ef Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 11 Feb 2022 22:03:30 +0100 Subject: [PATCH 094/821] whitespace --- src/runner/PlutoRunner.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index fc6dfb3e23..2e0204b714 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -1304,7 +1304,7 @@ end # This is similar to how Requires.jl works, except we don't use a callback, we just check every time. const integrations = Integration[ Integration( - id = Base.PkgId(Base.UUID(reinterpret(Int128, codeunits("Paul Berg Berlin")) |> first), "AbstractPlutoDingetjes"), + id = Base.PkgId(Base.UUID(reinterpret(UInt128, codeunits("Paul Berg Berlin")) |> first), "AbstractPlutoDingetjes"), code = quote @assert v"1.0.0" <= AbstractPlutoDingetjes.MY_VERSION < v"2.0.0" initial_value_getter_ref[] = AbstractPlutoDingetjes.Bonds.initial_value From dac1912e5ad05f2d14bbf75d863b46353cde30fb Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 11 Feb 2022 22:04:08 +0100 Subject: [PATCH 095/821] tweak: use Response.ok for better errors --- frontend/common/SliderServerClient.js | 8 ++++++-- frontend/components/RecordingUI.js | 5 +++-- frontend/components/Welcome.js | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/common/SliderServerClient.js b/frontend/common/SliderServerClient.js index cf0c86fb93..c25084ee29 100644 --- a/frontend/common/SliderServerClient.js +++ b/frontend/common/SliderServerClient.js @@ -4,6 +4,8 @@ import { pack, unpack } from "./MsgPack.js" import immer from "../imports/immer.js" import _ from "../imports/lodash.js" +const assert_response_ok = (/** @type {Response} */ r) => (r.ok ? r : Promise.reject(r)) + const actions_to_keep = ["get_published_object"] export const nothing_actions = ({ actions }) => @@ -20,6 +22,7 @@ export const nothing_actions = ({ actions }) => export const slider_server_actions = ({ setStatePromise, launch_params, actions, get_original_state, get_current_state, apply_notebook_patches }) => { const notebookfile_hash = fetch(launch_params.notebookfile) + .then(assert_response_ok) .then((r) => r.arrayBuffer()) .then(hash_arraybuffer) @@ -27,6 +30,7 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const bond_connections = notebookfile_hash .then((hash) => fetch(trailingslash(launch_params.slider_server_url) + "bondconnections/" + encodeURIComponent(hash))) + .then(assert_response_ok) .then((r) => r.arrayBuffer()) .then((b) => unpack(new Uint8Array(b))) @@ -65,11 +69,11 @@ export const slider_server_actions = ({ setStatePromise, launch_params, actions, const response = use_get ? await fetch(url + encodeURIComponent(await base64_arraybuffer(packed)), { method: "GET", - }) + }).then(assert_response_ok) : await fetch(url, { method: "POST", body: packed, - }) + }).then(assert_response_ok) unpacked = unpack(new Uint8Array(await response.arrayBuffer())) const { patches, ids_of_cells_that_ran } = unpacked diff --git a/frontend/components/RecordingUI.js b/frontend/components/RecordingUI.js index 3cd0b035c2..846dd41b13 100644 --- a/frontend/components/RecordingUI.js +++ b/frontend/components/RecordingUI.js @@ -6,6 +6,7 @@ import immer from "../imports/immer.js" import { base64_arraybuffer, blob_url_to_data_url } from "../common/PlutoHash.js" import { pack, unpack } from "../common/MsgPack.js" +const assert_response_ok = (/** @type {Response} */ r) => (r.ok ? r : Promise.reject(r)) let run = (x) => x() export const RecordingUI = ({ notebook_name, is_recording, recording_waiting_to_start, set_recording_states, patch_listeners, export_url }) => { @@ -53,7 +54,7 @@ export const RecordingUI = ({ notebook_name, is_recording, recording_waiting_to_ } } - let initial_html = await (await fetch(export_url("notebookexport"))).text() + let initial_html = await (await fetch(export_url("notebookexport")).then(assert_response_ok)).text() initial_html = initial_html.replaceAll( "https://cdn.jsdelivr.net/gh/fonsp/Pluto.jl@0.17.3/frontend/", @@ -205,7 +206,7 @@ export const RecordingPlaybackUI = ({ recording_url, audio_src, initializing, ap () => Promise.resolve().then(async () => { if (recording_url) { - return unpack(new Uint8Array(await (await fetch(recording_url)).arrayBuffer())) + return unpack(new Uint8Array(await (await fetch(recording_url).then(assert_response_ok)).arrayBuffer())) } else { return null } diff --git a/frontend/components/Welcome.js b/frontend/components/Welcome.js index c58ac800a1..b29660cdd2 100644 --- a/frontend/components/Welcome.js +++ b/frontend/components/Welcome.js @@ -65,7 +65,7 @@ export const process_path_or_url = async (path_or_url) => { const gist = await ( await fetch(`https://api.github.com/gists/${gist_id}`, { headers: { Accept: "application/vnd.github.v3+json" }, - }) + }).then((r) => (r.ok ? r : Promise.reject(r))) ).json() console.log(gist) const files = Object.values(gist.files) From 8d735ded933c968195cee9acf686f7803aec848b Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 11 Feb 2022 22:17:06 +0100 Subject: [PATCH 096/821] asdf --- frontend/binder.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/binder.css b/frontend/binder.css index ca5995c0ba..ef11506ec3 100644 --- a/frontend/binder.css +++ b/frontend/binder.css @@ -79,10 +79,10 @@ binder-spinner { body.wiggle_binder button#launch_binder, body.wiggle_binder #binder_launch_help { /* position: fixed; */ - animation: wiggle 0.3s ease-in-out 0s 1; + animation: wiggle-binder-button 0.3s ease-in-out 0s 1; } -@keyframes wiggle { +@keyframes wiggle-binder-button { 0% { transform: rotate(0deg); } From 425d7499a2d9e1ca13c879ad8c7e8d2790332a3d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Sat, 12 Feb 2022 11:44:01 +0100 Subject: [PATCH 097/821] compat FuzzyCompletions = "0.3,0.4,0.5" --- Project.toml | 2 +- src/runner/NotebookProcessProject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 916ed19f26..5fd7297426 100644 --- a/Project.toml +++ b/Project.toml @@ -25,7 +25,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] Configurations = "0.15, 0.16, 0.17" -FuzzyCompletions = "0.3,0.4" +FuzzyCompletions = "0.3,0.4,0.5" HTTP = "^0.9.1" MsgPack = "1.1" RelocatableFolders = "0.1" diff --git a/src/runner/NotebookProcessProject.toml b/src/runner/NotebookProcessProject.toml index c4ab0afd8b..35b0b10f3d 100644 --- a/src/runner/NotebookProcessProject.toml +++ b/src/runner/NotebookProcessProject.toml @@ -12,4 +12,4 @@ Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] -FuzzyCompletions = "0.3,0.4" +FuzzyCompletions = "0.3,0.4,0.5" From b911de6b09f2dbb75b2b0b0f5493f14468e2026a Mon Sep 17 00:00:00 2001 From: Derek Ryan Strong Date: Sat, 12 Feb 2022 02:45:07 -0800 Subject: [PATCH 098/821] Remove duplicate "of" in Plots sample notebook (#1903) --- sample/Plots.jl.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/Plots.jl.jl b/sample/Plots.jl.jl index 313197612d..436207f68f 100644 --- a/sample/Plots.jl.jl +++ b/sample/Plots.jl.jl @@ -38,7 +38,7 @@ apples = [15, 25, 80, 75, 50, 30, 35, 15, 25, 35] # ╔═╡ a405ae4c-9ad9-11ea-0008-f763e098846d md""" -Great, let's plot them! The basic syntax of of `Plots` is very simple. +Great, let's plot them! The basic syntax of `Plots` is very simple. """ # ╔═╡ 12a8c222-9ad9-11ea-2544-355bd080367f From a14bcbabf3cca932a38d7d2d7d832240e3d12fb2 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Sat, 12 Feb 2022 11:52:56 +0100 Subject: [PATCH 099/821] docs for `WorkspaceManager.poll` --- src/evaluation/WorkspaceManager.jl | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index a606823c88..aae50e5e4f 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -474,6 +474,40 @@ move_vars(session_notebook, bump_workspace_module(session_notebook)..., to_delet move_vars(args...; kwargs...) ) +""" +```julia +poll(query::Function, timeout::Real=Inf64, interval::Real=1/20)::Bool +``` + +Keep running your function `query()` in intervals until it returns `true`, or until `timeout` seconds have passed. + +`poll` returns `true` if `query()` returned `true`. If `timeout` seconds have passed, `poll` returns `false`. + +# Example +```julia +vals = [1,2,3] + +@async for i in 1:5 + sleep(1) + vals[3] = 99 +end + +poll(8 #= seconds =#) do + vals[3] == 99 +end # returns `true` (after 5 seconds)! + +### + +@async for i in 1:5 + sleep(1) + vals[3] = 5678 +end + +poll(2 #= seconds =#) do + vals[3] == 5678 +end # returns `false` (after 2 seconds)! +``` +""" function poll(query::Function, timeout::Real=Inf64, interval::Real=1/20) start = time() while time() < start + timeout From 9c237a14a5e49d62a08026ad85d494d7b189b764 Mon Sep 17 00:00:00 2001 From: Michiel Dral Date: Sun, 13 Feb 2022 12:07:38 +0100 Subject: [PATCH 100/821] Fix parsing `f(;)`, fixes #1912 --- frontend/components/CellInput/scopestate_statefield.js | 4 ++-- frontend/components/CellInput/tests/scopestate.test.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/components/CellInput/scopestate_statefield.js b/frontend/components/CellInput/scopestate_statefield.js index 6fe6a0f2a2..270bf7ab21 100644 --- a/frontend/components/CellInput/scopestate_statefield.js +++ b/frontend/components/CellInput/scopestate_statefield.js @@ -122,7 +122,7 @@ let explorer_function_definition_argument = (cursor, doc, scopestate, verbose = if ((match = match_function_call_argument(cursor)`; ${t.many("named_args")}`)) { // "Parameters", the `y, z` in `function f(x; y, z) end` - let { named_args } = match + let { named_args = [] } = match for (let { node: named_arg } of named_args) { scopestate = explorer_function_definition_argument(named_arg, doc, scopestate, verbose) } @@ -759,7 +759,7 @@ export let explore_variable_usage = ( let match = null if ((match = match_function_call_argument(arg)`; ${t.many("named_args")}`)) { // "Parameters", the part in `f(x; y, z)` after the `;` - let { named_args } = match + let { named_args = [] } = match for (let { node: named_arg } of named_args) { let match = null if ((match = match_function_call_named_argument(named_arg)`${t.as("name")} = ${t.as("value")}`)) { diff --git a/frontend/components/CellInput/tests/scopestate.test.js b/frontend/components/CellInput/tests/scopestate.test.js index 96072cb61b..5dc05e668c 100644 --- a/frontend/components/CellInput/tests/scopestate.test.js +++ b/frontend/components/CellInput/tests/scopestate.test.js @@ -145,3 +145,7 @@ Deno.test("Macros", () => { Deno.test("Lonely bare tuple", () => { test_implicit(jl`defined, = (global_var,)`) }) + +Deno.test("Very, very lonely arguments", () => { + test_implicit(jl`global_var(;)`) +}) From 4bacb231b5435a2b60438dc67bcce5a2928209b1 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 14 Feb 2022 19:52:04 +0100 Subject: [PATCH 101/821] tests whitespace --- test/React.jl | 218 +++++++++++++++++++-------------------- test/ReloadFromFile.jl | 2 +- test/RichOutput.jl | 24 ++--- test/WorkspaceManager.jl | 12 +-- test/packages/Basic.jl | 13 ++- 5 files changed, 136 insertions(+), 133 deletions(-) diff --git a/test/React.jl b/test/React.jl index 3069b89f13..7c74f513e5 100644 --- a/test/React.jl +++ b/test/React.jl @@ -42,12 +42,12 @@ import Distributed @test notebook.cells[2].runtime !== nothing update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false + @test notebook.cells[3] |> noerror @test notebook.cells[3].output.rootassignee === nothing update_run!(🍭, notebook, notebook.cells[4]) @test notebook.cells[4].output.body == "16" - @test notebook.cells[4].errored == false + @test notebook.cells[4] |> noerror @test notebook.cells[4].output.rootassignee === nothing setcode(notebook.cells[1], "x = 912") @@ -62,7 +62,7 @@ import Distributed setcode(notebook.cells[2], "y = 2") update_run!(🍭, notebook, notebook.cells[1:2]) update_run!(🍭, notebook, notebook.cells[5:6]) - @test notebook.cells[5].errored == false + @test notebook.cells[5] |> noerror @test notebook.cells[6].output.body == "3" setcode(notebook.cells[2], "y = 1") @@ -105,16 +105,16 @@ import Distributed setcode(notebook.cells[1], "") update_run!(🍭, notebook, notebook.cells[1]) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror # https://github.com/fonsp/Pluto.jl/issues/26 setcode(notebook.cells[1], "x = 1") update_run!(🍭, notebook, notebook.cells[1]) setcode(notebook.cells[2], "x") update_run!(🍭, notebook, notebook.cells[2]) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror update_run!(🍭, notebook, notebook.cells[3]) update_run!(🍭, notebook, notebook.cells[4]) @@ -123,8 +123,8 @@ import Distributed setcode(notebook.cells[3], "") update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror update_run!(🍭, notebook, notebook.cells[5]) update_run!(🍭, notebook, notebook.cells[6]) @@ -133,8 +133,8 @@ import Distributed setcode(notebook.cells[5], "") update_run!(🍭, notebook, notebook.cells[5]) - @test notebook.cells[5].errored == false - @test notebook.cells[6].errored == false + @test notebook.cells[5] |> noerror + @test notebook.cells[6] |> noerror WorkspaceManager.unmake_workspace((🍭, notebook)) end @@ -278,12 +278,12 @@ import Distributed update_run!(🍭, notebook, notebook.cells[1:1]) @test notebook.cells[1].errored == true # this cell is before the using Dates and will error - @test notebook.cells[3].errored == false # using the position in the notebook this cell will not error + @test notebook.cells[3] |> noerror # using the position in the notebook this cell will not error update_run!(🍭, notebook, notebook.cells[2:2]) - @test notebook.cells[1].errored == false - @test notebook.cells[3].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[3] |> noerror end @testset "Reactive usings 2" begin @@ -297,8 +297,8 @@ import Distributed update_run!(🍭, notebook, notebook.cells) - @test notebook.cells[1].errored == false - @test notebook.cells[3].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[3] |> noerror setcode(notebook.cells[2], "") update_run!(🍭, notebook, notebook.cells[2:2]) @@ -646,8 +646,8 @@ import Distributed fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:4]) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror @test notebook.cells[3].output.body == "1" @test notebook.cells[4].output.body == "2" @@ -660,15 +660,15 @@ import Distributed setcode(notebook.cells[1], "a(x) = 1") update_run!(🍭, notebook, notebook.cells[1]) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror @test notebook.cells[3].output.body == "1" @test notebook.cells[4].output.body == "2" setcode(notebook.cells[1], "") update_run!(🍭, notebook, notebook.cells[1]) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror @test notebook.cells[3].errored == true @test notebook.cells[4].output.body == "2" @@ -680,16 +680,16 @@ import Distributed setcode(notebook.cells[5], "") update_run!(🍭, notebook, notebook.cells[5]) - @test notebook.cells[5].errored == false - @test notebook.cells[6].errored == false + @test notebook.cells[5] |> noerror + @test notebook.cells[6] |> noerror @test notebook.cells[7].errored == true @test notebook.cells[8].output.body == "6" setcode(notebook.cells[5], "b = 5") setcode(notebook.cells[6], "") update_run!(🍭, notebook, notebook.cells[5:6]) - @test notebook.cells[5].errored == false - @test notebook.cells[6].errored == false + @test notebook.cells[5] |> noerror + @test notebook.cells[6] |> noerror @test notebook.cells[7].output.body == "12" @test notebook.cells[8].errored == true @@ -697,8 +697,8 @@ import Distributed @test notebook.cells[12].output.body == "missing" update_run!(🍭, notebook, notebook.cells[9:10]) - @test notebook.cells[9].errored == false - @test notebook.cells[10].errored == false + @test notebook.cells[9] |> noerror + @test notebook.cells[10] |> noerror @test notebook.cells[11].output.body == "9" @test notebook.cells[12].output.body == "10" @test notebook.cells[13].output.body == "10" @@ -727,20 +727,20 @@ import Distributed @test notebook.cells[18].errored == true update_run!(🍭, notebook, notebook.cells[14]) - @test notebook.cells[16].errored == false + @test notebook.cells[16] |> noerror @test notebook.cells[17].errored == true - @test notebook.cells[18].errored == false + @test notebook.cells[18] |> noerror update_run!(🍭, notebook, notebook.cells[15]) - @test notebook.cells[16].errored == false - @test notebook.cells[17].errored == false - @test notebook.cells[18].errored == false + @test notebook.cells[16] |> noerror + @test notebook.cells[17] |> noerror + @test notebook.cells[18] |> noerror setcode(notebook.cells[14], "") update_run!(🍭, notebook, notebook.cells[14]) @test notebook.cells[16].errored == true - @test notebook.cells[17].errored == false - @test notebook.cells[18].errored == false + @test notebook.cells[17] |> noerror + @test notebook.cells[18] |> noerror setcode(notebook.cells[15], "") update_run!(🍭, notebook, notebook.cells[15]) @@ -755,8 +755,8 @@ import Distributed # Cell("e(22)"), update_run!(🍭, notebook, notebook.cells[19:22]) - @test notebook.cells[19].errored == false - @test notebook.cells[21].errored == false + @test notebook.cells[19] |> noerror + @test notebook.cells[21] |> noerror @test notebook.cells[22].errored == true setcode(notebook.cells[20], "asdf(x) = asdf(x,x)") @@ -770,38 +770,38 @@ import Distributed setcode(notebook.cells[20], "") update_run!(🍭, notebook, notebook.cells[20]) - @test notebook.cells[19].errored == false - @test notebook.cells[20].errored == false - @test notebook.cells[21].errored == false + @test notebook.cells[19] |> noerror + @test notebook.cells[20] |> noerror + @test notebook.cells[21] |> noerror @test notebook.cells[22].errored == true setcode(notebook.cells[19], "begin struct asdf; x; y; end; asdf(x) = asdf(x,x); end") setcode(notebook.cells[20], "") update_run!(🍭, notebook, notebook.cells[19:20]) - @test notebook.cells[19].errored == false - @test notebook.cells[20].errored == false - @test notebook.cells[21].errored == false - @test notebook.cells[22].errored == false + @test notebook.cells[19] |> noerror + @test notebook.cells[20] |> noerror + @test notebook.cells[21] |> noerror + @test notebook.cells[22] |> noerror update_run!(🍭, notebook, notebook.cells[23:27]) - @test notebook.cells[23].errored == false - @test notebook.cells[24].errored == false - @test notebook.cells[25].errored == false - @test notebook.cells[26].errored == false - @test notebook.cells[27].errored == false + @test notebook.cells[23] |> noerror + @test notebook.cells[24] |> noerror + @test notebook.cells[25] |> noerror + @test notebook.cells[26] |> noerror + @test notebook.cells[27] |> noerror update_run!(🍭, notebook, notebook.cells[23:27]) - @test notebook.cells[23].errored == false - @test notebook.cells[24].errored == false - @test notebook.cells[25].errored == false - @test notebook.cells[26].errored == false - @test notebook.cells[27].errored == false + @test notebook.cells[23] |> noerror + @test notebook.cells[24] |> noerror + @test notebook.cells[25] |> noerror + @test notebook.cells[26] |> noerror + @test notebook.cells[27] |> noerror setcode.(notebook.cells[23:27], [""]) update_run!(🍭, notebook, notebook.cells[23:27]) setcode(notebook.cells[23], "@assert !any(isdefined.([@__MODULE__], [Symbol(:e,i) for i in 1:14]))") update_run!(🍭, notebook, notebook.cells[23]) - @test notebook.cells[23].errored == false + @test notebook.cells[23] |> noerror WorkspaceManager.unmake_workspace((🍭, notebook)) @@ -970,23 +970,23 @@ import Distributed setcode(notebook.cells[1], "") update_run!(🍭, notebook, notebook.cells[1]) - @test notebook.cells[1].errored == false + @test notebook.cells[1] |> noerror @test occursinerror("x not defined", notebook.cells[2]) update_run!(🍭, notebook, notebook.cells[4]) update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror setcode(notebook.cells[3], "struct a; x; y end") update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror setcode(notebook.cells[3], "") update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false + @test notebook.cells[3] |> noerror @test notebook.cells[4].errored == true @@ -1009,13 +1009,13 @@ import Distributed update_run!(🍭, notebook, notebook.cells[1]) @test notebook.cells[1].output.body == "f" || startswith(notebook.cells[1].output.body, "f (generic function with ") - @test notebook.cells[1].errored == false + @test notebook.cells[1] |> noerror update_run!(🍭, notebook, notebook.cells[2:3]) - @test notebook.cells[2].errored == false - @test notebook.cells[3].errored == false + @test notebook.cells[2] |> noerror + @test notebook.cells[3] |> noerror update_run!(🍭, notebook, notebook.cells[3]) - @test notebook.cells[3].errored == false + @test notebook.cells[3] |> noerror update_run!(🍭, notebook, notebook.cells[4]) @test notebook.cells[4].output.body == "2" @@ -1103,7 +1103,7 @@ import Distributed @testset "Changing functions" begin update_run!(🍭, notebook, notebook.cells[2]) - @test notebook.cells[2].errored == false + @test notebook.cells[2] |> noerror update_run!(🍭, notebook, notebook.cells[1]) update_run!(🍭, notebook, notebook.cells[3]) @@ -1112,12 +1112,12 @@ import Distributed setcode(notebook.cells[1], "y = 2") update_run!(🍭, notebook, notebook.cells[1]) @test notebook.cells[3].output.body == "5" - @test notebook.cells[2].errored == false + @test notebook.cells[2] |> noerror setcode(notebook.cells[1], "y") update_run!(🍭, notebook, notebook.cells[1]) @test occursinerror("UndefVarError", notebook.cells[1]) - @test notebook.cells[2].errored == false + @test notebook.cells[2] |> noerror @test occursinerror("UndefVarError", notebook.cells[3]) update_run!(🍭, notebook, notebook.cells[4]) @@ -1126,7 +1126,7 @@ import Distributed setcode(notebook.cells[4], "g(a) = a+a") update_run!(🍭, notebook, notebook.cells[4]) - @test notebook.cells[4].errored == false + @test notebook.cells[4] |> noerror @test notebook.cells[5].errored == true setcode(notebook.cells[5], "g(5)") @@ -1136,18 +1136,18 @@ import Distributed update_run!(🍭, notebook, notebook.cells[6]) update_run!(🍭, notebook, notebook.cells[7]) update_run!(🍭, notebook, notebook.cells[8]) - @test notebook.cells[6].errored == false - @test notebook.cells[7].errored == false + @test notebook.cells[6] |> noerror + @test notebook.cells[7] |> noerror @test notebook.cells[8].errored == true setcode(notebook.cells[6], "h(x::Float64) = 2.0 * x") update_run!(🍭, notebook, notebook.cells[6]) - @test notebook.cells[6].errored == false + @test notebook.cells[6] |> noerror @test notebook.cells[7].errored == true - @test notebook.cells[8].errored == false + @test notebook.cells[8] |> noerror update_run!(🍭, notebook, notebook.cells[9:10]) - @test notebook.cells[9].errored == false + @test notebook.cells[9] |> noerror @test notebook.cells[10].output.body == "true" setcode(notebook.cells[9], "p = p") @@ -1156,48 +1156,48 @@ import Distributed setcode(notebook.cells[9], "p = 9") update_run!(🍭, notebook, notebook.cells[9]) - @test notebook.cells[9].errored == false + @test notebook.cells[9] |> noerror @test notebook.cells[10].output.body == "false" setcode(notebook.cells[9], "p(x) = 9") update_run!(🍭, notebook, notebook.cells[9]) - @test notebook.cells[9].errored == false + @test notebook.cells[9] |> noerror @test notebook.cells[10].output.body == "true" end @testset "Extending imported functions" begin update_run!(🍭, notebook, notebook.cells[11:15]) - @test_broken notebook.cells[11].errored == false - @test_broken notebook.cells[12].errored == false # multiple definitions for `Something` should be okay? == false - @test notebook.cells[13].errored == false + @test_broken notebook.cells[11] |> noerror + @test_broken notebook.cells[12] |> noerror # multiple definitions for `Something` should be okay? == false + @test notebook.cells[13] |> noerror @test notebook.cells[14].errored == true # the definition for a was created before `a` was used, so it hides the `a` from `Something` @test notebook.cells[15].output.body == "15" @test_nowarn update_run!(🍭, notebook, notebook.cells[13:15]) - @test notebook.cells[13].errored == false + @test notebook.cells[13] |> noerror @test notebook.cells[14].errored == true # the definition for a was created before `a` was used, so it hides the `a` from `Something` @test notebook.cells[15].output.body == "15" @test_nowarn update_run!(🍭, notebook, notebook.cells[16:20]) - @test notebook.cells[16].errored == false + @test notebook.cells[16] |> noerror @test occursinerror("Multiple", notebook.cells[17]) @test occursinerror("Multiple", notebook.cells[18]) @test occursinerror("UndefVarError", notebook.cells[19]) @test occursinerror("UndefVarError", notebook.cells[20]) @test_nowarn update_run!(🍭, notebook, notebook.cells[21:24]) - @test notebook.cells[21].errored == false - @test notebook.cells[22].errored == false - @test notebook.cells[23].errored == false + @test notebook.cells[21] |> noerror + @test notebook.cells[22] |> noerror + @test notebook.cells[23] |> noerror @test notebook.cells[23].output.body == "\"🐟\"" @test notebook.cells[24].output.body == "24" setcode(notebook.cells[22], "import .Wow: c") @test_nowarn update_run!(🍭, notebook, notebook.cells[22]) - @test notebook.cells[22].errored == false + @test notebook.cells[22] |> noerror @test notebook.cells[23].output.body == "\"🐟\"" - @test notebook.cells[23].errored == false + @test notebook.cells[23] |> noerror @test notebook.cells[24].errored == true # the extension should no longer exist # https://github.com/fonsp/Pluto.jl/issues/59 @@ -1235,7 +1235,7 @@ import Distributed @testset "Using external libraries" begin update_run!(🍭, notebook, notebook.cells[30:31]) - @test notebook.cells[30].errored == false + @test notebook.cells[30] |> noerror @test notebook.cells[31].output.body == "31" update_run!(🍭, notebook, notebook.cells[31]) @test notebook.cells[31].output.body == "31" @@ -1266,10 +1266,10 @@ import Distributed @test notebook.cells[2].output.body == "4" update_run!(🍭, notebook, notebook.cells[3:6]) - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false - @test notebook.cells[5].errored == false - @test notebook.cells[6].errored == false + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror + @test notebook.cells[5] |> noerror + @test notebook.cells[6] |> noerror @test notebook.cells[6].output.body == "9" setcode(notebook.cells[3], "b = -3") @@ -1277,7 +1277,7 @@ import Distributed @test notebook.cells[6].output.body == "3" update_run!(🍭, notebook, notebook.cells[7:8]) - @test notebook.cells[7].errored == false + @test notebook.cells[7] |> noerror @test notebook.cells[8].output.body == "5" setcode(notebook.cells[3], "b = 3") @@ -1347,7 +1347,7 @@ import Distributed update_run!(🍭, notebook, notebook.cells[9:10]) @test !occursinerror("UndefVarError", notebook.cells[9]) - @test notebook.cells[10].errored == false + @test notebook.cells[10] |> noerror update_run!(🍭, notebook, notebook.cells[11]) @test_broken notebook.cells[9].errored == true @@ -1359,7 +1359,7 @@ import Distributed update_run!(🍭, notebook, notebook.cells[13:15]) @test notebook.cells[13].output.body == "15" - @test notebook.cells[14].errored == false + @test notebook.cells[14] |> noerror setcode(notebook.cells[15], "orange = 10005") update_run!(🍭, notebook, notebook.cells[15]) @@ -1493,7 +1493,7 @@ import Distributed ]) update_run!(🍭, notebook, notebook.cells) - @test notebook.cells[1].errored == false + @test notebook.cells[1] |> noerror @test notebook.cells[1].output.body == "false" @test notebook.cells[22].output.body == "Expr" @test notebook.cells[25].output.body == ":(:value)" @@ -1534,7 +1534,7 @@ import Distributed update_run!(🍭, notebook, notebook.cells[6]) @test old != notebook.cells[6].output.body - @test notebook.cells[7].errored == false + @test notebook.cells[7] |> noerror @test notebook.cells[7].output.body == "false" @test occursinerror("UndefVarError", notebook.cells[8]) @@ -1553,13 +1553,13 @@ import Distributed @test notebook.cells[19].output.body == ":(1 + 1)" @test notebook.cells[20].output.body == ":(1 + 1)" - @test notebook.cells[27].errored == false + @test notebook.cells[27] |> noerror @test notebook.topology.codes[notebook.cells[27]].function_wrapped == false - @test notebook.cells[28].errored == false + @test notebook.cells[28] |> noerror update_run!(🍭, notebook, notebook.cells[29:30]) - @test notebook.cells[29].errored == false - @test notebook.cells[30].errored == false + @test notebook.cells[29] |> noerror + @test notebook.cells[30] |> noerror WorkspaceManager.unmake_workspace((🍭, notebook)) @@ -1644,21 +1644,21 @@ import Distributed update_run!(🍭, notebook, notebook.cells[16:18]) - @test notebook.cells[16].errored == false + @test notebook.cells[16] |> noerror @test notebook.cells[16].output.body == "34" - @test notebook.cells[17].errored == false - @test notebook.cells[18].errored == false + @test notebook.cells[17] |> noerror + @test notebook.cells[18] |> noerror setcode(notebook.cells[18], "υ = 8") update_run!(🍭, notebook, notebook.cells[18]) @test notebook.cells[16].output.body == "24" update_run!(🍭, notebook, notebook.cells[19:22]) - @test notebook.cells[19].errored == false + @test notebook.cells[19] |> noerror @test notebook.cells[19].output.body == "60" - @test notebook.cells[20].errored == false - @test notebook.cells[21].errored == false - @test notebook.cells[22].errored == false + @test notebook.cells[20] |> noerror + @test notebook.cells[21] |> noerror + @test notebook.cells[22] |> noerror setcode(notebook.cells[22], "y = 0") update_run!(🍭, notebook, notebook.cells[22]) diff --git a/test/ReloadFromFile.jl b/test/ReloadFromFile.jl index a9c4bb909c..a6a5eadbb6 100644 --- a/test/ReloadFromFile.jl +++ b/test/ReloadFromFile.jl @@ -45,7 +45,7 @@ import Pkg end @test notebook.cells[2].output.body == "246" - @test notebook.cells[3].errored == false + @test notebook.cells[3] |> noerror original_rand_output = notebook.cells[3].output.body diff --git a/test/RichOutput.jl b/test/RichOutput.jl index 2bfc98e8bc..8e61e2a342 100644 --- a/test/RichOutput.jl +++ b/test/RichOutput.jl @@ -189,10 +189,10 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb update_run!(🍭, notebook, notebook.cells) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror end end @@ -259,9 +259,9 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb @test occursin("String?", string(notebook.cells[13].output.body)) # Issue 1490. @test notebook.cells[10].output.mime isa MIME"text/plain" - @test notebook.cells[10].errored == false + @test notebook.cells[10] |> noerror - @test notebook.cells[17].errored == false # Issue 1815 + @test notebook.cells[17] |> noerror # Issue 1815 # to see if we truncated correctly, we convert the output to string and check how big it is # because we don't want to test too specifically @@ -309,26 +309,26 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb update_run!(🍭, notebook, notebook.cells[3:end]) @test occursinerror("syntax: extra token after", notebook.cells[3]) - @test notebook.cells[4].errored == false + @test notebook.cells[4] |> noerror @test notebook.cells[4].output.body == "4" @test notebook.cells[4].output.rootassignee == :c - @test notebook.cells[5].errored == false + @test notebook.cells[5] |> noerror @test notebook.cells[5].output.body == "" @test notebook.cells[5].output.rootassignee === nothing - @test notebook.cells[6].errored == false + @test notebook.cells[6] |> noerror @test notebook.cells[6].output.body == "6" @test notebook.cells[6].output.rootassignee === nothing - @test notebook.cells[7].errored == false + @test notebook.cells[7] |> noerror @test notebook.cells[7].output.body == "" @test notebook.cells[7].output.rootassignee === nothing - @test notebook.cells[8].errored == false + @test notebook.cells[8] |> noerror @test notebook.cells[8].output.body == "" - @test notebook.cells[9].errored == false + @test notebook.cells[9] |> noerror @test notebook.cells[9].output.body == "" @test occursinerror("syntax: extra token after", notebook.cells[10]) diff --git a/test/WorkspaceManager.jl b/test/WorkspaceManager.jl index f90987d6a5..64e0a5d2c3 100644 --- a/test/WorkspaceManager.jl +++ b/test/WorkspaceManager.jl @@ -91,15 +91,15 @@ import Distributed update_run!(🍭, notebook, notebook.cells) - @test notebook.cells[1].errored == false - @test notebook.cells[2].errored == false - @test notebook.cells[3].errored == false - @test notebook.cells[4].errored == false - @test notebook.cells[5].errored == false + @test notebook.cells[1] |> noerror + @test notebook.cells[2] |> noerror + @test notebook.cells[3] |> noerror + @test notebook.cells[4] |> noerror + @test notebook.cells[5] |> noerror setcode(notebook.cells[5], "length(nb.cells)") update_run!(🍭, notebook, notebook.cells[5]) - @test notebook.cells[5].errored == false + @test notebook.cells[5] |> noerror desired_nprocs = Distributed.nprocs() - 1 diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index b87dea6f02..d53b88bb93 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -8,18 +8,21 @@ import Pluto.PkgUtils import Pluto.PkgCompat import Distributed +# We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. + const pluto_test_registry_spec = Pkg.RegistrySpec(; url="https://github.com/JuliaPluto/PlutoPkgTestRegistry", uuid=Base.UUID("96d04d5f-8721-475f-89c4-5ee455d3eda0"), name="PlutoPkgTestRegistry", ) + @testset "Built-in Pkg" begin # Pkg.Registry.rm("General") Pkg.Registry.add(pluto_test_registry_spec) - + # We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. @testset "Basic" begin fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() @@ -282,7 +285,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test noerror(notebook.cells[3]) @test noerror(notebook.cells[4]) - @test notebook.cells[5].errored == false + @test notebook.cells[5] |> noerror @test !has_embedded_pkgfiles(notebook) @@ -446,13 +449,13 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; # Cells that use Example will error because the package is not installed. # @test Distributed.remotecall_eval(Main, p, quote - # nb.cells[1].errored == false + # nb.cells[1] |> noerror # end) @test Distributed.remotecall_eval(Main, p, quote - nb.cells[2].errored == false + nb.cells[2] |> noerror end) # @test Distributed.remotecall_eval(Main, p, quote - # nb.cells[3].errored == false + # nb.cells[3] |> noerror # end) # @test Distributed.remotecall_eval(Main, p, quote # nb.cells[3].output.body == "25" From 83177dc47a1e617bd36c52ee5f81ba9792adfe0a Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 14 Feb 2022 23:08:45 +0100 Subject: [PATCH 102/821] oopsie! --- test/packages/Basic.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index d53b88bb93..adb1ce074a 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -449,13 +449,13 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; # Cells that use Example will error because the package is not installed. # @test Distributed.remotecall_eval(Main, p, quote - # nb.cells[1] |> noerror + # nb.cells[1].errored == false # end) @test Distributed.remotecall_eval(Main, p, quote - nb.cells[2] |> noerror + nb.cells[2].errored == false end) # @test Distributed.remotecall_eval(Main, p, quote - # nb.cells[3] |> noerror + # nb.cells[3].errored == false # end) # @test Distributed.remotecall_eval(Main, p, quote # nb.cells[3].output.body == "25" From 4885362c467be4a28c711ca471f514c69a975c00 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 14 Feb 2022 23:09:25 +0100 Subject: [PATCH 103/821] whitespace --- src/analysis/TopologicalOrder.jl | 2 +- src/analysis/topological_order.jl | 10 +++++----- src/evaluation/Run.jl | 2 +- src/notebook/Notebook.jl | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/analysis/TopologicalOrder.jl b/src/analysis/TopologicalOrder.jl index 4fa1e49521..bd159522bf 100644 --- a/src/analysis/TopologicalOrder.jl +++ b/src/analysis/TopologicalOrder.jl @@ -4,7 +4,7 @@ import .ExpressionExplorer: SymbolsState, FunctionName Base.@kwdef struct TopologicalOrder input_topology::NotebookTopology "Cells that form a directed acyclic graph, in topological order." - runnable::Array{Cell,1} + runnable::Vector{Cell} "Cells that are in a directed cycle, with corresponding `ReactivityError`s." errable::Dict{Cell,ReactivityError} end diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index d9bafd6cee..e95bf456f5 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -6,7 +6,7 @@ struct Cycle <: ChildExplorationResult 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 +function topological_order(notebook::Notebook, topology::NotebookTopology, roots::Vector{Cell}; allow_multiple_defs=false)::TopologicalOrder entries = Cell[] exits = Cell[] errable = Dict{Cell,ReactivityError}() @@ -109,12 +109,12 @@ function disjoint(a::Set, b::Set) end "Return the cells that reference any of the symbols defined by the given cell. Non-recursive: only direct dependencies are found." -function where_referenced(notebook::Notebook, topology::NotebookTopology, myself::Cell)::Array{Cell,1} +function where_referenced(notebook::Notebook, topology::NotebookTopology, myself::Cell)::Vector{Cell} to_compare = union(topology.nodes[myself].definitions, topology.nodes[myself].soft_definitions, topology.nodes[myself].funcdefs_without_signatures) where_referenced(notebook, topology, to_compare) end "Return the cells that reference any of the given symbols. Non-recursive: only direct dependencies are found." -function where_referenced(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Array{Cell,1} +function where_referenced(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} return filter(notebook.cells) do cell !disjoint(to_compare, topology.nodes[cell].references) end @@ -132,7 +132,7 @@ 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} +function where_assigned(notebook::Notebook, topology::NotebookTopology, myself::Cell)::Vector{Cell} self = topology.nodes[myself] return filter(notebook.cells) do cell other = topology.nodes[cell] @@ -147,7 +147,7 @@ function where_assigned(notebook::Notebook, topology::NotebookTopology, myself:: end end -function where_assigned(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Array{Cell,1} +function where_assigned(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} filter(notebook.cells) do cell other = topology.nodes[cell] !( diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index d0dc39a2f3..2ad0b3156b 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -402,7 +402,7 @@ end "Do all the things!" -function update_save_run!(session::ServerSession, notebook::Notebook, cells::Array{Cell,1}; save::Bool=true, run_async::Bool=false, prerender_text::Bool=false, kwargs...) +function update_save_run!(session::ServerSession, notebook::Notebook, cells::Vector{Cell}; save::Bool=true, run_async::Bool=false, prerender_text::Bool=false, kwargs...) old = notebook.topology new = notebook.topology = updated_topology(old, notebook, cells) # macros are not yet resolved diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index c2e5452b86..c1338d0a15 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -56,7 +56,7 @@ Base.@kwdef mutable struct Notebook bonds::Dict{Symbol,BondValue}=Dict{Symbol,BondValue}() end -Notebook(cells::Array{Cell,1}, path::AbstractString, notebook_id::UUID) = Notebook( +Notebook(cells::Vector{Cell}, path::AbstractString, notebook_id::UUID) = Notebook( cells_dict=Dict(map(cells) do cell (cell.cell_id, cell) end), @@ -65,7 +65,7 @@ Notebook(cells::Array{Cell,1}, path::AbstractString, notebook_id::UUID) = Notebo notebook_id=notebook_id, ) -Notebook(cells::Array{Cell,1}, path::AbstractString=numbered_until_new(joinpath(new_notebooks_directory(), cutename()))) = Notebook(cells, path, uuid1()) +Notebook(cells::Vector{Cell}, path::AbstractString=numbered_until_new(joinpath(new_notebooks_directory(), cutename()))) = Notebook(cells, path, uuid1()) function Base.getproperty(notebook::Notebook, property::Symbol) if property == :cells From 1f57985ce646ce5c71fa5a171789f5f24da1d143 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 00:52:49 +0100 Subject: [PATCH 104/821] whitespace --- test/Events.jl | 2 +- test/Notebook.jl | 16 ++++++++-------- test/helpers.jl | 21 ++++++++++++++------- test/packages/Basic.jl | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/test/Events.jl b/test/Events.jl index bdd56dee0f..fc5b941eb5 100644 --- a/test/Events.jl +++ b/test/Events.jl @@ -9,7 +9,7 @@ import UUIDs: UUID events = [] function test_listener(a::PlutoEvent) - @info "this run!" + # @info "this run!" push!(events, typeof(a)) end 🍭 = ServerSession() diff --git a/test/Notebook.jl b/test/Notebook.jl index ab6336dc7d..5e34ed350b 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -137,13 +137,13 @@ end save_notebook(nb) # @info "File" name Text(read(nb.path,String)) result = load_notebook_nobackup(nb.path) - @test notebook_inputs_equal(nb, result) + @test_notebook_inputs_equal(nb, result) end end @testset "I/O overloaded" begin @testset "$(name)" for (name, nb) in nbs - @test let + let tasks = [] for i in 1:16 push!(tasks, @async save_notebook(nb)) @@ -153,7 +153,7 @@ end end wait.(tasks) result = load_notebook_nobackup(nb.path) - notebook_inputs_equal(nb, result) + @test_notebook_inputs_equal(nb, result) end end end @@ -251,15 +251,15 @@ end @testset "$(name)" for (name, nb) in nbs file_contents = sprint(save_notebook, nb) - @test let + let result = sread(load_notebook_nobackup, file_contents, nb.path) - notebook_inputs_equal(nb, result) + @test_notebook_inputs_equal(nb, result) end - @test let + let file_contents_windowsed = replace(file_contents, "\n" => "\r\n") result_windowsed = sread(load_notebook_nobackup, file_contents_windowsed, nb.path) - notebook_inputs_equal(nb, result_windowsed) + @test_notebook_inputs_equal(nb, result_windowsed) end end end @@ -343,7 +343,7 @@ end write(jl_path, embedded_jl) result = load_notebook_nobackup(jl_path) - @test notebook_inputs_equal(nb, result; check_paths_equality=false) + @test_notebook_inputs_equal(nb, result, false) filename = "howdy.jl" diff --git a/test/helpers.jl b/test/helpers.jl index c259e5d232..a1ec18f830 100644 --- a/test/helpers.jl +++ b/test/helpers.jl @@ -129,13 +129,20 @@ function occursinerror(needle, haystack::Pluto.Cell) end "Test notebook equality, ignoring cell UUIDs and such." -function notebook_inputs_equal(nbA, nbB; check_paths_equality=true) - x = !check_paths_equality || (normpath(nbA.path) == normpath(nbB.path)) - - to_compare(cell) = (cell.cell_id, cell.code_folded, cell.code) - y = to_compare.(nbA.cells) == to_compare.(nbB.cells) - - x && y +macro test_notebook_inputs_equal(nbA, nbB, check_paths_equality::Bool=true) + quote + nbA = $(esc(nbA)) + nbB = $(esc(nbB)) + if $(check_paths_equality) + @test normpath(nbA.path) == normpath(nbB.path) + end + + @test length(nbA.cells) == length(nbB.cells) + @test getproperty.(nbA.cells, :cell_id) == getproperty.(nbB.cells, :cell_id) + @test getproperty.(nbA.cells, :code_folded) == getproperty.(nbB.cells, :code_folded) + @test getproperty.(nbA.cells, :code) == getproperty.(nbB.cells, :code) + + end |> Base.remove_linenums! end "Whether the given .jl file can be run without any errors. While notebooks cells can be in arbitrary order, their order in the save file must be topological. diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index adb1ce074a..25c8f61210 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -76,7 +76,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; old_A_terminal = deepcopy(terminals["PlutoPkgTestA"]) - @show old_A_terminal + # @show old_A_terminal update_save_run!(🍭, notebook, notebook.cells[[3, 4]]) # import B From 5c61a9303446554131d93ae9e7edc685806a30ec Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 14 Feb 2022 23:08:45 +0100 Subject: [PATCH 105/821] oopsie! From 2cca8697499f9733c182fa79702aa5eaeed91281 Mon Sep 17 00:00:00 2001 From: Michiel Dral Date: Tue, 15 Feb 2022 01:36:38 +0100 Subject: [PATCH 106/821] Open external links in new tab, fixes #1 (#1916) Co-authored-by: Fons van der Plas --- frontend/components/Editor.js | 3 ++ .../HijackExternalLinksToOpenInNewTab.js | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 frontend/components/HackySideStuff/HijackExternalLinksToOpenInNewTab.js diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index d9376cbd71..48c81b5433 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -32,6 +32,7 @@ import { ProgressBar } from "./ProgressBar.js" import { IsolatedCell } from "./Cell.js" import { RawHTMLContainer } from "./CellOutput.js" import { RecordingPlaybackUI, RecordingUI } from "./RecordingUI.js" +import { HijackExternalLinksToOpenInNewTab } from "./HackySideStuff/HijackExternalLinksToOpenInNewTab.js" const default_path = "..." const DEBUG_DIFFING = false @@ -1205,6 +1206,8 @@ patch: ${JSON.stringify( >` return html` + ${this.state.disable_ui === false && html`<${HijackExternalLinksToOpenInNewTab} />`} + <${PlutoContext.Provider} value=${this.actions}> <${PlutoBondsContext.Provider} value=${this.state.notebook.bonds}> <${PlutoJSInitializingContext.Provider} value=${this.js_init_set}> diff --git a/frontend/components/HackySideStuff/HijackExternalLinksToOpenInNewTab.js b/frontend/components/HackySideStuff/HijackExternalLinksToOpenInNewTab.js new file mode 100644 index 0000000000..3871d4bc99 --- /dev/null +++ b/frontend/components/HackySideStuff/HijackExternalLinksToOpenInNewTab.js @@ -0,0 +1,35 @@ +import { useEffect } from "../../imports/Preact.js" + +/** + * Time flies when you're building Pluto... + * At one moment you self-assignee to issue number #1, next moment we're approaching issue #2000... + * + * We can't just put `` in the ``, because this also opens hash links + * like `#id` in a new tab... + * + * This components takes every click event on an that points to another origin (i.e. not `#id`) + * and sneakily puts in a `target="_blank"` attribute so it opens in a new tab. + * + * Fixes https://github.com/fonsp/Pluto.jl/issues/1 + * Based on https://stackoverflow.com/a/12552017/2681964 + */ +export let HijackExternalLinksToOpenInNewTab = () => { + useEffect(() => { + let handler = (event) => { + if (event.defaultPrevented) return + + const origin = event.target.closest(`a`) + + if (origin && !origin.hasAttribute("target")) { + let as_url = new URL(origin.href) + if (as_url.origin !== window.location.origin) { + origin.target = "_blank" + } + } + } + document.addEventListener("click", handler) + return () => document.removeEventListener("click", handler) + }) + + return null +} From 63748e1c41a9d2bcf50f1a6a34db70baae89e07e Mon Sep 17 00:00:00 2001 From: Joel Dahne Date: Tue, 15 Feb 2022 01:38:13 +0100 Subject: [PATCH 107/821] Fix logging getting stuck when `maxlog` is specified and don't print `maxlog`, fixes #1902 (#1911) --- src/evaluation/WorkspaceManager.jl | 6 ++- test/Logging.jl | 67 +++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index aae50e5e4f..6ce1cbde2e 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -169,9 +169,13 @@ function start_relaying_logs((session, notebook)::SN, log_channel::Distributed.R try max_log = parse(Int, next_log["kwargs"][maybe_max_log][2] |> first) + # Don't include maxlog in the log-message, in line + # with how Julia handles it. + deleteat!(next_log["kwargs"], maybe_max_log) + # Don't show message with id more than max_log times if max_log isa Int && n_logs >= max_log - return + continue end catch end diff --git a/test/Logging.jl b/test/Logging.jl index eb0930cc7f..a19673eca6 100644 --- a/test/Logging.jl +++ b/test/Logging.jl @@ -11,20 +11,65 @@ using Pluto.WorkspaceManager: poll 🍭.connected_clients[fakeclient.id] = fakeclient @testset "Logging respects maxlog" begin - notebook = Notebook(Cell.([ - """ - for i in 1:10 - @info "logging" i maxlog=2 + @testset "Single log" begin + notebook = Notebook(Cell.([ + """ + for i in 1:10 + @info "logging" i maxlog=2 + end + """, + ])) + + update_run!(🍭, notebook, notebook.cells) + @test notebook.cells[begin] |> noerror + + @test poll(5, 1/60) do + length(notebook.cells[begin].logs) == 2 + end + + # Check that maxlog doesn't occur in the message + @test all(notebook.cells[begin].logs) do log + all(log["kwargs"]) do kwarg + kwarg[1] != "maxlog" + end + end + + WorkspaceManager.unmake_workspace((🍭, notebook)) + end + + @testset "Multiple log" begin + notebook = Notebook(Cell.([ + """ + for i in 1:10 + @info "logging" i maxlog=2 + @info "logging more" maxlog = 4 + @info "even more logging" + end + """, + ])) + + update_run!(🍭, notebook, notebook.cells) + @test notebook.cells[begin] |> noerror + + # Wait until all 16 logs are in + @test poll(5, 1/60) do + length(notebook.cells[begin].logs) == 16 end - """, - ])) - update_run!(🍭, notebook, notebook.cells) - @test notebook.cells[begin] |> noerror + # Get the ids of the three logs and their counts. We are + # assuming that the logs are ordered same as in the loop. + ids = unique(getindex.(notebook.cells[begin].logs, "id")) + counts = [count(log -> log["id"] == id, notebook.cells[begin].logs) for id in ids] + @test counts == [2, 4, 10] + + # Check that maxlog doesn't occur in the messages + @test all(notebook.cells[begin].logs) do log + all(log["kwargs"]) do kwarg + kwarg[1] != "maxlog" + end + end - @test poll(5, 1/60) do - length(notebook.cells[begin].logs) == 2 + WorkspaceManager.unmake_workspace((🍭, notebook)) end - WorkspaceManager.unmake_workspace((🍭, notebook)) end end From c7f94672113cb8b5cb1a3a93d4fc831e9ceb48e6 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 13:50:35 +0100 Subject: [PATCH 108/821] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 92cdf08c5d..caa87d86f7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,6 +12,7 @@ Thank you for reporting an issue about Pluto! Let's get it fixed! # To report an issue, you need two things: 1. 📹 A video recording demonstrating the problem. (You can drag a video file into this box) 2. 📝 A short Pluto notebook file. (Upload the notebook file to https://gist.github.com/ and include the link.) +3. 🤕 Try to clearly explain what the problem is, it might not be obvious to others! Instead of saying: "This does not work.", try to say: "I expected ..., but instead I am seeing ..." 🙋 But my issue is really simple, I don't want to make a screen recording / notebook! From 5b24a1fb32f30733ad92b107323c70a8d9857f5b Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 15:33:10 +0100 Subject: [PATCH 109/821] frontend tests: better debugging? --- test/frontend/helpers/common.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/frontend/helpers/common.js b/test/frontend/helpers/common.js index 3e45ea4ac8..b9a96949c1 100644 --- a/test/frontend/helpers/common.js +++ b/test/frontend/helpers/common.js @@ -81,11 +81,11 @@ export const waitForContentToBecome = async (page, selector, targetContent) => { return getTextContent(page, selector); }; -export const clickAndWaitForNavigation = (page, selector) => - Promise.all([ - page.waitForNavigation({ waitUntil: "networkidle0" }), - page.click(selector), - ]); +export const clickAndWaitForNavigation = async (page, selector) => { + let t = page.waitForNavigation({ waitUntil: "networkidle0" }) + await page.click(selector) + await t +} const dismissBeforeUnloadDialogs = (page) => { page.on("dialog", async (dialog) => { From b93918abdd136697907327603ba88aea65e23081 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 15:33:31 +0100 Subject: [PATCH 110/821] Make sure that the watch file task does not hang --- src/webserver/SessionActions.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index 94cba17888..4f63f5c3d3 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -95,12 +95,16 @@ function add(session::ServerSession, nb::Notebook; run_async::Bool=true) end in_session() = get(session.notebooks, nb.notebook_id, nothing) === nb - session.options.server.auto_reload_from_file && @asynclog while in_session() + session.options.server.auto_reload_from_file && @spawnlog while in_session() if !isfile(nb.path) # notebook file deleted... let's ignore this, changing the notebook will cause it to save again. Fine for now sleep(2) else - watch_file(nb.path) + e = watch_file(nb.path, 3) + if e.timedout + continue + end + # the above call is blocking until the file changes local modified_time = mtime(nb.path) From 20e7e50bf1249dc2005728f97abf820c7533ac20 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 15:40:08 +0100 Subject: [PATCH 111/821] oops --- src/webserver/SessionActions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index 4f63f5c3d3..6eb3393173 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -95,7 +95,7 @@ function add(session::ServerSession, nb::Notebook; run_async::Bool=true) end in_session() = get(session.notebooks, nb.notebook_id, nothing) === nb - session.options.server.auto_reload_from_file && @spawnlog while in_session() + session.options.server.auto_reload_from_file && @asynclog while in_session() if !isfile(nb.path) # notebook file deleted... let's ignore this, changing the notebook will cause it to save again. Fine for now sleep(2) From d6db07920adb95336d92f0310eb9fbbd779f1120 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 15:57:20 +0100 Subject: [PATCH 112/821] Update Test.yml --- .github/workflows/Test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 8bd5540875..f5a53091da 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -9,6 +9,7 @@ on: - "frontend/**" - "frontend-bundler/**" - "frontend-dist/**" + - "test/frontend/**" - "**.md" branches: - main From c7219600919216661d02a7b782e24e2301adea7b Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 15 Feb 2022 19:04:26 +0100 Subject: [PATCH 113/821] Fix frontend tests!! (#1922) --- test/frontend/helpers/common.js | 42 ++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/test/frontend/helpers/common.js b/test/frontend/helpers/common.js index b9a96949c1..6b0b229300 100644 --- a/test/frontend/helpers/common.js +++ b/test/frontend/helpers/common.js @@ -2,6 +2,44 @@ import path from "path"; import mkdirp from "mkdirp"; import * as process from "process"; +// from https://github.com/puppeteer/puppeteer/issues/1908#issuecomment-380308269 +class InflightRequests { + constructor(page) { + this._page = page; + this._requests = new Map(); + this._onStarted = this._onStarted.bind(this); + this._onFinished = this._onFinished.bind(this); + this._page.on('request', this._onStarted); + this._page.on('requestfinished', this._onFinished); + this._page.on('requestfailed', this._onFinished); + } + + _onStarted(request) { this._requests.set(request, 1 + (this._requests.get(request) ?? 0)); } + _onFinished(request) { this._requests.set(request, -1 + (this._requests.get(request) ?? 0)); } + + inflightRequests() { return Array.from([...this._requests.entries()].flatMap(([k,v]) => v > 0 ? [k] : [])); } + + dispose() { + this._page.removeListener('request', this._onStarted); + this._page.removeListener('requestfinished', this._onFinished); + this._page.removeListener('requestfailed', this._onFinished); + } +} + +const with_connections_debug = (page, action) => { + const tracker = new InflightRequests(page); + return action().finally(() => { + tracker.dispose(); + const inflight = tracker.inflightRequests(); + if(inflight.length > 0) { + console.warn("Open connections: ", inflight.map(request => request.url())); + } + }).catch(e => { + + throw e + }) +} + export const getTextContent = (page, selector) => { // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#differences_from_innertext return page.evaluate( @@ -82,7 +120,9 @@ export const waitForContentToBecome = async (page, selector, targetContent) => { }; export const clickAndWaitForNavigation = async (page, selector) => { - let t = page.waitForNavigation({ waitUntil: "networkidle0" }) + let t = with_connections_debug(page, () => page.waitForNavigation({ waitUntil: "networkidle0" })).catch(e => { + console.warn("Network idle never happened after navigation... weird!", e) + }) await page.click(selector) await t } From 7a74c63e887772d543b336d3540ddf588aa76965 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 16 Feb 2022 14:27:11 +0100 Subject: [PATCH 114/821] Store cell order in NotebookTopology (#1920) --- src/Pluto.jl | 1 + src/analysis/DependencyCache.jl | 24 ++++--- src/analysis/ExpressionExplorer.jl | 5 +- src/analysis/MoreAnalysis.jl | 2 +- src/analysis/Topology.jl | 45 +++++------- src/analysis/TopologyUpdate.jl | 20 ++++-- src/analysis/data structures.jl | 110 +++++++++++++++++++++++++++++ src/analysis/topological_order.jl | 29 ++++---- src/evaluation/Run.jl | 41 ++++++++--- src/notebook/Notebook.jl | 44 ++++++++---- test/React.jl | 12 ++-- test/data structures.jl | 104 +++++++++++++++++++++++++++ test/packages/Basic.jl | 2 +- test/runtests.jl | 1 + 14 files changed, 347 insertions(+), 93 deletions(-) create mode 100644 src/analysis/data structures.jl create mode 100644 test/data structures.jl diff --git a/src/Pluto.jl b/src/Pluto.jl index e6c3ff3bb5..90a95b6519 100644 --- a/src/Pluto.jl +++ b/src/Pluto.jl @@ -47,6 +47,7 @@ include("./analysis/ReactiveNode.jl") include("./packages/PkgCompat.jl") include("./notebook/Cell.jl") +include("./analysis/data structures.jl") include("./analysis/Topology.jl") include("./analysis/Errors.jl") include("./analysis/TopologicalOrder.jl") diff --git a/src/analysis/DependencyCache.jl b/src/analysis/DependencyCache.jl index 00ee819a09..750144142e 100644 --- a/src/analysis/DependencyCache.jl +++ b/src/analysis/DependencyCache.jl @@ -4,15 +4,16 @@ Gets a dictionary of all symbols and the respective cells which are dependent on Changes in the given cell cause re-evaluation of these cells. Note that only direct dependents are given here, not indirect dependents. """ -function downstream_cells_map(cell::Cell, notebook::Notebook)::Dict{Symbol,Vector{Cell}} - defined_symbols = let node = notebook.topology.nodes[cell] +function downstream_cells_map(cell::Cell, topology::NotebookTopology)::Dict{Symbol,Vector{Cell}} + defined_symbols = let node = topology.nodes[cell] node.definitions ∪ node.funcdefs_without_signatures end return Dict{Symbol,Vector{Cell}}( - sym => where_referenced(notebook, notebook.topology, Set([sym])) + sym => where_referenced(topology, Set([sym])) for sym in defined_symbols ) end +@deprecate downstream_cells_map(cell::Cell, notebook::Notebook) downstream_cells_map(cell, notebook.topology) """ Gets a dictionary of all symbols and the respective cells on which the given cell depends. @@ -20,20 +21,21 @@ Gets a dictionary of all symbols and the respective cells on which the given cel Changes in these cells cause re-evaluation of the given cell. Note that only direct dependencies are given here, not indirect dependencies. """ -function upstream_cells_map(cell::Cell, notebook::Notebook)::Dict{Symbol,Vector{Cell}} - referenced_symbols = notebook.topology.nodes[cell].references +function upstream_cells_map(cell::Cell, topology::NotebookTopology)::Dict{Symbol,Vector{Cell}} + referenced_symbols = topology.nodes[cell].references return Dict{Symbol,Vector{Cell}}( - sym => where_assigned(notebook, notebook.topology, Set([sym]) ) + sym => where_assigned(topology, Set([sym]) ) for sym in referenced_symbols ) end +@deprecate upstream_cells_map(cell::Cell, notebook::Notebook) upstream_cells_map(cell, notebook.topology) "Fills cell dependency information for display in the GUI" -function update_dependency_cache!(cell::Cell, notebook::Notebook) +function update_dependency_cache!(cell::Cell, topology::NotebookTopology) cell.cell_dependencies = CellDependencies( - downstream_cells_map(cell, notebook), - upstream_cells_map(cell, notebook), - cell_precedence_heuristic(notebook.topology, cell), + downstream_cells_map(cell, topology), + upstream_cells_map(cell, topology), + cell_precedence_heuristic(topology, cell), ) end @@ -41,6 +43,6 @@ end function update_dependency_cache!(notebook::Notebook) notebook._cached_topological_order = topological_order(notebook) for cell in values(notebook.cells_dict) - update_dependency_cache!(cell, notebook) + update_dependency_cache!(cell, notebook.topology) end end diff --git a/src/analysis/ExpressionExplorer.jl b/src/analysis/ExpressionExplorer.jl index cb54f70a0a..20c2a62b0b 100644 --- a/src/analysis/ExpressionExplorer.jl +++ b/src/analysis/ExpressionExplorer.jl @@ -309,8 +309,9 @@ function macro_has_special_heuristic_inside(; symstate::SymbolsState, expr::Expr module_usings_imports = ExpressionExplorer.compute_usings_imports(expr), ) local fake_topology = Pluto.NotebookTopology( - nodes = Pluto.DefaultDict(Pluto.ReactiveNode, Dict(fake_cell => fake_reactive_node)), - codes = Pluto.DefaultDict(Pluto.ExprAnalysisCache, Dict(fake_cell => fake_expranalysiscache)) + nodes = Pluto.ImmutableDefaultDict(Pluto.ReactiveNode, Dict(fake_cell => fake_reactive_node)), + codes = Pluto.ImmutableDefaultDict(Pluto.ExprAnalysisCache, Dict(fake_cell => fake_expranalysiscache)), + cell_order = Pluto.ImmutableVector([fake_cell]), ) return Pluto.cell_precedence_heuristic(fake_topology, fake_cell) < 9 diff --git a/src/analysis/MoreAnalysis.jl b/src/analysis/MoreAnalysis.jl index 232b5893e3..7db3f28b53 100644 --- a/src/analysis/MoreAnalysis.jl +++ b/src/analysis/MoreAnalysis.jl @@ -62,7 +62,7 @@ end function _upstream_recursive!(found::Set{Cell}, notebook::Notebook, topology::NotebookTopology, from::Vector{Cell})::Nothing for cell in from references = topology.nodes[cell].references - for upstream in Pluto.where_assigned(notebook, topology, references) + for upstream in Pluto.where_assigned(topology, references) if upstream ∉ found push!(found, upstream) _upstream_recursive!(found, notebook, topology, Cell[upstream]) diff --git a/src/analysis/Topology.jl b/src/analysis/Topology.jl index 7c26c0b8d7..655421706c 100644 --- a/src/analysis/Topology.jl +++ b/src/analysis/Topology.jl @@ -25,42 +25,29 @@ function ExprAnalysisCache(old_cache::ExprAnalysisCache; new_properties...) ExprAnalysisCache(;properties...) end -struct DefaultDict{K,V} <: AbstractDict{K,V} - default::Union{Function,DataType} - container::Dict{K,V} -end - - "The (information needed to create the) dependency graph of a notebook. Cells are linked by the names of globals that they define and reference. 🕸" Base.@kwdef struct NotebookTopology - nodes::DefaultDict{Cell,ReactiveNode} = DefaultDict{Cell,ReactiveNode}(ReactiveNode) - codes::DefaultDict{Cell,ExprAnalysisCache}=DefaultDict{Cell,ExprAnalysisCache}(ExprAnalysisCache) + nodes::ImmutableDefaultDict{Cell,ReactiveNode}=ImmutableDefaultDict{Cell,ReactiveNode}(ReactiveNode) + codes::ImmutableDefaultDict{Cell,ExprAnalysisCache}=ImmutableDefaultDict{Cell,ExprAnalysisCache}(ExprAnalysisCache) + cell_order::ImmutableVector{Cell}=ImmutableVector{Cell}() - unresolved_cells::Set{Cell} = Set{Cell}() + unresolved_cells::ImmutableSet{Cell} = ImmutableSet{Cell}() end +# BIG TODO HERE: CELL ORDER +all_cells(topology::NotebookTopology) = topology.cell_order.c is_resolved(topology::NotebookTopology) = isempty(topology.unresolved_cells) function set_unresolved(topology::NotebookTopology, unresolved_cells::Vector{Cell}) - codes = Dict{Cell,ExprAnalysisCache}(cell => ExprAnalysisCache(topology.codes[cell]; function_wrapped=false, forced_expr_id=nothing) for cell in unresolved_cells) - NotebookTopology(nodes=topology.nodes, codes=merge(topology.codes, codes), unresolved_cells=union(topology.unresolved_cells, unresolved_cells)) -end - -DefaultDict{K,V}(default::Union{Function,DataType}) where {K,V} = DefaultDict{K,V}(default, Dict{K,V}()) - -function Base.getindex(aid::DefaultDict{K,V}, key::K)::V where {K,V} - get!(aid.default, aid.container, key) -end -function Base.merge(a1::DefaultDict{K,V}, a2::DefaultDict{K,V}) where {K,V} - DefaultDict{K,V}(a1.default, merge(a1.container, a2.container)) -end -function Base.merge(a1::DefaultDict{K,V}, a2::AbstractDict) where {K,V} - DefaultDict{K,V}(a1.default, merge(a1.container, a2)) + codes = Dict{Cell,ExprAnalysisCache}( + cell => ExprAnalysisCache(topology.codes[cell]; function_wrapped=false, forced_expr_id=nothing) + for cell in unresolved_cells + ) + NotebookTopology( + nodes=topology.nodes, + codes=merge(topology.codes, codes), + unresolved_cells=union(topology.unresolved_cells, unresolved_cells), + cell_order = topology.cell_order, + ) end -Base.setindex!(aid::DefaultDict, args...) = Base.setindex!(aid.container, args...) -Base.delete!(aid::DefaultDict, args...) = Base.delete!(aid.container, args...) -Base.keys(aid::DefaultDict) = Base.keys(aid.container) -Base.values(aid::DefaultDict) = Base.values(aid.container) -Base.length(aid::DefaultDict) = Base.length(aid.container) -Base.iterate(aid::DefaultDict, args...) = Base.iterate(aid.container, args...) diff --git a/src/analysis/TopologyUpdate.jl b/src/analysis/TopologyUpdate.jl index 2378396eb7..0f9e1f4017 100644 --- a/src/analysis/TopologyUpdate.jl +++ b/src/analysis/TopologyUpdate.jl @@ -6,7 +6,7 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce updated_codes = Dict{Cell,ExprAnalysisCache}() updated_nodes = Dict{Cell,ReactiveNode}() - unresolved_cells = copy(old_topology.unresolved_cells) + unresolved_cells = copy(old_topology.unresolved_cells.c) for cell in cells old_code = old_topology.codes[cell] if old_code.code !== cell.code @@ -34,10 +34,20 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce new_nodes = merge(old_topology.nodes, updated_nodes) for removed_cell in setdiff(keys(old_topology.nodes), notebook.cells) - delete!(new_nodes, removed_cell) - delete!(new_codes, removed_cell) + delete_unsafe!(new_nodes, removed_cell) + delete_unsafe!(new_codes, removed_cell) delete!(unresolved_cells, removed_cell) end - - NotebookTopology(nodes=new_nodes, codes=new_codes, unresolved_cells=unresolved_cells) + + # TODO: this could be cached in a better way if it matters + cell_order = notebook.cells == old_topology.cell_order ? + old_topology.cell_order : # if the order is the same, we can reuse the old one + ImmutableVector(notebook.cells) # (this will do a shallow copy) + + NotebookTopology(; + nodes=new_nodes, + codes=new_codes, + unresolved_cells=ImmutableSet(unresolved_cells; skip_copy=true), + cell_order + ) end diff --git a/src/analysis/data structures.jl b/src/analysis/data structures.jl new file mode 100644 index 0000000000..6f0c2cb89b --- /dev/null +++ b/src/analysis/data structures.jl @@ -0,0 +1,110 @@ + +begin + """ + ```julia + ImmutableSet{T}(xs::Set{T}) + ``` + + Wraps around, and behaves like a regular `Set`, but mutating operations (like `push!` or `empty!`) are not allowed. + + When called on a set, a *shallow copy* of the set is stored. This means that it's fine to mutate the input set after creating an `ImmutableSet` from it. To prevent this, call `ImmutableSet(xs; skip_copy=true)`. + """ + struct ImmutableSet{T} <: AbstractSet{T} + c::Set{T} + ImmutableSet{T}(s::Set{T}; skip_copy::Bool=false) where T = new{T}(skip_copy ? s : copy(s)) + end + ImmutableSet(s::Set{T}; skip_copy::Bool=false) where T = ImmutableSet{T}(s; skip_copy) + + ImmutableSet(arg) = let + s = Set(arg) + ImmutableSet{eltype(s)}(s; skip_copy=true) + end + ImmutableSet{T}() where T = ImmutableSet{T}(Set{T}(); skip_copy=true) + ImmutableSet() = ImmutableSet(Set(); skip_copy=true) + + Base.copy(s::ImmutableSet) = ImmutableSet(copy(s.c)) + Base.in(x, s::ImmutableSet) = Base.in(x, s.c) + Base.isempty(s::ImmutableSet) = Base.isempty(s.c) + Base.length(s::ImmutableSet) = Base.length(s.c) + Base.iterate(s::ImmutableSet, i...) = Base.iterate(s.c, i...) + Base.setdiff(s::ImmutableSet, i...) = ImmutableSet(setdiff(s.c, i...)) +end + +begin + """ + ```julia + ImmutableVector{T}(xs::Vector{T}) + ``` + + Wraps around, and behaves like a regular `Vector`, but mutating operations (like `push!` or `setindex!`) are not allowed. + + When called on a vector, a *shallow copy* of the vector is stored. This means that it's fine to mutate the input vector after creating an `ImmutableVector` from it. To prevent this, call `ImmutableVector(xs; skip_copy=true)`. + """ + struct ImmutableVector{T} <: AbstractVector{T} + c::Vector{T} + ImmutableVector{T}(x; skip_copy::Bool=false) where T = new{T}(skip_copy ? x : copy(x)) + end + ImmutableVector(x::AbstractVector{T}; kwargs...) where T = ImmutableVector{T}(x; kwargs...) + ImmutableVector{T}() where T = ImmutableVector{T}(Vector{T}()) + + Base.copy(s::ImmutableVector) = ImmutableVector(copy(s.c)) + Base.in(x, s::ImmutableVector) = Base.in(x, s.c) + Base.isempty(s::ImmutableVector) = Base.isempty(s.c) + Base.length(s::ImmutableVector) = Base.length(s.c) + Base.size(s::ImmutableVector) = Base.size(s.c) + Base.iterate(s::ImmutableVector, i...) = Base.iterate(s.c, i...) + Base.getindex(s::ImmutableVector, i::Integer) = Base.getindex(s.c, i) + Base.getindex(s::ImmutableVector, i...) = ImmutableVector(Base.getindex(s.c, i...)) + delete_unsafe!(s::ImmutableSet, args...) = Base.delete!(s.c, args...) +end + +begin + """ + ```julia + ImmutableDefaultDict{K,V}(default::Function, container::Dict{K,V}) + ``` + + Wraps around, and behaves like a regular `Dict`, but if a key is not found, it will call return `default()`. + """ + struct ImmutableDefaultDict{K,V} <: AbstractDict{K,V} + default::Union{Function,DataType} + container::Dict{K,V} + end + + ImmutableDefaultDict{K,V}(default::Union{Function,DataType}) where {K,V} = ImmutableDefaultDict{K,V}(default, Dict{K,V}()) + + function Base.getindex(aid::ImmutableDefaultDict{K,V}, key::K)::V where {K,V} + get!(aid.default, aid.container, key) + end + function Base.merge(a1::ImmutableDefaultDict{K,V}, a2::ImmutableDefaultDict{K,V}) where {K,V} + ImmutableDefaultDict{K,V}(a1.default, merge(a1.container, a2.container)) + end + function Base.merge(a1::ImmutableDefaultDict{K,V}, a2::AbstractDict) where {K,V} + ImmutableDefaultDict{K,V}(a1.default, merge(a1.container, a2)) + end + # disabled because it's immutable! + # Base.setindex!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.setindex!(aid.container, args...) + # Base.delete!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.delete!(aid.container, args...) + delete_unsafe!(aid::ImmutableDefaultDict{K,V}, args...) where {K,V} = Base.delete!(aid.container, args...) + Base.copy(aid::ImmutableDefaultDict{K,V}) where {K,V} = ImmutableDefaultDict{K,V}(aid.default, copy(aid.container)) + Base.keys(aid::ImmutableDefaultDict) = Base.keys(aid.container) + Base.values(aid::ImmutableDefaultDict) = Base.values(aid.container) + Base.length(aid::ImmutableDefaultDict) = Base.length(aid.container) + Base.iterate(aid::ImmutableDefaultDict, args...) = Base.iterate(aid.container, args...) +end + +""" +```julia +setdiffkeys(d::Dict{K,V}, key_itrs...)::Dict{K,V} +``` + +Apply `setdiff` on the keys of a dictionary. + +# Example +```julia +setdiffkeys(Dict(1 => "one", 2 => "two", 3 => "three"), [1, 3]) +# result: `Dict(2 => "two")` +``` +""" +setdiffkeys(d::Dict{K,V}, key_itrs...) where {K,V} = Dict{K,V}(k => d[k] for k in setdiff(keys(d), key_itrs...)) +setdiffkeys(d::ImmutableDefaultDict{K,V}, key_itrs...) where {K,V} = ImmutableDefaultDict{K,V}(d.default, setdiffkeys(d.container, key_itrs...)) diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index e95bf456f5..534909663a 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -5,8 +5,10 @@ struct Cycle <: ChildExplorationResult cycled_cells::Vector{Cell} end +@deprecate topological_order(::Notebook, topology::NotebookTopology, args...; kwargs...) topological_order(topology, args...; kwargs...) + "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::Vector{Cell}; allow_multiple_defs=false)::TopologicalOrder +function topological_order(topology::NotebookTopology, roots::AbstractVector{Cell}; allow_multiple_defs=false)::TopologicalOrder entries = Cell[] exits = Cell[] errable = Dict{Cell,ReactivityError}() @@ -39,13 +41,13 @@ function topological_order(notebook::Notebook, topology::NotebookTopology, roots push!(entries, cell) - assigners = where_assigned(notebook, topology, cell) + assigners = where_assigned(topology, cell) if !allow_multiple_defs && length(assigners) > 1 for c in assigners errable[c] = MultipleDefinitionsError(topology, c, assigners) end end - referencers = where_referenced(notebook, topology, cell) |> Iterators.reverse + referencers = where_referenced(topology, cell) |> Iterators.reverse for c in (allow_multiple_defs ? referencers : union(assigners, referencers)) if c != cell child_result = bfs(c) @@ -96,7 +98,7 @@ end function topological_order(notebook::Notebook) cached = notebook._cached_topological_order if cached === nothing || cached.input_topology !== notebook.topology - topological_order(notebook, notebook.topology, notebook.cells) + topological_order(notebook.topology, all_cells(notebook.topology)) else cached end @@ -109,16 +111,17 @@ function disjoint(a::Set, b::Set) end "Return the cells that reference any of the symbols defined by the given cell. Non-recursive: only direct dependencies are found." -function where_referenced(notebook::Notebook, topology::NotebookTopology, myself::Cell)::Vector{Cell} +function where_referenced(topology::NotebookTopology, myself::Cell)::Vector{Cell} to_compare = union(topology.nodes[myself].definitions, topology.nodes[myself].soft_definitions, topology.nodes[myself].funcdefs_without_signatures) - where_referenced(notebook, topology, to_compare) + where_referenced(topology, to_compare) end "Return the cells that reference any of the given symbols. Non-recursive: only direct dependencies are found." -function where_referenced(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} - return filter(notebook.cells) do cell +function where_referenced(topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} + return filter(all_cells(topology)) do cell !disjoint(to_compare, topology.nodes[cell].references) end end +where_referenced(::Notebook, args...) = where_referenced(args...) "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) @@ -132,9 +135,9 @@ 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)::Vector{Cell} +function where_assigned(topology::NotebookTopology, myself::Cell)::Vector{Cell} self = topology.nodes[myself] - return filter(notebook.cells) do cell + return filter(all_cells(topology)) do cell other = topology.nodes[cell] !( disjoint(self.definitions, other.definitions) && @@ -147,8 +150,8 @@ function where_assigned(notebook::Notebook, topology::NotebookTopology, myself:: end end -function where_assigned(notebook::Notebook, topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} - filter(notebook.cells) do cell +function where_assigned(topology::NotebookTopology, to_compare::Set{Symbol})::Vector{Cell} + filter(all_cells(topology)) do cell other = topology.nodes[cell] !( disjoint(to_compare, other.definitions) && @@ -156,6 +159,8 @@ function where_assigned(notebook::Notebook, topology::NotebookTopology, to_compa ) end end +where_assigned(::Notebook, args...) = where_assigned(args...) + "Return whether any cell references the given symbol. Used for the @bind mechanism." function is_referenced_anywhere(notebook::Notebook, topology::NotebookTopology, sym::Symbol)::Bool diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 2ad0b3156b..244eb1582c 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -39,23 +39,24 @@ function run_reactive!(session::ServerSession, notebook::Notebook, old_topology: Dict(cell => ExprAnalysisCache() for cell in removed_cells) ), unresolved_cells = new_topology.unresolved_cells, + cell_order = new_topology.cell_order, ) # save the old topological order - we'll delete variables assigned from it and re-evalutate its cells unless the cells have already run previously in the reactive run - old_order = topological_order(notebook, old_topology, roots) + old_order = topological_order(old_topology, roots) old_runnable = setdiff(old_order.runnable, already_run) to_delete_vars = union!(Set{Symbol}(), defined_variables(old_topology, old_runnable)...) to_delete_funcs = union!(Set{Tuple{UUID,FunctionName}}(), defined_functions(old_topology, old_runnable)...) # get the new topological order - new_order = topological_order(notebook, new_topology, union(roots, keys(old_order.errable))) + new_order = topological_order(new_topology, union(roots, keys(old_order.errable))) new_runnable = setdiff(new_order.runnable, already_run) to_run_raw = setdiff(union(new_runnable, old_runnable), keys(new_order.errable))::Vector{Cell} # TODO: think if old error cell order matters # find (indirectly) deactivated cells and update their status deactivated = filter(c -> c.running_disabled, notebook.cells) - indirectly_deactivated = collect(topological_order(notebook, new_topology, deactivated)) + indirectly_deactivated = collect(topological_order(new_topology, deactivated)) for cell in indirectly_deactivated cell.running = false cell.queued = false @@ -250,7 +251,12 @@ is_macro_identifier(symbol::Symbol) = startswith(string(symbol), "@") function with_new_soft_definitions(topology::NotebookTopology, cell::Cell, soft_definitions) old_node = topology.nodes[cell] new_node = union!(ReactiveNode(), old_node, ReactiveNode(soft_definitions=soft_definitions)) - NotebookTopology(codes=topology.codes, nodes=merge(topology.nodes, Dict(cell => new_node)), unresolved_cells=topology.unresolved_cells) + NotebookTopology( + codes=topology.codes, + nodes=merge(topology.nodes, Dict(cell => new_node)), + unresolved_cells=topology.unresolved_cells, + cell_order=topology.cell_order, + ) end collect_implicit_usings(topology::NotebookTopology, cell::Cell) = ExpressionExplorer.collect_implicit_usings(topology.codes[cell].module_usings_imports) @@ -374,7 +380,12 @@ function resolve_topology( all_nodes = merge(unresolved_topology.nodes, new_nodes) all_codes = merge(unresolved_topology.codes, new_codes) - NotebookTopology(nodes=all_nodes, codes=all_codes, unresolved_cells=still_unresolved_nodes) + NotebookTopology( + nodes=all_nodes, + codes=all_codes, + unresolved_cells=ImmutableSet(still_unresolved_nodes; skip_copy=true), + cell_order=unresolved_topology.cell_order, + ) end """Tries to add information about macro calls without running any code, using knowledge about common macros. @@ -393,7 +404,12 @@ function static_resolve_topology(topology::NotebookTopology) new_nodes = Dict{Cell,ReactiveNode}(cell => static_macroexpand(topology, cell) for cell in topology.unresolved_cells) all_nodes = merge(topology.nodes, new_nodes) - NotebookTopology(nodes=all_nodes, codes=topology.codes, unresolved_cells=topology.unresolved_cells) + NotebookTopology( + nodes=all_nodes, + codes=topology.codes, + unresolved_cells=topology.unresolved_cells, + cell_order=topology.cell_order, + ) end ### @@ -444,11 +460,14 @@ function update_save_run!(session::ServerSession, notebook::Notebook, cells::Vec end # for the remaining cells, clear their topology info so that they won't run as dependencies - for cell in setdiff(to_run_online, setup_cells) - delete!(notebook.topology.nodes, cell) - delete!(notebook.topology.codes, cell) - delete!(notebook.topology.unresolved_cells, cell) - end + old = notebook.topology + to_remove = setdiff(to_run_online, setup_cells) + notebook.topology = NotebookTopology( + nodes=setdiffkeys(old.nodes, to_remove), + codes=setdiffkeys(old.codes, to_remove), + unresolved_cells=setdiff(old.unresolved_cells, to_remove), + cell_order=old.cell_order, + ) # and don't run them to_run_online = to_run_online ∩ setup_cells diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index c1338d0a15..034ff748ea 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -26,7 +26,7 @@ Base.@kwdef mutable struct Notebook path::String notebook_id::UUID=uuid1() - topology::NotebookTopology=NotebookTopology() + topology::NotebookTopology _cached_topological_order::Union{Nothing,TopologicalOrder}=nothing # buffer will contain all unfetched updates - must be big enough @@ -56,26 +56,33 @@ Base.@kwdef mutable struct Notebook bonds::Dict{Symbol,BondValue}=Dict{Symbol,BondValue}() end -Notebook(cells::Vector{Cell}, path::AbstractString, notebook_id::UUID) = Notebook( +_collect_cells(cells_dict::Dict{UUID,Cell}, cells_order::Vector{UUID}) = + map(i -> cells_dict[i], cells_order) +_initial_topology(cells_dict::Dict{UUID,Cell}, cells_order::Vector{UUID}) = + NotebookTopology(; + cell_order=ImmutableVector(_collect_cells(cells_dict, cells_order)), + ) + +function Notebook(cells::Vector{Cell}, path::AbstractString, notebook_id::UUID) cells_dict=Dict(map(cells) do cell (cell.cell_id, cell) - end), - cell_order=map(x -> x.cell_id, cells), - path=path, - notebook_id=notebook_id, -) + end) + cell_order=map(x -> x.cell_id, cells) + Notebook(; + cells_dict, cell_order, + topology=_initial_topology(cells_dict, cell_order), + path=path, + notebook_id=notebook_id, + ) +end Notebook(cells::Vector{Cell}, path::AbstractString=numbered_until_new(joinpath(new_notebooks_directory(), cutename()))) = Notebook(cells, path, uuid1()) function Base.getproperty(notebook::Notebook, property::Symbol) if property == :cells - cells_dict = getfield(notebook, :cells_dict) - cell_order = getfield(notebook, :cell_order) - map(cell_order) do id - cells_dict[id] - end + _collect_cells(notebook.cells_dict, notebook.cell_order) elseif property == :cell_inputs - getfield(notebook, :cells_dict) + notebook.cells_dict else getfield(notebook, property) end @@ -279,7 +286,14 @@ function load_notebook_nobackup(io, path)::Notebook k ∈ appeared_order end - Notebook(cells_dict=appeared_cells_dict, cell_order=appeared_order, path=path, nbpkg_ctx=nbpkg_ctx, nbpkg_installed_versions_cache=nbpkg_cache(nbpkg_ctx)) + Notebook(; + cells_dict=appeared_cells_dict, + cell_order=appeared_order, + topology=_initial_topology(appeared_cells_dict, appeared_order), + path=path, + nbpkg_ctx=nbpkg_ctx, + nbpkg_installed_versions_cache=nbpkg_cache(nbpkg_ctx), + ) end function load_notebook_nobackup(path::String)::Notebook @@ -307,7 +321,7 @@ function load_notebook(path::String; disable_writing_notebook_files::Bool=false) update_dependency_cache!(loaded) disable_writing_notebook_files || save_notebook(loaded) - loaded.topology = NotebookTopology() + loaded.topology = NotebookTopology(; cell_order=ImmutableVector(loaded.cells)) disable_writing_notebook_files || if only_versions_or_lineorder_differ(path, backup_path) rm(backup_path) diff --git a/test/React.jl b/test/React.jl index 7c74f513e5..856bb447f9 100644 --- a/test/React.jl +++ b/test/React.jl @@ -148,11 +148,11 @@ import Distributed ]) notebook.topology = Pluto.updated_topology(notebook.topology, notebook, notebook.cells) - let topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells[[1]]) + let topo_order = Pluto.topological_order(notebook.topology, notebook.cells[[1]]) @test indexin(topo_order.runnable, notebook.cells) == [1,2] @test topo_order.errable |> keys == notebook.cells[[3,4]] |> Set end - let topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells[[1]], allow_multiple_defs=true) + let topo_order = Pluto.topological_order(notebook.topology, notebook.cells[[1]], allow_multiple_defs=true) @test indexin(topo_order.runnable, notebook.cells) == [1,3,4,2] # x first, y second and third, z last # this also tests whether multiple defs run in page order @test topo_order.errable == Dict() @@ -204,14 +204,14 @@ import Distributed ]) notebook.topology = Pluto.updated_topology(notebook.topology, notebook, notebook.cells) - topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells) + topo_order = Pluto.topological_order(notebook.topology, notebook.cells) @test indexin(topo_order.runnable, notebook.cells) == [6, 5, 4, 7, 3, 1, 2, 8] # 6, 5, 4, 3 should run first (this is implemented using `cell_precedence_heuristic`), in that order # 1, 2, 7 remain, and should run in notebook order. # if the cells were placed in reverse order... reverse!(notebook.cell_order) - topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells) + topo_order = Pluto.topological_order(notebook.topology, notebook.cells) @test indexin(topo_order.runnable, reverse(notebook.cells)) == [6, 5, 4, 7, 3, 8, 2, 1] # 6, 5, 4, 3 should run first (this is implemented using `cell_precedence_heuristic`), in that order # 1, 2, 7 remain, and should run in notebook order, which is 7, 2, 1. @@ -233,7 +233,7 @@ import Distributed notebook.topology = Pluto.updated_topology(notebook.topology, notebook, notebook.cells) - topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells) + topo_order = Pluto.topological_order(notebook.topology, notebook.cells) comesbefore(A, first, second) = findfirst(isequal(first),A) < findfirst(isequal(second), A) @@ -261,7 +261,7 @@ import Distributed ]) notebook.topology = Pluto.updated_topology(notebook.topology, notebook, notebook.cells) - topo_order = Pluto.topological_order(notebook, notebook.topology, notebook.cells) + topo_order = Pluto.topological_order(notebook.topology, notebook.cells) run_order = indexin(topo_order.runnable, notebook.cells) @test run_order == [3, 1, 2] diff --git a/test/data structures.jl b/test/data structures.jl new file mode 100644 index 0000000000..4a532e9a9c --- /dev/null +++ b/test/data structures.jl @@ -0,0 +1,104 @@ +import Pluto: ImmutableVector, ImmutableSet, ImmutableDefaultDict, setdiffkeys + +@testset "ImmutableCollections" begin + + + + + +# ╔═╡ bd27d82e-62d6-422c-8fbe-61993dc4c268 +@test isempty(ImmutableVector{Int}()) + +# ╔═╡ d4f2016a-b093-4619-9ccb-3e99bf6fdc9b +@test ImmutableVector{Int32}([1,2,3]).c |> eltype == Int32 + +# ╔═╡ 055f21c0-3741-4762-ac4e-4c89633afbc4 +let + x = [1,2,3] + y = ImmutableVector(x) + push!(x,4) + @test y == [1,2,3] +end + +# ╔═╡ 52310ade-6e06-4ab8-8589-444c161cd93b +let + x = [1,2,3] + y = ImmutableVector{Int32}(x) + push!(x,4) + @test y == [1,2,3] +end + +# ╔═╡ d61600f0-2202-4228-8d35-380f732214e7 +ImmutableSet() + +# ╔═╡ d3871580-cd22-48c1-a1fd-d13a7f2f2135 +ImmutableSet{Int}() + +# ╔═╡ 2af00467-8bbf-49d8-bfe3-6f8d6307e900 +ImmutableSet{Int64}(Set([1,2]); skip_copy=true) + +# ╔═╡ 46836112-7c5c-4ffd-8d1e-93a2c8990b20 +@test ImmutableSet{Int64}(Set([1,2]); skip_copy=true) == Set([1,2]) + +# ╔═╡ fd687b2e-8bec-48b2-810e-38ef00bf567b +let + x = [1.1,2,3] + y = ImmutableVector(x; skip_copy=true) + push!(x,4) + @test y == [1.1,2,3,4] +end + +# ╔═╡ f4dddf0b-cf0a-41d0-880e-6a8fac7c60cb +let + x = [1.1,2,3] + y = ImmutableVector{Float64}(x; skip_copy=true) + push!(x,4) + @test y == [1.1,2,3,4] +end + +# ╔═╡ 25c78371-f12d-44ae-b180-32b88d3aa4f5 +@test eltype(ImmutableSet([2,3,4])) == Int + +# ╔═╡ 45115ac6-6586-458c-83e6-d661c2ce8db2 +let + x = Set([1,2,3]) + y = ImmutableSet(x) + push!(x,4) + @test y == Set([1,2,3]) +end + +# ╔═╡ 4f26640d-31d2-44c4-bbba-82c18d7497ae +let + x = Set([1.1,2,3]) + y = ImmutableSet(x; skip_copy=true) + push!(x,4) + @test y == Set([1.1,2,3,4]) +end + +# ╔═╡ eac9c95b-a2b6-4f1f-8cce-a2ad4c0972c5 +@test union(ImmutableSet([1,2]),[2,3]) == ImmutableSet([1,2,3]) + +# ╔═╡ bff65a2c-8654-4403-8e34-58aac8616729 +@test filter(x -> true, ImmutableVector([1,2,3])) == [1,2,3] + +# ╔═╡ ce3cdb24-e851-4cc3-9955-b34fe358b41a +@test ImmutableVector([1,2,3])[2:end] isa ImmutableVector + +# ╔═╡ c61196d6-f529-4883-b334-ed1b0f653acf + + +# ╔═╡ 5c2b3440-7231-42df-b4e5-619001d225a8 +ImmutableSet([123,234]) + + + +@test setdiffkeys(Dict(1=>2,3=>4),[3]) == Dict(1=>2) + +let + d = setdiffkeys(ImmutableDefaultDict(() -> 7, Dict(1=>2,3=>4)),[3]) + @test d[1] == 2 && d[3] == 7 +end + +@test setdiff(ImmutableSet([1,2]), [2]) isa ImmutableSet + +end \ No newline at end of file diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index 25c8f61210..bf61b8fc79 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -372,7 +372,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; ]) fakeclient.connected_notebook = notebook - notebook.topology = Pluto.updated_topology(Pluto.NotebookTopology(), notebook, notebook.cells) |> Pluto.static_resolve_topology + notebook.topology = Pluto.updated_topology(Pluto.NotebookTopology(cell_order=Pluto.ImmutableVector(notebook.cells)), notebook, notebook.cells) |> Pluto.static_resolve_topology @test !Pluto.use_plutopkg(notebook.topology) order = collect(Pluto.topological_order(notebook)) diff --git a/test/runtests.jl b/test/runtests.jl index ba9ea13bff..62622f7e63 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,6 +35,7 @@ include("./MethodSignatures.jl") include("./MoreAnalysis.jl") include("./Analysis.jl") include("./webserver_utils.jl") +include("./data structures.jl") include("./DependencyCache.jl") include("./Throttled.jl") include("./cell_disabling.jl") From d34e2a8f9228b21368a29a980cc10cf45de02e2f Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 12:59:13 +0100 Subject: [PATCH 115/821] whitespace --- frontend/components/CellInput.js | 6 +++--- frontend/components/CellInput/mixedParsers.js | 8 ++++---- frontend/components/CellOutput.js | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index b286692577..e68866dc04 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -49,7 +49,7 @@ import { pythonLanguage, } from "../imports/CodemirrorPlutoSetup.js" -import { markdown, html as htmlLang, javascript, sqlLang, python, julia_andrey } from "./CellInput/mixedParsers.js" +import { markdown, html as htmlLang, javascript, sqlLang, python, julia_mixed } from "./CellInput/mixedParsers.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" import { awesome_line_wrapping } from "./CellInput/awesome_line_wrapping.js" @@ -618,9 +618,9 @@ export const CellInput = ({ }), EditorState.tabSize.of(4), indentUnit.of("\t"), - julia_andrey(), + julia_mixed(), markdown({ - defaultCodeLanguage: julia_andrey(), + defaultCodeLanguage: julia_mixed(), }), htmlLang(), //Provides tag closing!, javascript(), diff --git a/frontend/components/CellInput/mixedParsers.js b/frontend/components/CellInput/mixedParsers.js index aab8abba04..b2434945da 100644 --- a/frontend/components/CellInput/mixedParsers.js +++ b/frontend/components/CellInput/mixedParsers.js @@ -12,7 +12,7 @@ import { sql, javascript, python, - julia_andrey as julia_andrey_original, + julia_andrey, parseCode, } from "../../imports/CodemirrorPlutoSetup.js" @@ -161,11 +161,11 @@ const juliaWrapper = parseMixed((node, input) => { return { parser, overlay } }) -const julia_andrey = (config) => { - const julia = julia_andrey_original(config) +const julia_mixed = (config) => { + const julia = julia_andrey(config) // @ts-ignore julia.language.parser = julia.language.parser.configure({ wrap: juliaWrapper }) return julia } -export { julia_andrey, sqlLang, pythonLanguage, javascript, htmlLanguage, javascriptLanguage, python, markdown, html } +export { julia_mixed, sqlLang, pythonLanguage, javascript, htmlLanguage, javascriptLanguage, python, markdown, html } diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index 029dd4e4f4..d873f6e85f 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -16,7 +16,7 @@ import { pluto_syntax_colors } from "./CellInput.js" import { useState } from "../imports/Preact.js" import hljs from "../imports/highlightjs.js" -import { julia_andrey } from "./CellInput/mixedParsers.js" +import { julia_mixed } from "./CellInput/mixedParsers.js" export class CellOutput extends Component { constructor() { @@ -514,7 +514,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_mixed() : null, EditorView.lineWrapping, EditorView.editable.of(false), ].filter((x) => x != null), From 6f6a852a9ca2830ac58f14b6678b71fe72598e6d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 12:59:25 +0100 Subject: [PATCH 116/821] whitespace --- frontend/components/CellOutput.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index d873f6e85f..f640a2cec9 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -466,10 +466,9 @@ export let RawHTMLContainer = ({ body, className = "", persist_js_state = false, container.current.querySelectorAll("code").forEach((code_element) => { code_element.classList.forEach((className) => { if (className.startsWith("language-")) { - let language = className.substr(9) - // Remove "language-" - highlight(code_element, language) + let language = className.substring(9) + // highlight(code_element, language) } }) }) From e6c5a3679deaf7081178422bfa1e8fadfab4c24c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 13:14:27 +0100 Subject: [PATCH 117/821] update highlightjs --- frontend/components/CellOutput.js | 6 ++++-- frontend/highlightjs.css | 5 ++++- frontend/imports/highlightjs.js | 8 +++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index f640a2cec9..e4593b566b 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -468,11 +468,13 @@ export let RawHTMLContainer = ({ body, className = "", persist_js_state = false, if (className.startsWith("language-")) { // Remove "language-" let language = className.substring(9) - // highlight(code_element, language) + highlight(code_element, language) } }) }) - } catch (err) {} + } catch (err) { + console.warn("Highlighting failed", err) + } } finally { js_init_set?.delete(container.current) } diff --git a/frontend/highlightjs.css b/frontend/highlightjs.css index 7ae6ec2dac..922c4cdd0c 100644 --- a/frontend/highlightjs.css +++ b/frontend/highlightjs.css @@ -50,12 +50,15 @@ code.hljs { } .hljs-bullet, .hljs-link, -.hljs-meta, .hljs-selector-id, .hljs-symbol, .hljs-title { color: var(--cm-link-color); } +.hljs-meta { + color: var(--cm-macro-color); + font-weight: 700; +} .hljs-built_in, .hljs-class .hljs-title, .hljs-title.class_ { diff --git a/frontend/imports/highlightjs.js b/frontend/imports/highlightjs.js index 15fd1190ff..17ffd64503 100644 --- a/frontend/imports/highlightjs.js +++ b/frontend/imports/highlightjs.js @@ -1,13 +1,11 @@ // @ts-ignore -import hljs from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.3.1/build/es/highlight.min.js" +import hljs from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/es/highlight.min.js" // @ts-ignore -import hljs_julia from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.3.1/build/es/languages/julia.min.js" +import hljs_julia from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/es/languages/julia.min.js" // @ts-ignore -import hljs_juliarepl from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.3.1/build/es/languages/julia-repl.min.js" +import hljs_juliarepl from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/es/languages/julia-repl.min.js" hljs.registerLanguage("julia", hljs_julia) hljs.registerLanguage("julia-repl", hljs_juliarepl) -// https://github.com/highlightjs/highlight.js/pull/3432 -hljs.registerAliases(["jldoctest"], { languageName: "julia-repl" }) export default hljs From 476b37a63dfe49808efa3362964034aba124cca0 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 13:14:37 +0100 Subject: [PATCH 118/821] css tweak --- frontend/light_color.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/light_color.css b/frontend/light_color.css index 42244c8ad3..252d0782cd 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -132,7 +132,7 @@ --helpbox-notfound-header-color: rgb(139, 139, 139); --helpbox-text-color: black; --code-section-bg-color: whitesmoke; - --code-section-bg-color: #dbdbdb; + --code-section-bg-color: #f3f3f3; /*footer*/ --footer-color: #333333; From cc67e2f4c475915a71fdf23fc97725584f381566 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Tue, 22 Feb 2022 07:23:01 -0500 Subject: [PATCH 119/821] Update WebServer.jl (#1933) --- src/webserver/WebServer.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index d20c57d45a..2ce23baa5e 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -55,6 +55,8 @@ For the full list, see the [`Pluto.Configuration`](@ref) module. Some **common p - `launch_browser`: Optional. Whether to launch the system default browser. Disable this on SSH and such. - `host`: Optional. The default `host` is `"127.0.0.1"`. For wild setups like Docker and heroku, you might need to change this to `"0.0.0.0"`. - `port`: Optional. The default `port` is `1234`. +- `auto_reload_from_file`: Reload when the `.jl` file is modified. The default is `false`. +- `secret`: Set a fixed secret for access. ## Technobabble From a57727e9ea94f2d98d06c50f5868ac507958c561 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 14:39:37 +0100 Subject: [PATCH 120/821] =?UTF-8?q?=F0=9F=99=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/CellInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index e68866dc04..6fc78bb1ce 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -50,6 +50,7 @@ import { } from "../imports/CodemirrorPlutoSetup.js" import { markdown, html as htmlLang, javascript, sqlLang, python, julia_mixed } from "./CellInput/mixedParsers.js" +import { julia_andrey } from "../imports/CodemirrorPlutoSetup.js" import { pluto_autocomplete } from "./CellInput/pluto_autocomplete.js" import { NotebookpackagesFacet, pkgBubblePlugin } from "./CellInput/pkg_bubble_plugin.js" import { awesome_line_wrapping } from "./CellInput/awesome_line_wrapping.js" From f633e5b43c046458fcaec2aa9068c4a86c825b43 Mon Sep 17 00:00:00 2001 From: Rik Huijzer Date: Tue, 22 Feb 2022 14:51:28 +0100 Subject: [PATCH 121/821] Use `precompile` for `SessionActions.open` (#1934) --- src/webserver/SessionActions.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index 6eb3393173..802f56fc06 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -137,6 +137,7 @@ function add(session::ServerSession, nb::Notebook; run_async::Bool=true) nb end +precompile(SessionActions.open, (ServerSession, String)) function save_upload(content::Vector{UInt8}) save_path = emptynotebook().path From 5c3c4688c236c9496e99962319de8e957828ca89 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 16:59:35 +0100 Subject: [PATCH 122/821] =?UTF-8?q?=F0=9F=90=A6=20Fix=20#1894?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/editor.css | 1 + frontend/index.css | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/editor.css b/frontend/editor.css index ef3d334d38..767725608b 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2488,6 +2488,7 @@ body > nav#slide_controls > button.next > span::after { .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { /* this is the line height rounded to an integer to prevent jiggle */ height: 16px; + overflow-y: hidden; /* font-size: 16px; */ line-height: 16px; border-radius: 3px; diff --git a/frontend/index.css b/frontend/index.css index 28ec2b92b1..e3ada1c1a8 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -175,6 +175,7 @@ pluto-filepicker button { .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { /* this is the line height rounded to an integer to prevent jiggle */ height: 16px; + overflow-y: hidden; /* font-size: 16px; */ line-height: 16px; border-radius: 3px; From 728be33496ccb73171b3b1a64b7a95f0ab09747a Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 17:02:31 +0100 Subject: [PATCH 123/821] whitespace? (#1942) --- src/evaluation/Run.jl | 29 ++++++++++++++++++++++++++--- src/evaluation/WorkspaceManager.jl | 4 ++-- src/runner/PlutoRunner.jl | 4 +++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 244eb1582c..8da19e73e0 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -6,7 +6,16 @@ import .WorkspaceManager: macroexpand_in_workspace Base.push!(x::Set{Cell}) = x "Run given cells and all the cells that depend on them, based on the topology information before and after the changes." -function run_reactive!(session::ServerSession, notebook::Notebook, old_topology::NotebookTopology, new_topology::NotebookTopology, roots::Vector{Cell}; deletion_hook::Function = WorkspaceManager.move_vars, user_requested_run::Bool = true, already_in_run::Bool = false, already_run::Vector{Cell} = Cell[])::TopologicalOrder +function run_reactive!( + session::ServerSession, + notebook::Notebook, + old_topology::NotebookTopology, new_topology::NotebookTopology, + roots::Vector{Cell}; + deletion_hook::Function = WorkspaceManager.move_vars, + user_requested_run::Bool = true, + already_in_run::Bool = false, + already_run::Vector{Cell} = Cell[] +)::TopologicalOrder if !already_in_run # make sure that we're the only `run_reactive!` being executed - like a semaphor take!(notebook.executetoken) @@ -192,7 +201,13 @@ function defined_functions(topology::NotebookTopology, cells) end "Run a single cell non-reactively, set its output, return run information." -function run_single!(session_notebook::Union{Tuple{ServerSession,Notebook},WorkspaceManager.Workspace}, cell::Cell, reactive_node::ReactiveNode, expr_cache::ExprAnalysisCache; user_requested_run::Bool=true) +function run_single!( + session_notebook::Union{Tuple{ServerSession,Notebook},WorkspaceManager.Workspace}, + cell::Cell, + reactive_node::ReactiveNode, + expr_cache::ExprAnalysisCache; + user_requested_run::Bool=true +) run = WorkspaceManager.eval_format_fetch_in_workspace( session_notebook, expr_cache.parsedcode, @@ -418,7 +433,15 @@ end "Do all the things!" -function update_save_run!(session::ServerSession, notebook::Notebook, cells::Vector{Cell}; save::Bool=true, run_async::Bool=false, prerender_text::Bool=false, kwargs...) +function update_save_run!( + session::ServerSession, + notebook::Notebook, + cells::Vector{Cell}; + save::Bool=true, + run_async::Bool=false, + prerender_text::Bool=false, + kwargs... +) old = notebook.topology new = notebook.topology = updated_topology(old, notebook, cells) # macros are not yet resolved diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index 6ce1cbde2e..236f04417f 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -344,7 +344,7 @@ function eval_format_fetch_in_workspace( forced_expr_id::Union{PlutoRunner.ObjectID,Nothing}=nothing, user_requested_run::Bool=true, known_published_objects::Vector{String}=String[], -)::NamedTuple{(:output_formatted, :errored, :interrupted, :process_exited, :runtime, :published_objects, :has_pluto_hook_features),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Bool,Union{UInt64,Nothing},Dict{String,Any},Bool}} +)::PlutoRunner.FormattedCellResult workspace = get_workspace(session_notebook) @@ -397,7 +397,7 @@ function format_fetch_in_workspace( ends_with_semicolon, known_published_objects::Vector{String}=String[], showmore_id::Union{PlutoRunner.ObjectDimPair,Nothing}=nothing, -)::NamedTuple{(:output_formatted, :errored, :interrupted, :process_exited, :runtime, :published_objects, :has_pluto_hook_features),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Bool,Union{UInt64,Nothing},Dict{String,Any},Bool}} +)::PlutoRunner.FormattedCellResult workspace = get_workspace(session_notebook) # instead of fetching the output value (which might not make sense in our context, since the user can define structs, types, functions, etc), we format the cell output on the worker, and fetch the formatted output. diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index 2e0204b714..43a5eddff5 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -760,13 +760,15 @@ const table_column_display_limit_increase = 30 const tree_display_extra_items = Dict{UUID,Dict{ObjectDimPair,Int64}}() +const FormattedCellResult = NamedTuple{(:output_formatted, :errored, :interrupted, :process_exited, :runtime, :published_objects, :has_pluto_hook_features),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Bool,Union{UInt64,Nothing},Dict{String,Any},Bool}} + function formatted_result_of( cell_id::UUID, ends_with_semicolon::Bool, known_published_objects::Vector{String}=String[], showmore::Union{ObjectDimPair,Nothing}=nothing, workspace::Module=Main, -)::NamedTuple{(:output_formatted, :errored, :interrupted, :process_exited, :runtime, :published_objects, :has_pluto_hook_features),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Bool,Union{UInt64,Nothing},Dict{String,Any},Bool}} +)::FormattedCellResult load_integrations_if_needed() currently_running_cell_id[] = cell_id From c173673778969c23aa22f8b04f605017765bb96c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 18:28:31 +0100 Subject: [PATCH 124/821] Update Test.yml --- .github/workflows/Test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index f5a53091da..ee987c91e3 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -16,6 +16,9 @@ on: pull_request: paths-ignore: - "frontend/**" + - "frontend-bundler/**" + - "frontend-dist/**" + - "test/frontend/**" - "**.md" branches-ignore: - release From 31625b1e61a1496e14583e6c91591ae3762c1d04 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 22 Feb 2022 21:51:06 +0100 Subject: [PATCH 125/821] =?UTF-8?q?=F0=9F=8F=83=20Don't=20use=20body.class?= =?UTF-8?q?List=20for=20ctrl=5Fdown=20to=20avoid=20style=20recalc=20(#1945?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/Editor.js | 18 ++++++++++++++---- frontend/editor.css | 10 +++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 48c81b5433..777a99f211 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -974,18 +974,28 @@ patch: ${JSON.stringify( this.patch_listeners.forEach((f) => f(patches)) } + let ctrl_down_last_val = { current: false } + const set_ctrl_down = (value) => { + if (value !== ctrl_down_last_val.current) { + ctrl_down_last_val.current = value + document.body.querySelectorAll("pluto-variable-link").forEach((el) => { + el.setAttribute("data-ctrl-down", value ? "true" : "false") + }) + } + } + document.addEventListener("keyup", (e) => { - document.body.classList.toggle("ctrl_down", has_ctrl_or_cmd_pressed(e)) + set_ctrl_down(has_ctrl_or_cmd_pressed(e)) }) document.addEventListener("visibilitychange", (e) => { - document.body.classList.toggle("ctrl_down", false) + set_ctrl_down(false) setTimeout(() => { - document.body.classList.toggle("ctrl_down", false) + set_ctrl_down(false) }, 100) }) document.addEventListener("keydown", (e) => { - document.body.classList.toggle("ctrl_down", has_ctrl_or_cmd_pressed(e)) + set_ctrl_down(has_ctrl_or_cmd_pressed(e)) // if (e.defaultPrevented) { // return // } diff --git a/frontend/editor.css b/frontend/editor.css index 767725608b..b246a606b0 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2553,13 +2553,13 @@ Based on "Paraíso (Light)" by Jan T. Sott: } } -body.ctrl_down [data-pluto-variable], -body.ctrl_down [data-cell-variable] { +[data-ctrl-down="true"][data-pluto-variable], +[data-ctrl-down="true"][data-cell-variable] { text-decoration-color: #d177e6; cursor: pointer; } -body.ctrl_down [data-pluto-variable]:hover, -body.ctrl_down [data-pluto-variable]:hover * { +[data-ctrl-down="true"][data-pluto-variable]:hover, +[data-ctrl-down="true"][data-pluto-variable]:hover * { /* This basically `color: #af5bc3`, but it works for emoji too!! */ color: transparent !important; text-shadow: 0 0 #af5bc3; @@ -2570,7 +2570,7 @@ body.ctrl_down [data-pluto-variable]:hover * { /* Can give this cool styles later as well, but not for now nahhh */ text-decoration: none; } -body.ctrl_down [data-cell-variable]:hover * { +[data-ctrl-down="true"][data-cell-variable]:hover * { /* This basically `color: #af5bc3`, but it works for emoji too!! */ color: transparent !important; text-shadow: 0 0 #af5bc3; From fe3b59bf60e0722d8b69ae661b380d49a4c4c262 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 00:04:07 +0100 Subject: [PATCH 126/821] Fix #1809 again By sticking to the MDN spec :) --- frontend/components/DropRuler.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/components/DropRuler.js b/frontend/components/DropRuler.js index 1ef654dfd6..ea7fc6c115 100644 --- a/frontend/components/DropRuler.js +++ b/frontend/components/DropRuler.js @@ -14,7 +14,6 @@ export class DropRuler extends Component { this.cell_edges.push(last(cell_nodes).offsetTop + last(cell_nodes).scrollHeight) } this.getDropIndexOf = ({ pageX, pageY }) => { - const notebook = document.querySelector("pluto-notebook") const distances = this.cell_edges.map((p) => Math.abs(p - pageY - 8)) // 8 is the magic computer number: https://en.wikipedia.org/wiki/8 return argmin(distances) } @@ -29,14 +28,7 @@ export class DropRuler extends Component { componentDidMount() { document.addEventListener("dragstart", (e) => { let target = /** @type {Element} */ (e.target) - if (!target.matches("pluto-shoulder")) { - this.setState({ - drag_start: false, - drag_target: false, - }) - this.props.set_scroller({ up: false, down: false }) - this.dropee = null - } else { + if (target.matches("pluto-shoulder")) { this.dropee = target.parentElement e.dataTransfer.setData("text/pluto-cell", this.props.serialize_selected(this.dropee.id)) this.dropped = false @@ -47,6 +39,13 @@ export class DropRuler extends Component { drop_index: this.getDropIndexOf(e), }) this.props.set_scroller({ up: true, down: true }) + } else { + this.setState({ + drag_start: false, + drag_target: false, + }) + this.props.set_scroller({ up: false, down: false }) + this.dropee = null } }) document.addEventListener("dragenter", (e) => { @@ -54,6 +53,7 @@ export class DropRuler extends Component { if (!this.state.drag_target) this.precompute_cell_edges() this.lastenter = e.target this.setState({ drag_target: true }) + e.preventDefault() }) document.addEventListener("dragleave", (e) => { if (e.dataTransfer.types[0] !== "text/pluto-cell") return @@ -69,6 +69,10 @@ export class DropRuler extends Component { this.setState({ drop_index: this.getDropIndexOf(e), }) + if (this.state.drag_start) { + // Then we're dragging a cell from within the notebook. Use a move icon: + e.dataTransfer.dropEffect = "move" + } e.preventDefault() }) document.addEventListener("dragend", (e) => { From c0c40db9146e5cb76b261b70f378c3737106601c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 00:18:35 +0100 Subject: [PATCH 127/821] Disable CM highlighting in live docs / cell output (#1939) --- frontend/components/CellOutput.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index e4593b566b..08b6a50622 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -488,6 +488,9 @@ export let RawHTMLContainer = ({ body, className = "", persist_js_state = false, return html`
` } +// https://github.com/fonsp/Pluto.jl/issues/1692 +const ENABLE_CM_HIGHLIGHTING = false + /** @param {HTMLElement} code_element */ export let highlight = (code_element, language) => { language = language.toLowerCase() @@ -495,6 +498,7 @@ export let highlight = (code_element, language) => { if (code_element.children.length === 0) { if ( + ENABLE_CM_HIGHLIGHTING && language === "julia" && // CodeMirror does not want to render inside a `
`... // I tried to debug this, it does not happen on a clean webpage with the same CM versions: From 5f052aa34e3f04eb8100937df01465d4f0566434 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 00:18:50 +0100 Subject: [PATCH 128/821] Disable CM mixed parser (#1940) --- frontend/components/CellInput.js | 25 ++- frontend/components/CellOutput.js | 5 +- sample/JavaScript.jl | 275 +++++++++++++++++++++++++++++- 3 files changed, 292 insertions(+), 13 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 6fc78bb1ce..7f72310d0e 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -63,6 +63,8 @@ import { commentKeymap } from "./CellInput/comment_mixed_parsers.js" import { debug_syntax_plugin } from "./CellInput/debug_syntax_plugin.js" import { ScopeStateField } from "./CellInput/scopestate_statefield.js" +export const ENABLE_CM_MIXED_PARSER = false + 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 😈) */ @@ -619,14 +621,21 @@ export const CellInput = ({ }), EditorState.tabSize.of(4), indentUnit.of("\t"), - julia_mixed(), - markdown({ - defaultCodeLanguage: julia_mixed(), - }), - htmlLang(), //Provides tag closing!, - javascript(), - python(), - sqlLang, + ...(ENABLE_CM_MIXED_PARSER + ? [ + julia_mixed(), + markdown({ + defaultCodeLanguage: julia_mixed(), + }), + htmlLang(), //Provides tag closing!, + javascript(), + python(), + sqlLang, + ] + : [ + // + julia_andrey(), + ]), go_to_definition_plugin, pluto_autocomplete({ request_autocomplete: async ({ text }) => { diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index 08b6a50622..ebd37216c2 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -12,11 +12,12 @@ import register from "../imports/PreactCustomElement.js" import { EditorState, EditorView, defaultHighlightStyle } from "../imports/CodemirrorPlutoSetup.js" -import { pluto_syntax_colors } from "./CellInput.js" +import { pluto_syntax_colors, ENABLE_CM_MIXED_PARSER } from "./CellInput.js" import { useState } from "../imports/Preact.js" import hljs from "../imports/highlightjs.js" import { julia_mixed } from "./CellInput/mixedParsers.js" +import { julia_andrey } from "../imports/CodemirrorPlutoSetup.js" export class CellOutput extends Component { constructor() { @@ -519,7 +520,7 @@ export let highlight = (code_element, language) => { defaultHighlightStyle.fallback, EditorState.tabSize.of(4), // TODO Other languages possibly? - language === "julia" ? julia_mixed() : null, + language === "julia" ? (ENABLE_CM_MIXED_PARSER ? julia_mixed() : julia_andrey()) : null, EditorView.lineWrapping, EditorView.editable.of(false), ].filter((x) => x != null), diff --git a/sample/JavaScript.jl b/sample/JavaScript.jl index 0ee47f5ab9..d5ec691e74 100644 --- a/sample/JavaScript.jl +++ b/sample/JavaScript.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.17.7 +# v0.18.0 using Markdown using InteractiveUtils @@ -766,6 +766,255 @@ details(x, summary="Show more") = @htl("""
""") +# ╔═╡ 93abe0dc-f041-475f-9ef7-d8ee4408414b +details(md""" + ```htmlmixed + +
+

+ Learning HTML and CSS +

+

+ It is easy to learn HTML and CSS because they are not 'programming languages' like Julia and JavaScript, they are markup languages: there are no loops, functions or arrays, you only declare how your document is structured (HTML) and what that structure looks like on a 2D color display (CSS). +

+

+ As an example, this is what this cell looks like, written in HTML and CSS: +

+
+ + + + ``` + """, "Show with syntax highlighting") + +# ╔═╡ d12b98df-8c3f-4620-ba3c-2f3dadac521b +details(md""" + ```htmlmixed + + ``` + """, "Show with syntax highlighting") + +# ╔═╡ 94561cb1-2325-49b6-8b22-943923fdd91b +details(md""" + ```htmlmixed + + + + ``` + """, "Show with syntax highlighting") + +# ╔═╡ b0c246ed-b871-461b-9541-280e49b49136 +details(md""" +```htmlmixed +
+ + + +
+``` +""", "Show with syntax highlighting") + +# ╔═╡ d121e085-c69b-490f-b315-c11a9abd57a6 +details(md""" + ```htmlmixed + + ``` + """, "Show with syntax highlighting") + +# ╔═╡ d4bdc4fe-2af8-402f-950f-2afaf77c62de +details(md""" + ```htmlmixed + + ``` + """, "Show with syntax highlighting") + +# ╔═╡ e910982c-8508-4729-a75d-8b5b847918b6 +details(md""" +```htmlmixed + + + +``` +""", "Show with syntax highlighting") + +# ╔═╡ 05d28aa2-9622-4e62-ab39-ca4c7dde6eb4 +details(md""" + ```htmlmixed + + ``` + """, "Show with syntax highlighting") + # ╔═╡ cc318a19-316f-4fd9-8436-fb1d42f888a3 demo_img = let url = "https://user-images.githubusercontent.com/6933510/116753174-fa40ab80-aa06-11eb-94d7-88f4171970b2.jpeg" @@ -845,6 +1094,10 @@ git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" version = "0.11.0" +[[CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" @@ -906,7 +1159,7 @@ uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" [[LinearAlgebra]] -deps = ["Libdl"] +deps = ["Libdl", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[Logging]] @@ -929,6 +1182,10 @@ uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +[[OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" + [[Parsers]] deps = ["Dates"] git-tree-sha1 = "0b5cfbb704034b5b4c1869e36634438a047df065" @@ -954,7 +1211,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[Reexport]] @@ -1002,6 +1259,10 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +[[libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" + [[nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" @@ -1016,6 +1277,7 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╠═571613a1-6b4b-496d-9a68-aac3f6a83a4b # ╟─168e13f7-2ff2-4207-be56-e57755041d36 # ╠═28ae1424-67dc-4b76-a172-1185cc76cb59 +# ╟─93abe0dc-f041-475f-9ef7-d8ee4408414b # ╟─ea39c63f-7466-4015-a66c-08bd9c716343 # ╟─8b082f9a-073e-4112-9422-4087850fc89e # ╟─d70a3a02-ef3a-450f-bf5a-4a0d7f6262e2 @@ -1041,14 +1303,17 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╟─7afbf8ef-e91c-45b9-bf22-24201cbb4828 # ╠═b226da72-9512-4d14-8582-2f7787c25028 # ╠═a6fd1f7b-a8fc-420d-a8bb-9f549842ad3e +# ╟─d12b98df-8c3f-4620-ba3c-2f3dadac521b # ╟─965f3660-6ec4-4a86-a2a2-c167dbe9315f # ╠═01ce31a9-6856-4ee7-8bce-7ce635167457 # ╠═00d97588-d591-4dad-9f7d-223c237deefd # ╠═21f57310-9ceb-423c-a9ce-5beb1060a5a3 +# ╟─94561cb1-2325-49b6-8b22-943923fdd91b # ╟─7d9d6c28-131a-4b2a-84f8-5c085f387e85 # ╟─0866afc2-fd42-42b7-a572-9d824cf8b83b # ╟─75e1a973-7ef0-4ac5-b3e2-5edb63577927 # ╠═e8d8a60e-489b-467a-b49c-1fa844807751 +# ╟─b0c246ed-b871-461b-9541-280e49b49136 # ╠═9346d8e2-9ba0-4475-a21f-11bdd018bc60 # ╠═7822fdb7-bee6-40cc-a089-56bb32d77fe6 # ╟─701de4b8-42d3-46a3-a399-d7761dccd83d @@ -1064,21 +1329,25 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╟─4cf27df3-6a69-402e-a71c-26538b2a52e7 # ╟─5721ad33-a51a-4a91-adb2-0915ea0efa13 # ╠═c857bb4b-4cf4-426e-b340-592cf7700434 +# ╟─d121e085-c69b-490f-b315-c11a9abd57a6 # ╟─fc8984c8-4668-418a-b258-a1718809470c # ╠═846354c8-ba3b-4be7-926c-d3c9cc9add5f # ╟─a33c7d7a-8071-448e-abd6-4e38b5444a3a # ╠═91f3dab8-5521-44a0-9890-8d988a994076 # ╠═dcaae662-4a4f-4dd3-8763-89ea9eab7d43 +# ╟─d4bdc4fe-2af8-402f-950f-2afaf77c62de # ╟─e77cfefc-429d-49db-8135-f4604f6a9f0b # ╠═2d5689f5-1d63-4b8b-a103-da35933ad26e # ╠═6dd221d1-7fd8-446e-aced-950512ea34bc # ╠═0a9d6e2d-3a41-4cd5-9a4e-a9b76ed89fa9 # ╟─0962d456-1a76-4b0d-85ff-c9e7dc66621d # ╠═bf9b36e8-14c5-477b-a54b-35ba8e415c77 +# ╟─e910982c-8508-4729-a75d-8b5b847918b6 # ╟─781adedc-2da7-4394-b323-e508d614afae # ╟─de789ad1-8197-48ae-81b2-a21ec2340ae0 # ╠═85483b28-341e-4ed6-bb1e-66c33613725e # ╠═9e37c18c-3ebb-443a-9663-bb4064391d6e +# ╟─05d28aa2-9622-4e62-ab39-ca4c7dde6eb4 # ╠═3266f9e6-42ad-4103-8db3-b87d2c315290 # ╟─ebec177c-4c33-45a4-bdbd-f16944631aff # ╟─da7091f5-8ba2-498b-aa8d-bbf3b4505b81 From 6421f8d269fd5b89939535ce058f9dc61b08e36f Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 01:02:49 +0100 Subject: [PATCH 129/821] =?UTF-8?q?=F0=9F=A6=8A=20fix=20frontend=20tests?= =?UTF-8?q?=20(#1943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/frontend/helpers/pluto.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/frontend/helpers/pluto.js b/test/frontend/helpers/pluto.js index 65aaea0bb9..d596edb16b 100644 --- a/test/frontend/helpers/pluto.js +++ b/test/frontend/helpers/pluto.js @@ -75,7 +75,9 @@ export const getCellIds = (page) => page.evaluate(() => Array.from(document.quer */ export const waitForPlutoToCalmDown = async (page) => { await page.waitForTimeout(1000) - await page.waitForFunction(() => document.body._update_is_ongoing === false && document.querySelector(`pluto-cell.running, pluto-cell.queued`) === null) + await page.waitForFunction( + () => document.querySelector("body")._update_is_ongoing === false && document.querySelector(`pluto-cell.running, pluto-cell.queued`) === null + ) } /** @@ -99,7 +101,7 @@ export const waitForCellOutputToChange = (page, cellId, currentOutput) => { export const waitForNoUpdateOngoing = async (page, options = {}) => { await page.waitForTimeout(1000) - return await page.waitForFunction(() => document.body._update_is_ongoing === false, options) + return await page.waitForFunction(() => document.querySelector("body")._update_is_ongoing === false, options) } /** From 1b73bbe56c96a72d7d07cac673db3cb6f92665ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A0=CE=B1=CE=BD=CE=B1=CE=B3=CE=B9=CF=8E=CF=84=CE=B7?= =?UTF-8?q?=CF=82=20=CE=93=CE=B5=CF=89=CF=81=CE=B3=CE=B1=CE=BA=CF=8C=CF=80?= =?UTF-8?q?=CE=BF=CF=85=CE=BB=CE=BF=CF=82?= Date: Wed, 23 Feb 2022 02:35:10 +0200 Subject: [PATCH 130/821] Fix #1781 - cell selection/move performance regression (#1944) Co-authored-by: Fons van der Plas --- frontend/components/Cell.js | 95 +++++++++++++++----------------- frontend/components/CellInput.js | 6 +- frontend/components/DropRuler.js | 20 +++++-- frontend/components/Editor.js | 18 +++--- frontend/components/Notebook.js | 56 +++++++++++-------- 5 files changed, 105 insertions(+), 90 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 1adb104f38..dd2bb943ea 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -1,5 +1,5 @@ import _ from "../imports/lodash.js" -import { html, useState, useEffect, useMemo, useRef, useContext, useLayoutEffect } from "../imports/Preact.js" +import { html, useState, useEffect, useMemo, useRef, useContext, useLayoutEffect, useCallback } from "../imports/Preact.js" import { CellOutput } from "./CellOutput.js" import { CellInput } from "./CellInput.js" @@ -36,7 +36,6 @@ const useCellApi = (node_ref, published_object_keys, pluto_actions) => { * cell_dependencies: import("./Editor.js").CellDependencyData * nbpkg: import("./Editor.js").NotebookPkgData?, * selected: boolean, - * selected_cells: Array, * force_hide_input: boolean, * focus_after_creation: boolean, * [key: string]: any, @@ -48,23 +47,19 @@ export const Cell = ({ cell_dependencies, cell_input_local, notebook_id, - on_update_doc_query, - on_change, - on_focus_neighbor, selected, - selected_cells, force_hide_input, focus_after_creation, is_process_ready, disable_input, nbpkg, + global_definition_locations, }) => { let pluto_actions = useContext(PlutoContext) - const notebook = pluto_actions.get_notebook() - let variables_in_all_notebook = Object.fromEntries( - Object.values(notebook?.cell_dependencies ?? {}).flatMap((x) => Object.keys(x.downstream_cells_map).map((variable) => [variable, x.cell_id])) - ) - const variables = Object.keys(notebook?.cell_dependencies?.[cell_id]?.downstream_cells_map || {}) + const on_update_doc_query = pluto_actions.set_doc_query + const on_focus_neighbor = pluto_actions.focus_on_neighbor + const on_change = useCallback((val) => pluto_actions.set_local_cell(cell_id, val), [cell_id, pluto_actions]) + const variables = useMemo(() => Object.keys(cell_dependencies), [cell_dependencies]) // cm_forced_focus is null, except when a line needs to be highlighted because it is part of a stack trace const [cm_forced_focus, set_cm_forced_focus] = useState(null) const [cm_highlighted_line, set_cm_highlighted_line] = useState(null) @@ -131,7 +126,36 @@ export const Cell = ({ const set_waiting_to_run_smart = (x) => set_waiting_to_run(x && should_set_waiting_to_run_ref.current) const cell_api_ready = useCellApi(node_ref, published_object_keys, pluto_actions) - + const on_delete = useCallback(() => { + pluto_actions.confirm_delete_multiple("Delete", pluto_actions.get_selected_cells(cell_id, selected)) + }, [pluto_actions, selected, cell_id]) + const on_submit = useCallback(() => { + if (!disable_input_ref.current) { + set_waiting_to_run_smart(true) + pluto_actions.set_and_run_multiple([cell_id]) + } + }, [pluto_actions, set_waiting_to_run, cell_id]) + const on_change_cell_input = useCallback( + (new_code) => { + if (!disable_input_ref.current) { + if (code_folded && cm_forced_focus != null) { + pluto_actions.fold_remote_cells([cell_id], false) + } + on_change(new_code) + } + }, + [code_folded, cm_forced_focus, pluto_actions, on_change] + ) + const on_add_after = useCallback(() => { + pluto_actions.add_remote_cell(cell_id, "after") + }, [pluto_actions, cell_id, selected]) + const on_code_fold = useCallback(() => { + pluto_actions.fold_remote_cells(pluto_actions.get_selected_cells(cell_id, selected), !code_folded) + }, [pluto_actions, cell_id, selected, code_folded]) + const on_run = useCallback(() => { + pluto_actions.set_and_run_multiple(pluto_actions.get_selected_cells(cell_id, selected)) + set_waiting_to_run_smart(true) + }, [pluto_actions, cell_id, selected, set_waiting_to_run_smart]) return html` ${variables.map((name) => html``)} - @@ -183,34 +196,16 @@ export const Cell = ({ local_code=${cell_input_local?.code ?? code} remote_code=${code} cell_dependencies=${cell_dependencies} - variables_in_all_notebook=${variables_in_all_notebook} + global_definition_locations=${global_definition_locations} disable_input=${disable_input} focus_after_creation=${focus_after_creation} cm_forced_focus=${cm_forced_focus} set_cm_forced_focus=${set_cm_forced_focus} show_input=${show_input} - on_submit=${() => { - if (!disable_input_ref.current) { - set_waiting_to_run_smart(true) - pluto_actions.set_and_run_multiple([cell_id]) - } - }} - on_delete=${() => { - let cells_to_delete = selected ? selected_cells : [cell_id] - pluto_actions.confirm_delete_multiple("Delete", cells_to_delete) - }} - on_add_after=${() => { - pluto_actions.add_remote_cell(cell_id, "after") - }} - on_fold=${(new_folded) => pluto_actions.fold_remote_cell(cell_id, new_folded)} - on_change=${(new_code) => { - if (!disable_input_ref.current) { - if (code_folded && cm_forced_focus != null) { - pluto_actions.fold_remote_cell(cell_id, false) - } - on_change(new_code) - } - }} + on_submit=${on_submit} + on_delete=${on_delete} + on_add_after=${on_add_after} + on_change=${on_change_cell_input} on_update_doc_query=${on_update_doc_query} on_focus_neighbor=${on_focus_neighbor} on_line_heights=${set_line_heights} @@ -229,11 +224,7 @@ export const Cell = ({ cell_id=${cell_id} running_disabled=${running_disabled} depends_on_disabled_cells=${depends_on_disabled_cells} - on_run=${() => { - set_waiting_to_run_smart(true) - let cell_to_run = selected ? selected_cells : [cell_id] - pluto_actions.set_and_run_multiple(cell_to_run) - }} + on_run=${on_run} on_interrupt=${() => { pluto_actions.interrupt_remote(cell_id) }} diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 7f72310d0e..c5245fdc6e 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -325,7 +325,7 @@ let line_and_ch_to_cm6_position = (/** @type {import("../imports/CodemirrorPluto * scroll_into_view_after_creation: boolean, * cell_dependencies: import("./Editor.js").CellDependencyData, * nbpkg: import("./Editor.js").NotebookPkgData?, - * variables_in_all_notebook: { [variable_name: string]: string }, + * global_definition_locations: { [variable_name: string]: string }, * [key: string]: any, * }} props */ @@ -353,7 +353,7 @@ export const CellInput = ({ show_logs, set_show_logs, cm_highlighted_line, - variables_in_all_notebook, + global_definition_locations, }) => { let pluto_actions = useContext(PlutoContext) @@ -364,7 +364,7 @@ export const CellInput = ({ on_change_ref.current = on_change let nbpkg_compartment = useCompartment(newcm_ref, NotebookpackagesFacet.of(nbpkg)) - let global_definitions_compartment = useCompartment(newcm_ref, GlobalDefinitionsFacet.of(variables_in_all_notebook)) + let global_definitions_compartment = useCompartment(newcm_ref, GlobalDefinitionsFacet.of(global_definition_locations)) let highlighted_line_compartment = useCompartment(newcm_ref, HighlightLineFacet.of(cm_highlighted_line)) let editable_compartment = useCompartment(newcm_ref, EditorState.readOnly.of(disable_input)) diff --git a/frontend/components/DropRuler.js b/frontend/components/DropRuler.js index ea7fc6c115..7ed6c49916 100644 --- a/frontend/components/DropRuler.js +++ b/frontend/components/DropRuler.js @@ -1,4 +1,5 @@ import { html, Component } from "../imports/Preact.js" +import _ from "../imports/lodash.js" export class DropRuler extends Component { constructor() { @@ -6,7 +7,7 @@ export class DropRuler extends Component { this.dropee = null this.dropped = null this.cell_edges = [] - this.pointer_position = {} + this.pointer_position = { pageX: 0, pageY: 0 } this.precompute_cell_edges = () => { /** @type {Array} */ const cell_nodes = Array.from(document.querySelectorAll("pluto-notebook > pluto-cell")) @@ -61,14 +62,24 @@ export class DropRuler extends Component { this.setState({ drag_target: false }) } }) + const precompute_cell_edges_throttled = _.throttle(this.precompute_cell_edges, 4000, { leading: false, trailing: true }) + const update_drop_index_throttled = _.throttle( + () => { + this.setState({ + drop_index: this.getDropIndexOf(this.pointer_position), + }) + }, + 300, + { leading: false, trailing: true } + ) document.addEventListener("dragover", (e) => { // Called continuously during drag if (e.dataTransfer.types[0] !== "text/pluto-cell") return this.pointer_position = e - this.setState({ - drop_index: this.getDropIndexOf(e), - }) + precompute_cell_edges_throttled() + update_drop_index_throttled() + if (this.state.drag_start) { // Then we're dragging a cell from within the notebook. Use a move icon: e.dataTransfer.dropEffect = "move" @@ -77,6 +88,7 @@ export class DropRuler extends Component { }) document.addEventListener("dragend", (e) => { // Called after drag, also when dropped outside of the browser or when ESC is pressed + update_drop_index_throttled.flush() this.setState({ drag_start: false, drag_target: false, diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 777a99f211..2792bfe664 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -489,12 +489,14 @@ export class Editor extends Component { } } }, - fold_remote_cell: async (cell_id, newFolded) => { - if (!newFolded) { - this.setState({ last_created_cell: cell_id }) + fold_remote_cells: async (cell_ids, newFolded) => { + if (!newFolded && cell_ids.length > 0) { + this.setState({ last_created_cell: cell_ids[cell_ids.length - 1] }) } await update_notebook((notebook) => { - notebook.cell_inputs[cell_id].code_folded = newFolded + for (let cell_id of cell_ids) { + notebook.cell_inputs[cell_id].code_folded = newFolded + } }) }, set_and_run_all_changed_remote_cells: () => { @@ -556,6 +558,9 @@ export class Editor extends Component { false ) }, + /** This actions avoids pushing selected cells all the way down, which is too heavy to handle! */ + get_selected_cells: (cell_id, /** @type {boolean} */ allow_other_selected_cells) => + allow_other_selected_cells ? this.state.selected_cells : [cell_id], get_avaible_versions: async ({ package_name, notebook_id }) => { const { message } = await this.client.send("nbpkg_available_versions", { package_name: package_name }, { notebook_id: notebook_id }) return message.versions @@ -1325,9 +1330,6 @@ patch: ${JSON.stringify( <${Notebook} notebook=${this.state.notebook} cell_inputs_local=${this.state.cell_inputs_local} - on_update_doc_query=${this.actions.set_doc_query} - on_cell_input=${this.actions.set_local_cell} - on_focus_neighbor=${this.actions.focus_on_neighbor} disable_input=${this.state.disable_ui || !this.state.connected /* && this.state.binder_phase == null*/} show_logs=${this.state.show_logs} set_show_logs=${(enabled) => this.setState({ show_logs: enabled })} @@ -1354,7 +1356,7 @@ patch: ${JSON.stringify( on_selection=${(selected_cell_ids) => { // @ts-ignore if ( - selected_cell_ids.length !== this.state.selected_cells || + selected_cell_ids.length !== this.state.selected_cells.length || _.difference(selected_cell_ids, this.state.selected_cells).length !== 0 ) { this.setState({ diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index 2c6677ec48..dcdf606806 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -1,20 +1,36 @@ import { PlutoContext } from "../common/PlutoContext.js" -import { html, useContext, useEffect, useMemo, useState } from "../imports/Preact.js" +import { html, useContext, useEffect, useMemo, useRef, useState } from "../imports/Preact.js" import { Cell } from "./Cell.js" import { nbpkg_fingerprint } from "./PkgStatusMark.js" +/** Like `useMemo`, but explain to the console what invalidated the memo. */ +const useMemoDebug = (fn, args) => { + const last_values = useRef(args) + return useMemo(() => { + const new_values = args + console.group("useMemoDebug: something changed!") + if (last_values.current.length !== new_values.length) { + console.log("Length changed. ", " old ", last_values.current, " new ", new_values) + } else { + for (let i = 0; i < last_values.current.length; i++) { + if (last_values.current[i] !== new_values[i]) { + console.log("Element changed. Index: ", i, " old ", last_values.current[i], " new ", new_values[i]) + } + } + } + console.groupEnd() + return fn() + }, args) +} + let CellMemo = ({ cell_result, cell_input, cell_input_local, notebook_id, - on_update_doc_query, - on_cell_input, cell_dependencies, - on_focus_neighbor, selected, - selected_cells, focus_after_creation, force_hide_input, is_process_ready, @@ -22,8 +38,8 @@ let CellMemo = ({ show_logs, set_show_logs, nbpkg, + global_definition_locations, }) => { - 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, logs } = cell_result || {} const { cell_id, code, code_folded, running_disabled } = cell_input || {} @@ -35,11 +51,7 @@ let CellMemo = ({ cell_input=${cell_input} cell_input_local=${cell_input_local} notebook_id=${notebook_id} - on_update_doc_query=${on_update_doc_query} - on_change=${(val) => on_cell_input(cell_input.cell_id, val)} - on_focus_neighbor=${on_focus_neighbor} selected=${selected} - selected_cells=${selected_cells} force_hide_input=${force_hide_input} focus_after_creation=${focus_after_creation} is_process_ready=${is_process_ready} @@ -47,6 +59,7 @@ let CellMemo = ({ show_logs=${show_logs} set_show_logs=${set_show_logs} nbpkg=${nbpkg} + global_definition_locations=${global_definition_locations} /> ` }, [ @@ -67,18 +80,15 @@ let CellMemo = ({ code_folded, cell_input_local, notebook_id, - on_update_doc_query, - on_cell_input, cell_dependencies, - on_focus_neighbor, selected, - selected_cells_diffable_primitive, force_hide_input, focus_after_creation, is_process_ready, disable_input, show_logs, ...nbpkg_fingerprint(nbpkg), + global_definition_locations, ]) } @@ -111,9 +121,6 @@ const render_cell_outputs_minimum = 20 export const Notebook = ({ notebook, cell_inputs_local, - on_update_doc_query, - on_cell_input, - on_focus_neighbor, last_created_cell, selected_cells, is_initializing, @@ -143,7 +150,13 @@ export const Notebook = ({ }, render_cell_outputs_delay(notebook.cell_order.length)) } }, [cell_outputs_delayed, notebook.cell_order.length]) - + let global_definition_locations = useMemo( + () => + Object.fromEntries( + Object.values(notebook?.cell_dependencies ?? {}).flatMap((x) => Object.keys(x.downstream_cells_map).map((variable) => [variable, x.cell_id])) + ), + [notebook?.cell_dependencies] + ) return html` ${notebook.cell_order @@ -161,14 +174,10 @@ export const Notebook = ({ logs: [], }} cell_input=${notebook.cell_inputs[cell_id]} - cell_dependencies=${notebook.cell_dependencies[cell_id] ?? {}} + cell_dependencies=${notebook?.cell_dependencies?.[cell_id] ?? {}} cell_input_local=${cell_inputs_local[cell_id]} notebook_id=${notebook.notebook_id} - on_update_doc_query=${on_update_doc_query} - on_cell_input=${on_cell_input} - on_focus_neighbor=${on_focus_neighbor} selected=${selected_cells.includes(cell_id)} - selected_cells=${selected_cells} focus_after_creation=${last_created_cell === cell_id} force_hide_input=${false} is_process_ready=${is_process_ready} @@ -176,6 +185,7 @@ export const Notebook = ({ show_logs=${show_logs} set_show_logs=${set_show_logs} nbpkg=${notebook.nbpkg} + global_definition_locations=${global_definition_locations} />` )} From 906e3460c0457866faa8b4eb4b40a95b1718c3f5 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 01:35:57 +0100 Subject: [PATCH 131/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5fd7297426..4ac0193e48 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.18.0" +version = "0.18.1" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 5b858ded0db12517cf053215b1b40709b5772369 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 02:14:01 +0100 Subject: [PATCH 132/821] Update index.css --- frontend/index.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/index.css b/frontend/index.css index e3ada1c1a8..09e847ce32 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -262,17 +262,17 @@ body.nosessions ul#new ~ * { } #recent li.running button > span::after { - background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle.svg); + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle.svg"); filter: var(--image-filters); } #recent li.recent button > span::after { - background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-forward-circle-outline.svg); + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-forward-circle-outline.svg"); filter: var(--image-filters); } #recent li.transitioning button > span::after { - background-image: url(https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-horizontal-outline.svg); + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-horizontal-outline.svg"); filter: var(--image-filters); } From 19e455484158457249d47cfa9b8386f4bb2ef78c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 11:55:00 +0100 Subject: [PATCH 133/821] =?UTF-8?q?=F0=9F=93=81=20Fix=20#1785?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/webserver/REPLTools.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/webserver/REPLTools.jl b/src/webserver/REPLTools.jl index b4b5fd9b35..ab87db0d09 100644 --- a/src/webserver/REPLTools.jl +++ b/src/webserver/REPLTools.jl @@ -17,8 +17,13 @@ responses[:completepath] = function response_completepath(🙋::ClientRequest) pos = lastindex(path) results, loc, found = complete_path(path, pos) + # too many candiates otherwise. -0.1 instead of 0 to enable autocompletions for paths: `/` or `/asdf/` isenough(x) = x ≥ -0.1 - filter!(isenough ∘ score, results) # too many candiates otherwise. -0.1 instead of 0 to enable autocompletions for paths: `/` or `/asdf/` + ishidden(path_completion) = let p = path_completion.path + @info p basename(isdirpath(p) ? dirname(p) : p) + startswith(basename(isdirpath(p) ? dirname(p) : p), ".") + end + filter!(p -> !ishidden(p) && (isenough ∘ score)(p), results) start_utf8 = let # REPLCompletions takes into account that spaces need to be prefixed with `\` in the shell, so it subtracts the number of spaces in the filename from `start`: From e6b55d1c285b3cc07721eb21b241b5dca46aa136 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 13:25:29 +0100 Subject: [PATCH 134/821] HTTP header: Referrer-Policy: same-origin --- src/webserver/WebServer.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 2ce23baa5e..1597d1760c 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -242,7 +242,8 @@ function run(session::ServerSession, pluto_router) request.response::HTTP.Response = response_body request.response.request = request try - HTTP.setheader(http, "Referrer-Policy" => "origin-when-cross-origin") + # https://github.com/fonsp/Pluto.jl/pull/722 + HTTP.setheader(http, "Referrer-Policy" => "same-origin") HTTP.startwrite(http) write(http, request.response.body) HTTP.closewrite(http) From 6b07f9107284a37bfeb4aaf1578622381ec97b29 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 13:26:10 +0100 Subject: [PATCH 135/821] HTTP header: Server: Pluto/0.18.1 Julia/1.7.1 See #1102 --- src/webserver/WebServer.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 1597d1760c..6172e371e5 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -244,6 +244,8 @@ function run(session::ServerSession, pluto_router) try # https://github.com/fonsp/Pluto.jl/pull/722 HTTP.setheader(http, "Referrer-Policy" => "same-origin") + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#:~:text=is%202%20minutes.-,14.38%20Server + HTTP.setheader(http, "Server" => "Pluto.jl/$(PLUTO_VERSION_STR[2:end]) Julia/$(JULIA_VERSION_STR[2:end])") HTTP.startwrite(http) write(http, request.response.body) HTTP.closewrite(http) From 3286275e616dd6eda2c12a484beab9be0f2ef605 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 13:31:08 +0100 Subject: [PATCH 136/821] remove console.log --- src/webserver/REPLTools.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webserver/REPLTools.jl b/src/webserver/REPLTools.jl index ab87db0d09..e80a8c2444 100644 --- a/src/webserver/REPLTools.jl +++ b/src/webserver/REPLTools.jl @@ -20,7 +20,6 @@ responses[:completepath] = function response_completepath(🙋::ClientRequest) # too many candiates otherwise. -0.1 instead of 0 to enable autocompletions for paths: `/` or `/asdf/` isenough(x) = x ≥ -0.1 ishidden(path_completion) = let p = path_completion.path - @info p basename(isdirpath(p) ? dirname(p) : p) startswith(basename(isdirpath(p) ? dirname(p) : p), ".") end filter!(p -> !ishidden(p) && (isenough ∘ score)(p), results) From a640db64da5b936d13cf5f60043a510f2fc7fedb Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 13:34:43 +0100 Subject: [PATCH 137/821] HTTP header: Content-Length See #1102 --- src/webserver/WebServer.jl | 3 +++ src/webserver/WebSocketFix.jl | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 6172e371e5..c1ee31f303 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -226,6 +226,8 @@ function run(session::ServerSession, pluto_router) end end else + # then it's a regular HTTP request, not a WS upgrade + request::HTTP.Request = http.message request.body = read(http) HTTP.closeread(http) @@ -242,6 +244,7 @@ function run(session::ServerSession, pluto_router) request.response::HTTP.Response = response_body request.response.request = request try + HTTP.setheader(http, "Content-Length" => string(length(request.response.body))) # https://github.com/fonsp/Pluto.jl/pull/722 HTTP.setheader(http, "Referrer-Policy" => "same-origin") # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#:~:text=is%202%20minutes.-,14.38%20Server diff --git a/src/webserver/WebSocketFix.jl b/src/webserver/WebSocketFix.jl index 517724bcaa..a7ad726938 100644 --- a/src/webserver/WebSocketFix.jl +++ b/src/webserver/WebSocketFix.jl @@ -5,14 +5,14 @@ import HTTP.WebSockets function readframe(ws::WebSockets.WebSocket) header = WebSockets.readheader(ws.io) - @debug 1 "WebSocket ➡️ $header" + # @debug 1 "WebSocket ➡️ $header" if header.length > 0 if length(ws.rxpayload) < header.length resize!(ws.rxpayload, header.length) end unsafe_read(ws.io, pointer(ws.rxpayload), header.length) - @debug 2 " ➡️ \"$(String(ws.rxpayload[1:header.length]))\"" + # @debug 2 " ➡️ \"$(String(ws.rxpayload[1:header.length]))\"" end l = Int(header.length) if header.hasmask From b57444cdc8d7a7c787855a7fc0c3e2a60ec19507 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 15:09:31 +0100 Subject: [PATCH 138/821] CSS: close fallbacks for JuliaMono while loading --- frontend/editor.css | 19 ++++++++++--------- frontend/index.css | 2 +- frontend/treeview.css | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/editor.css b/frontend/editor.css index b246a606b0..4946ed319e 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -28,6 +28,7 @@ /* use the value "contextual" to enable contextual ligatures `document.documentElement.style.setProperty('--pluto-operator-ligatures', 'contextual');` for julia mono see here: https://cormullion.github.io/pages/2020-07-26-JuliaMono/#contextual_and_stylistic_alternates_and_ligatures */ --pluto-operator-ligatures: none; + --julia-mono-font-stack: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace; --sans-serif-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; color-scheme: light dark; @@ -222,13 +223,13 @@ a:hover { } pluto-output code { - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75em; /* not rem */ font-variant-ligatures: none; } pluto-output code .cm-editor .cm-line { - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); } pluto-output pre > code { @@ -253,7 +254,7 @@ pluto-output pre { word-break: break-all; tab-size: 4; -moz-tab-size: 4; /* https://bugzilla.mozilla.org/show_bug.cgi?id=737785 */ - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75rem; font-variant-ligatures: none; } @@ -406,7 +407,7 @@ pluto-output table > thead { } pluto-output table > tbody td { - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75rem; font-variant-ligatures: none; } @@ -720,7 +721,7 @@ body.binder > header > nav#at_the_top > pluto-filepicker > * { body.binder > header > nav#at_the_top > pluto-filepicker > a { display: block; font-size: 16px; - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); opacity: 0.8; text-decoration: none; } @@ -1024,7 +1025,7 @@ pluto-output:not(.rich_output) { } pluto-output > assignee { - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75rem; font-variant-ligatures: none; } @@ -2077,7 +2078,7 @@ pluto-helpbox > section hr { .pluto-docs-binding > span { display: inline-block; transform: translate(-19px, -16px); - font-family: "JuliaMono", monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.9rem; font-weight: 700; margin-top: -1em; @@ -2518,7 +2519,7 @@ pluto-input .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { .cm-completionIcon::before { /* TODO This loses all color when it's not font-family: JuliaMono, which can happen */ content: "⚫"; - font-family: JuliaMono, monospace !important; + font-family: var(--julia-mono-font-stack) !important; color: transparent; font-size: 0.75rem; margin-right: 0.5em; @@ -2585,7 +2586,7 @@ Based on "Paraíso (Light)" by Jan T. Sott: pluto-input .cm-editor .cm-content, pluto-input .cm-editor .cm-scroller, .cm-editor .cm-tooltip-autocomplete .cm-completionLabel { - font-family: JuliaMono, monospace !important; + font-family: var(--julia-mono-font-stack) !important; font-variant-ligatures: none; font-size: 0.75rem; } diff --git a/frontend/index.css b/frontend/index.css index 09e847ce32..be60eb9b71 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -223,7 +223,7 @@ pluto-filepicker button { } .cm-editor .cm-tooltip-autocomplete .cm-completionLabel { - font-family: JuliaMono, monospace !important; + font-family: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace !important; font-variant-ligatures: none; font-size: 0.75rem; } diff --git a/frontend/treeview.css b/frontend/treeview.css index 4a4448b8ae..54487b2545 100644 --- a/frontend/treeview.css +++ b/frontend/treeview.css @@ -8,7 +8,7 @@ pluto-tree, pluto-tree-pair { - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75rem; } pluto-tree { @@ -256,7 +256,7 @@ jlerror > section > ol > li > mark { background: var(--jlerror-mark-bg-color); border-radius: 6px; color: var(--jlerror-mark-color); - font-family: JuliaMono, monospace; + font-family: var(--julia-mono-font-stack); font-variant-ligatures: none; } jlerror > section > ol > li > em > a { @@ -282,7 +282,7 @@ table.pluto-table td { table.pluto-table .schema-types { color: var(--pluto-schema-types-color); - font-family: "JuliaMono", monospace; + font-family: var(--julia-mono-font-stack); font-size: 0.75rem; opacity: 0; } From a64ba16d42d6a13780542cb5a2c69596814b8776 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 15:35:39 +0100 Subject: [PATCH 139/821] MathJax.js: even lazier load (#1947) --- frontend/common/SetupMathJax.js | 111 +++++++++++++++++++------------- frontend/components/Editor.js | 5 ++ frontend/editor.html | 4 +- 3 files changed, 72 insertions(+), 48 deletions(-) diff --git a/frontend/common/SetupMathJax.js b/frontend/common/SetupMathJax.js index e56bae6f03..f0e8f8f658 100644 --- a/frontend/common/SetupMathJax.js +++ b/frontend/common/SetupMathJax.js @@ -1,50 +1,69 @@ -// @ts-nocheck -const deprecated = () => console.warn("Pluto uses MathJax 3, but a MathJax 2 function was called.") +import "https://cdn.jsdelivr.net/npm/requestidlecallback-polyfill@1.0.2/index.js" -window.MathJax = { - options: { - ignoreHtmlClass: "no-MαθJax", - processHtmlClass: "tex", - }, - startup: { - typeset: true, // because we load MathJax asynchronously - ready: () => { - window.MathJax.startup.defaultReady() +export const setup_mathjax = () => { + const deprecated = () => console.warn("Pluto uses MathJax 3, but a MathJax 2 function was called.") - // plotly uses MathJax 2, so we have this shim to make it work kindof - window.MathJax.Hub = { - Queue: function () { - for (var i = 0, m = arguments.length; i < m; i++) { - var fn = window.MathJax.Callback(arguments[i]) - window.MathJax.startup.promise = window.MathJax.startup.promise.then(fn) - } - return window.MathJax.startup.promise - }, - Typeset: function (elements, callback) { - var promise = window.MathJax.typesetPromise(elements) - if (callback) { - promise = promise.then(callback) - } - return promise - }, - Register: { - MessageHook: deprecated, - StartupHook: deprecated, - LoadHook: deprecated, - }, - Config: deprecated, - Configured: deprecated, - setRenderer: deprecated, - } + // @ts-ignore + window.MathJax = { + options: { + ignoreHtmlClass: "no-MαθJax", + processHtmlClass: "tex", }, - }, - tex: { - inlineMath: [ - ["$", "$"], - ["\\(", "\\)"], - ], - }, - svg: { - fontCache: "global", - }, + startup: { + typeset: true, // because we load MathJax asynchronously + ready: () => { + // @ts-ignore + window.MathJax.startup.defaultReady() + + // plotly uses MathJax 2, so we have this shim to make it work kindof + // @ts-ignore + window.MathJax.Hub = { + Queue: function () { + for (var i = 0, m = arguments.length; i < m; i++) { + // @ts-ignore + var fn = window.MathJax.Callback(arguments[i]) + // @ts-ignore + window.MathJax.startup.promise = window.MathJax.startup.promise.then(fn) + } + // @ts-ignore + return window.MathJax.startup.promise + }, + Typeset: function (elements, callback) { + // @ts-ignore + var promise = window.MathJax.typesetPromise(elements) + if (callback) { + promise = promise.then(callback) + } + return promise + }, + Register: { + MessageHook: deprecated, + StartupHook: deprecated, + LoadHook: deprecated, + }, + Config: deprecated, + Configured: deprecated, + setRenderer: deprecated, + } + }, + }, + tex: { + inlineMath: [ + ["$", "$"], + ["\\(", "\\)"], + ], + }, + svg: { + fontCache: "global", + }, + } + + requestIdleCallback( + () => { + console.log("Loading mathjax!!") + const script = document.head.querySelector("#MathJax-script") + script.setAttribute("src", script.getAttribute("not-the-src-yet")) + }, + { timeout: 2000 } + ) } diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 2792bfe664..0d19bb67f5 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -25,6 +25,7 @@ import { PlutoContext, PlutoBondsContext, PlutoJSInitializingContext, SetWithEmp import { unpack } from "../common/MsgPack.js" import { PkgTerminalView } from "./PkgTerminalView.js" import { start_binder, BinderPhase, count_stat } from "../common/Binder.js" +import { setup_mathjax } from "../common/SetupMathJax.js" import { read_Uint8Array_with_progress, FetchProgress } from "./FetchProgress.js" import { BinderButton } from "./BinderButton.js" import { slider_server_actions, nothing_actions } from "../common/SliderServerClient.js" @@ -1165,6 +1166,10 @@ patch: ${JSON.stringify( if (old_state.disable_ui !== this.state.disable_ui) { this.on_disable_ui() } + if (old_state.initializing && !this.state.initializing) { + console.info("Initialization done!") + setup_mathjax() + } if (old_state.notebook.nbpkg?.restart_recommended_msg !== new_state.notebook.nbpkg?.restart_recommended_msg) { console.warn(`New restart recommended message: ${new_state.notebook.nbpkg?.restart_recommended_msg}`) diff --git a/frontend/editor.html b/frontend/editor.html index 40f8f2b10d..0a2d36b338 100644 --- a/frontend/editor.html +++ b/frontend/editor.html @@ -46,8 +46,8 @@ - - + + From 0a2145f9db4cdc939efe24d5c064596d17c52eb5 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 16:00:52 +0100 Subject: [PATCH 140/821] =?UTF-8?q?=F0=9F=90=8A=20Fix=20#1929=20causing=20?= =?UTF-8?q?a=20permanent=20break?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/CellOutput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index ebd37216c2..a8098b84ba 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -482,6 +482,7 @@ export let RawHTMLContainer = ({ body, className = "", persist_js_state = false, }) return () => { + js_init_set?.delete(container.current) invalidate_scripts.current?.() } }, [body, persist_js_state, last_run_timestamp, pluto_actions]) From 9a163a31e0d22e9f837600a286e415d3386a5735 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Feb 2022 21:54:48 +0100 Subject: [PATCH 141/821] =?UTF-8?q?=E2=8C=9B=EF=B8=8F=20Progress=20bar=20i?= =?UTF-8?q?n=20isolated=20cell=20view=20(#1950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/Editor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 0d19bb67f5..4d8f2e78b7 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -1194,6 +1194,7 @@ patch: ${JSON.stringify( <${PlutoContext.Provider} value=${this.actions}> <${PlutoBondsContext.Provider} value=${this.state.notebook.bonds}> <${PlutoJSInitializingContext.Provider} value=${this.js_init_set}> + <${ProgressBar} notebook=${this.state.notebook} binder_phase=${this.state.binder_phase} status=${status}/>
${this.state.notebook.cell_order.map( (cell_id, i) => html` From 47bcaf0fc3f0333bb5431684b61ebef74a553d80 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 13:39:56 +0100 Subject: [PATCH 142/821] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..2231ae1755 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: julialang From ec2bc1cb828d3747700ebd6a50b37ce3980d1a83 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 18:24:55 +0100 Subject: [PATCH 143/821] css tweak --- frontend/editor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/editor.css b/frontend/editor.css index 4946ed319e..edadd9396e 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2013,6 +2013,7 @@ pluto-helpbox .cm-line { /* from https://docs.julialang.org/en/v1/assets/themes/documenter-light.css */ font-family: "Roboto Mono", "SFMono-Regular", "Menlo", "Consolas", "Liberation Mono", "DejaVu Sans Mono", monospace; font-size: 0.95em; + line-height: initial; } pluto-helpbox pre code { font-size: 1em; From 4bd8ec58987c2d57a72e6ed78b4e87083669f3df Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 20:27:24 +0100 Subject: [PATCH 144/821] =?UTF-8?q?=E2=8F=B3=20Fix=20progress=20bar=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/Logs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/Logs.js b/frontend/components/Logs.js index e9907b892b..663804abc2 100644 --- a/frontend/components/Logs.js +++ b/frontend/components/Logs.js @@ -84,7 +84,7 @@ const Progress = ({ progress }) => { const bar_ref = useRef(null) useLayoutEffect(() => { - bar_ref.current.style.backgroundSize = `${progress * 100}%` + bar_ref.current.style.backgroundSize = `${progress * 100}% 100%` }, [progress]) return html`${Math.ceil(100 * progress)}%` From ecc31e76ff9e49d7b29176f4c80d8e9551a9424c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 20:46:57 +0100 Subject: [PATCH 145/821] Safari: fix smooth scroll polyfill --- frontend/common/Polyfill.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/common/Polyfill.js b/frontend/common/Polyfill.js index 28e226aa45..9764bda7b8 100644 --- a/frontend/common/Polyfill.js +++ b/frontend/common/Polyfill.js @@ -27,4 +27,5 @@ if (Blob.prototype.arrayBuffer == null) { } } -import "https://cdn.jsdelivr.net/npm/smoothscroll-polyfill@0.4.4/dist/smoothscroll.min.js" +import { polyfill as scroll_polyfill } from "https://esm.sh/seamless-scroll-polyfill@2.1.8/lib/polyfill.js" +scroll_polyfill() From 529266d60cf57bbe5ab08ecc61b3f1f358892c33 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 22:10:48 +0100 Subject: [PATCH 146/821] small CSS refactors --- frontend/components/PkgPopup.js | 20 ++++++++++---------- frontend/dark_color.css | 3 ++- frontend/editor.css | 21 ++++----------------- frontend/light_color.css | 1 + 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/frontend/components/PkgPopup.js b/frontend/components/PkgPopup.js index a1b297437a..7bdd469061 100644 --- a/frontend/components/PkgPopup.js +++ b/frontend/components/PkgPopup.js @@ -9,6 +9,13 @@ import { package_status, nbpkg_fingerprint_without_terminal } from "./PkgStatusM import { PkgTerminalView } from "./PkgTerminalView.js" 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 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) + export const PkgPopup = ({ notebook }) => { let pluto_actions = useContext(PlutoContext) @@ -90,13 +97,6 @@ export const PkgPopup = ({ notebook }) => { const showupdate = pkg_status?.offer_update ?? false - // 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 - const circle_outline_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/arrow-up-circle-outline.svg", import.meta.url) - const document_text_outline_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/document-text-outline.svg", import.meta.url) - const help_circle_outline_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/help-circle-outline.svg", import.meta.url) - //
${recent_event?.package_name}
return html` { } e.preventDefault() }} - >i⬆️
{ set_showterminal(!showterminal) e.preventDefault() }} - >i📄 i❔
<${PkgTerminalView} value=${terminal_value ?? "Loading..."} /> diff --git a/frontend/dark_color.css b/frontend/dark_color.css index c995ce31f4..51cbe22b99 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -88,7 +88,8 @@ --loading-grad-color-2: #d0d4d7; /*saveall container*/ --overlay-button-bg: #2c2c2c; - --overlay-button-border: #c7a74670; + --overlay-button-border: #9e9e9e70; + --overlay-button-border-save: #c7a74670; --overlay-button-color: white; /*input_context_menu*/ diff --git a/frontend/editor.css b/frontend/editor.css index edadd9396e..768be1f9b7 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -30,6 +30,8 @@ --pluto-operator-ligatures: none; --julia-mono-font-stack: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace; --sans-serif-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --lato-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif; color-scheme: light dark; } @@ -901,24 +903,9 @@ main > preamble { pointer-events: none; } -main > preamble #saveall-container { - margin-left: auto; - pointer-events: all; - right: min(0px, 700px + 25px - 100vw); - position: relative; - background: var(--overlay-button-bg); - color: var(--overlay-button-color); - padding: 5px 8px; - border: 3px solid var(--overlay-button-border); - border-radius: 12px; - height: 35px; - font-family: "Segoe UI Emoji", "Roboto Mono", monospace; - font-size: 0.75rem; - pointer-events: all; -} - .overlay-button { background: var(--overlay-button-bg); + color: var(--overlay-button-color); padding: 5px 8px; border: 3px solid var(--overlay-button-border); border-radius: 12px; @@ -1695,7 +1682,7 @@ pkg-popup { border-radius: 10px; padding: 8px; overflow-wrap: break-word; - font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", sans-serif; + font-family: var(--lato-ui-font-stack); opacity: 0; transform: scale(0.2); transform-origin: left; diff --git a/frontend/light_color.css b/frontend/light_color.css index 252d0782cd..fa33f154a7 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -93,6 +93,7 @@ /*saveall container*/ --overlay-button-bg: #ffffff; --overlay-button-border: #f3f2f2; + --overlay-button-border-save: #f3f2f2; /*input_context_menu*/ --input-context-menu-border-color: rgba(0, 0, 0, 0.1); From b80f2c92c3197cf918f23919063ae9326f0439f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Feb 2022 23:24:06 +0100 Subject: [PATCH 147/821] CompatHelper: bump compat for RelocatableFolders to 0.2, (keep existing compat) (#1953) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 4ac0193e48..bf03618f83 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ Configurations = "0.15, 0.16, 0.17" FuzzyCompletions = "0.3,0.4,0.5" HTTP = "^0.9.1" MsgPack = "1.1" -RelocatableFolders = "0.1" +RelocatableFolders = "0.1, 0.2" Tables = "1" julia = "^1.6" From 8f088f7a9a84d2b98eb2e456bb5b5a2c726dd315 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 25 Feb 2022 23:30:10 +0100 Subject: [PATCH 148/821] =?UTF-8?q?=F0=9F=92=AC=20Generalized=20PkgPopup?= =?UTF-8?q?=20to=20any=20popup=20type=20(#1956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/Editor.js | 4 +- frontend/components/PkgStatusMark.js | 10 ++-- frontend/components/{PkgPopup.js => Popup.js} | 51 +++++++++++++------ frontend/editor.css | 33 ++++++++---- 4 files changed, 68 insertions(+), 30 deletions(-) rename frontend/components/{PkgPopup.js => Popup.js} (82%) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 4d8f2e78b7..9300b2ae42 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -16,7 +16,7 @@ import { UndoDelete } from "./UndoDelete.js" import { SlideControls } from "./SlideControls.js" import { Scroller } from "./Scroller.js" import { ExportBanner } from "./ExportBanner.js" -import { PkgPopup } from "./PkgPopup.js" +import { Popup } from "./Popup.js" import { slice_utf8, length_utf8 } from "../common/UnicodeTools.js" import { has_ctrl_or_cmd_pressed, ctrl_or_cmd_name, is_mac_keyboard, in_textarea_or_input } from "../common/KeyboardShortcuts.js" @@ -1378,7 +1378,7 @@ patch: ${JSON.stringify( on_update_doc_query=${this.actions.set_doc_query} notebook=${this.state.notebook} /> - <${PkgPopup} notebook=${this.state.notebook}/> + <${Popup} notebook=${this.state.notebook}/> <${UndoDelete} recently_deleted=${this.state.recently_deleted} on_click=${() => { diff --git a/frontend/components/PkgStatusMark.js b/frontend/components/PkgStatusMark.js index 9bc8abdf08..36594e817d 100644 --- a/frontend/components/PkgStatusMark.js +++ b/frontend/components/PkgStatusMark.js @@ -115,9 +115,10 @@ export const PkgStatusMark = ({ package_name, pluto_actions, notebook_id, nbpkg
- \ No newline at end of file + diff --git a/sample/test1.jl b/sample/test1.jl index 7005463cd6..2972e4334f 100644 --- a/sample/test1.jl +++ b/sample/test1.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.17.5 +# v0.19.8 using Markdown using InteractiveUtils @@ -45,6 +45,7 @@ md"## Stopping cells" md"# CodeMirror" # ╔═╡ bc5bf64e-8b47-45ac-baf2-3cbb8e35d916 +# (this code should error) """ Some sample code from https://juliamono.netlify.app/ """ @@ -451,6 +452,94 @@ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem # ╔═╡ 4e320206-d682-11ea-3dfe-b77f6e96f33a Text(s) +# ╔═╡ f16e48a7-f1db-4f15-a013-3d509b2f93ea +md""" +# LaTeX math +""" + +# ╔═╡ c3ae2fd1-628b-4876-9253-053805c47295 +md""" +This should be centered: + +$$T^{n+1}_i = \kappa (T^n_{i-1} - 2 T^n_i + T^n_{i+1}).$$ + +and should look exactly the same as: + +```math +T^{n+1}_i = \kappa (T^n_{i-1} - 2 T^n_i + T^n_{i+1}). +``` + +this should be on the left: + + $T^{n+1}_i = \kappa (T^n_{i-1} - 2 T^n_i + T^n_{i+1}).$ +""" + +# ╔═╡ ae4ab84b-ed29-4e09-b9c3-049d287e8c60 +md""" +This z``\alpha``z and z$\alpha$z should look the same. +""" + +# ╔═╡ 4b44cf5b-cac3-4283-b156-7adfce878228 +md""" +> Calling `T` the current vector, i.e. $\mathbf{T}^n := (T^n_i)_{i=1, \ldots, N_x}$, and `T′` the new vector at the next time step, we have the following basic expression, for the ``i``th element. +""" + +# ╔═╡ a10b9a7c-33d6-447e-99c0-c96a43fb3c6f +md""" +$$\begin{aligned} +\dot{x} &= a + x^2 y - bx - x \\ +\dot{y} &= b x - x^2 y +\end{aligned}$$ +""" + +# ╔═╡ ec741d6c-b148-4929-8805-2cd93ce6fca7 +md""" +## Extensions +""" + +# ╔═╡ 1d10093f-3256-46a8-b0d4-5186092825e2 +md""" +### `mchem` +Here is a chemical reaction: ``\ce{CO2 + C -> 2 CO}``. And another one: + +```math +\ce{Hg^2+ ->[I-] $\underset{\mathrm{red}}{\ce{HgI2}}$ ->[I-] $\underset{\mathrm{red}}{\ce{[Hg^{II}I4]^2-}}$} +``` +""" + +# ╔═╡ 7376d9e9-5740-4b67-9e8e-a2447e2c17cf +html""" +

This should look like a centered, black version of

+image + +""" + +# ╔═╡ d61ed468-4922-4d7b-b7f1-475e17bcd1e4 +md""" +### `cases` +```math +\begin{numcases} {|x|=} +x, & for $x \geq 0$\\ +-x, & for $x < 0$ +\end{numcases} +``` +""" + +# ╔═╡ 9d4c1242-cea0-4e76-8554-de0e05938de3 +md""" +### `physics` +```math +\require{physics} +\principalvalue hysics +``` +""" + +# ╔═╡ fe97e64f-9de6-46aa-bdb4-a1d6e2f61297 +md""" +### `color` +``\color{red}{i\ am\ red}`` +""" + # ╔═╡ 1bb05fc0-b15d-11ea-3dae-7734f66a0c56 md"# Testing machinery" @@ -703,7 +792,7 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" # ╔═╡ Cell order: # ╟─ce7f9d90-b163-11ea-0ff7-cf7378644741 # ╠═878a4750-b15e-11ea-2584-8feba490699f -# ╟─fd0763a0-b163-11ea-23b4-a7bae7052e19 +# ╠═fd0763a0-b163-11ea-23b4-a7bae7052e19 # ╟─11f3ae90-b164-11ea-1027-0d2e6e7048dd # ╟─26d2b310-b164-11ea-0029-f7b04ee5ba73 # ╟─1c6229f0-b165-11ea-0f1d-674022c43971 @@ -815,6 +904,17 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" # ╟─46fc284a-d682-11ea-34b6-69874efcaf65 # ╟─4d452956-d682-11ea-3aeb-cd7d1b2f67dc # ╠═4e320206-d682-11ea-3dfe-b77f6e96f33a +# ╟─f16e48a7-f1db-4f15-a013-3d509b2f93ea +# ╟─c3ae2fd1-628b-4876-9253-053805c47295 +# ╟─ae4ab84b-ed29-4e09-b9c3-049d287e8c60 +# ╟─4b44cf5b-cac3-4283-b156-7adfce878228 +# ╟─a10b9a7c-33d6-447e-99c0-c96a43fb3c6f +# ╟─ec741d6c-b148-4929-8805-2cd93ce6fca7 +# ╟─1d10093f-3256-46a8-b0d4-5186092825e2 +# ╟─7376d9e9-5740-4b67-9e8e-a2447e2c17cf +# ╟─d61ed468-4922-4d7b-b7f1-475e17bcd1e4 +# ╟─9d4c1242-cea0-4e76-8554-de0e05938de3 +# ╟─fe97e64f-9de6-46aa-bdb4-a1d6e2f61297 # ╟─1bb05fc0-b15d-11ea-3dae-7734f66a0c56 # ╠═9ac925d0-b15d-11ea-2abd-7db360900be0 # ╠═7e2cc6c0-b15d-11ea-32b0-15394cdebd35 From 4d3410763000e7241c093b90198297401f81805e Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 15 Jun 2022 23:27:34 +0200 Subject: [PATCH 376/821] Request autocompletions for latex on launch, this time really cc @rikhuijzer --- frontend/components/Editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index bf002bd4d5..8c64c0643a 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -779,7 +779,7 @@ patch: ${JSON.stringify( // TODO Do this from julia itself this.client.send("complete", { query: "sq" }, { notebook_id: this.state.notebook.notebook_id }) - this.client.send("complete", { query: "\sq" }, { notebook_id: this.state.notebook.notebook_id }) + this.client.send("complete", { query: "\\sq" }, { notebook_id: this.state.notebook.notebook_id }) setTimeout(init_feedback, 2 * 1000) // 2 seconds - load feedback a little later for snappier UI } From 128d51c361f3d38027e673ac4c6825cc2f7e32e6 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 15 Jun 2022 23:37:18 +0200 Subject: [PATCH 377/821] context menu: copy output hover help --- frontend/components/CellInput.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 346c1b418f..b52baf7a59 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -884,7 +884,11 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, any_ : html`Show logs`} ` : null} - ${is_copy_output_supported() ? html`
  • Copy output
  • ` : null} + ${is_copy_output_supported() + ? html`
  • + Copy output +
  • ` + : null}
  • Coming soon…
  • ` : html``} From dc30133bb1558773cf22cb5e6d456c4c9044a988 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 00:15:02 +0200 Subject: [PATCH 378/821] Improve configuration docs --- frontend/common/PlutoConnection.js | 2 +- src/Configuration.jl | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/common/PlutoConnection.js b/frontend/common/PlutoConnection.js index 71693e5d21..4d5156d8f3 100644 --- a/frontend/common/PlutoConnection.js +++ b/frontend/common/PlutoConnection.js @@ -243,7 +243,7 @@ const default_ws_address = () => ws_address_from_base(window.location.href) /** * @typedef PlutoConnection * @type {{ - * session_options: Object, + * session_options: Record, * send: import("./PlutoConnectionSendFn").SendFn, * kill: () => void, * version_info: { diff --git a/src/Configuration.jl b/src/Configuration.jl index a7cdd26cdb..e211de4ef9 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -33,6 +33,7 @@ const PORT_DEFAULT = nothing const LAUNCH_BROWSER_DEFAULT = true const DISMISS_UPDATE_NOTIFICATION_DEFAULT = false const SHOW_FILE_SYSTEM_DEFAULT = true +const ENABLE_PACKAGE_AUTHOR_FEATURES_DEFAULT = true const DISABLE_WRITING_NOTEBOOK_FILES_DEFAULT = false const AUTO_RELOAD_FROM_FILE_DEFAULT = false const AUTO_RELOAD_FROM_FILE_COOLDOWN_DEFAULT = 0.4 @@ -49,13 +50,12 @@ const ON_EVENT_DEFAULT = function(a) #= @info "$(typeof(a))" =# end The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. -# Arguments +# Keyword arguments -- `root_url::Union{Nothing,String} = $ROOT_URL_DEFAULT` -- `host::String = "$HOST_DEFAULT"` -- `port::Union{Nothing,Integer} = $PORT_DEFAULT` +- `host::String = "$HOST_DEFAULT"` Set to `"127.0.0.1"` (default) to run on *localhost*, which makes the server available to your computer and the local network (LAN). Set to `"0.0.0.0"` to make the server available to the entire network (internet). +- `port::Union{Nothing,Integer} = $PORT_DEFAULT` When specified, this port will be used for the server. - `launch_browser::Bool = $LAUNCH_BROWSER_DEFAULT` -- `dismiss_update_notification::Bool = $DISMISS_UPDATE_NOTIFICATION_DEFAULT` +- `dismiss_update_notification::Bool = $DISMISS_UPDATE_NOTIFICATION_DEFAULT` If `false`, the Pluto frontend will check the Pluto.jl github releases for any new recommended updates, and show a notification if there are any. If `true`, this is disabled. - `show_file_system::Bool = $SHOW_FILE_SYSTEM_DEFAULT` - `notebook_path_suggestion::String = notebook_path_suggestion()` - `disable_writing_notebook_files::Bool = $DISABLE_WRITING_NOTEBOOK_FILES_DEFAULT` @@ -68,6 +68,7 @@ The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. - `simulated_pkg_lag::Real=$SIMULATED_PKG_LAG_DEFAULT` (internal) Extra lag to add to operations done by Pluto's package manager. Will be multiplied by `0.5 + rand()`. - `injected_javascript_data_url::String = "$INJECTED_JAVASCRIPT_DATA_URL_DEFAULT"` (internal) Optional javascript injectables to the front-end. Can be used to customize the editor, but this API is not meant for general use yet. - `on_event::Function = $ON_EVENT_DEFAULT` +- `root_url::Union{Nothing,String} = $ROOT_URL_DEFAULT` This setting is used to specify the root URL of the Pluto server, but this setting is *only* used to customize the launch message (*"Go to http://localhost:1234/ in your browser"*). You can probably ignore this. """ @option mutable struct ServerOptions root_url::Union{Nothing,String} = ROOT_URL_DEFAULT @@ -90,7 +91,7 @@ The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. end const REQUIRE_SECRET_FOR_OPEN_LINKS_DEFAULT = true -const REQUIRE_SECRET_FOR_ACESS_DEFAULT = true +const REQUIRE_SECRET_FOR_ACCESS_DEFAULT = true """ SecurityOptions([; kwargs...]) @@ -105,7 +106,7 @@ Security settings for the HTTP server. Use `true` for almost every setup. Only use `false` if Pluto is running in a safe container (like mybinder.org), where arbitrary code execution is not a problem. -- `require_secret_for_access::Bool = $REQUIRE_SECRET_FOR_ACESS_DEFAULT` +- `require_secret_for_access::Bool = $REQUIRE_SECRET_FOR_ACCESS_DEFAULT` If false, you do not need to use a `secret` in the URL to access Pluto: you will be authenticated by visiting `http://localhost:1234/` in your browser. An authentication cookie is still used for access (to prevent XSS and deceptive links or an img src to `http://localhost:1234/open?url=badpeople.org/script.jl`), and is set automatically, but this request to `/` is protected by cross-origin policy. @@ -117,7 +118,7 @@ Note that Pluto is quickly evolving software, maintained by designers, educators """ @option mutable struct SecurityOptions require_secret_for_open_links::Bool = REQUIRE_SECRET_FOR_OPEN_LINKS_DEFAULT - require_secret_for_access::Bool = REQUIRE_SECRET_FOR_ACESS_DEFAULT + require_secret_for_access::Bool = REQUIRE_SECRET_FOR_ACCESS_DEFAULT end const RUN_NOTEBOOK_ON_LOAD_DEFAULT = true @@ -231,7 +232,7 @@ function from_flat_kwargs(; injected_javascript_data_url::String = INJECTED_JAVASCRIPT_DATA_URL_DEFAULT, on_event::Function = ON_EVENT_DEFAULT, require_secret_for_open_links::Bool = REQUIRE_SECRET_FOR_OPEN_LINKS_DEFAULT, - require_secret_for_access::Bool = REQUIRE_SECRET_FOR_ACESS_DEFAULT, + require_secret_for_access::Bool = REQUIRE_SECRET_FOR_ACCESS_DEFAULT, run_notebook_on_load::Bool = RUN_NOTEBOOK_ON_LOAD_DEFAULT, workspace_use_distributed::Bool = WORKSPACE_USE_DISTRIBUTED_DEFAULT, lazy_workspace_creation::Bool = LAZY_WORKSPACE_CREATION_DEFAULT, From c21290a7e23aace544d3c5fefca2ac6a4e4e34c5 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Thu, 16 Jun 2022 00:40:58 +0200 Subject: [PATCH 379/821] Implement @skip_as_script functionality inside cell metadata (#2018) Co-authored-by: Paul Berg Co-authored-by: Fons van der Plas --- frontend/components/Cell.js | 32 +++++++++++++----- frontend/components/CellInput.js | 23 +++++++++++-- frontend/components/Editor.js | 14 +++++--- frontend/components/Notebook.js | 4 +-- frontend/dark_color.css | 1 + frontend/editor.css | 24 ++++++++++++-- frontend/light_color.css | 1 + src/notebook/Cell.jl | 3 ++ src/notebook/saving and loading.jl | 3 +- test/Notebook.jl | 53 ++++++++++++++++++++++++++++++ 10 files changed, 137 insertions(+), 21 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 644dd143f8..d42fb3c572 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -7,6 +7,7 @@ import { Logs } from "./Logs.js" import { RunArea, useDebouncedTruth } from "./RunArea.js" import { cl } from "../common/ClassTable.js" import { PlutoActionsContext } from "../common/PlutoContext.js" +import { open_pluto_popup } from "./Popup.js" const useCellApi = (node_ref, published_object_keys, pluto_actions) => { const [cell_api_ready, set_cell_api_ready] = useState(false) @@ -55,7 +56,7 @@ export const Cell = ({ nbpkg, global_definition_locations, }) => { - const { show_logs, disabled: running_disabled } = metadata + const { show_logs, disabled: running_disabled, skip_as_script } = metadata let pluto_actions = useContext(PlutoActionsContext) const on_update_doc_query = pluto_actions.set_doc_query const on_focus_neighbor = pluto_actions.focus_on_neighbor @@ -159,15 +160,16 @@ export const Cell = ({ ref=${node_ref} class=${cl({ queued: queued || (waiting_to_run && is_process_ready), - running: running, - activate_animation: activate_animation, - errored: errored, - selected: selected, + running, + activate_animation, + errored, + selected, code_differs: class_code_differs, code_folded: class_code_folded, - running_disabled: running_disabled, - depends_on_disabled_cells: depends_on_disabled_cells, - show_input: show_input, + skip_as_script, + running_disabled, + depends_on_disabled_cells, + show_input, shrunk: Object.values(logs).length > 0, hooked_up: output?.has_pluto_hook_features ?? false, })} @@ -240,6 +242,20 @@ export const Cell = ({ > + ${skip_as_script + ? html`
    { + open_pluto_popup({ + type: "info", + source_element: e.target, + body: html`This cell is currently stored in the notebook file as a Julia comment, instead of code.
    + This way, it will not run when the notebook runs as a script outside of Pluto.`, + }) + }} + >
    ` + : null} ` } diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index b52baf7a59..4fcac96f82 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -375,7 +375,7 @@ export const CellInput = ({ global_definition_locations, }) => { let pluto_actions = useContext(PlutoActionsContext) - const { disabled: running_disabled } = metadata + const { disabled: running_disabled, skip_as_script } = metadata const newcm_ref = useRef(/** @type {EditorView?} */ (null)) const dom_node_ref = useRef(/** @type {HTMLElement?} */ (null)) @@ -814,6 +814,7 @@ export const CellInput = ({ on_delete=${on_delete} cell_id=${cell_id} run_cell=${on_submit} + skip_as_script=${skip_as_script} running_disabled=${running_disabled} any_logs=${any_logs} show_logs=${show_logs} @@ -823,13 +824,21 @@ export const CellInput = ({ ` } -const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, any_logs, show_logs, set_show_logs }) => { +const InputContextMenu = ({ on_delete, cell_id, run_cell, skip_as_script, running_disabled, any_logs, show_logs, set_show_logs }) => { const timeout = useRef(null) let pluto_actions = useContext(PlutoActionsContext) const [open, setOpen] = useState(false) const mouseenter = () => { if (timeout.current) clearTimeout(timeout.current) } + const toggle_skip_as_script = async (e) => { + const new_val = !skip_as_script + e.preventDefault() + // e.stopPropagation() + await pluto_actions.update_notebook((notebook) => { + notebook.cell_inputs[cell_id].metadata["skip_as_script"] = new_val + }) + } const toggle_running_disabled = async (e) => { const new_val = !running_disabled e.preventDefault() @@ -889,7 +898,15 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, running_disabled, any_ Copy output ` : null} -
  • Coming soon…
  • +
  • + ${skip_as_script ? html`` : html``} + ${skip_as_script ? html`Enable in file` : html`Disable in file`} +
  • ` : html``} ` diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 8c64c0643a..c265fed812 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -41,9 +41,11 @@ export const default_path = "..." const DEBUG_DIFFING = false // Be sure to keep this in sync with DEFAULT_CELL_METADATA in Cell.jl +/** @type {CellMetaData} */ const DEFAULT_CELL_METADATA = { disabled: false, show_logs: true, + skip_as_script: false, } // from our friends at https://stackoverflow.com/a/2117523 @@ -104,15 +106,19 @@ const first_true_key = (obj) => { } /** + * @typedef CellMetaData + * @type {{ + * disabled: boolean, + * show_logs: boolean, + * skip_as_script: boolean + * }} + * * @typedef CellInputData * @type {{ * cell_id: string, * code: string, * code_folded: boolean, - * metadata: { - * disabled: boolean, - * show_logs: boolean, - * }, + * metadata: CellMetaData, * }} */ diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index d208b225a7..c97af65383 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -63,8 +63,8 @@ let CellMemo = ({ }, [ // Object references may invalidate this faster than the optimal. To avoid this, spread out objects to primitives! cell_id, - metadata.disabled, - metadata.show_logs, + ...Object.keys(metadata), + ...Object.values(metadata), depends_on_disabled_cells, queued, running, diff --git a/frontend/dark_color.css b/frontend/dark_color.css index 51cbe22b99..85d722c801 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -59,6 +59,7 @@ --selected-cell-bg-color: rgb(42 115 205 / 78%); --hover-scrollbar-color-1: rgba(0, 0, 0, 0.15); --hover-scrollbar-color-2: rgba(0, 0, 0, 0.05); + --skip-as-script-border-color: #888; /* Pluto shoulders */ --shoulder-hover-bg-color: rgba(255, 255, 255, 0.05); diff --git a/frontend/editor.css b/frontend/editor.css index 06a2722d04..93978ededc 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -1109,6 +1109,19 @@ pluto-cell.running_disabled > pluto-output { background-color: var(--disabled-cell-bg-color); } +/* SKIP AS SCRIPT CELLS */ +pluto-cell.skip_as_script .skip_as_script_marker { + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + cursor: help; + right: -4.85px; + border-radius: 5px; + border-right: 5px solid var(--skip-as-script-border-color); +} + /* SELECTION */ pluto-cell.selected { @@ -1531,13 +1544,13 @@ pluto-cell > button.add_cell > span::after { filter: var(--image-filters); } pluto-input > button.input_context_menu ul { - --width: 156px; left: 100%; top: -8px; margin: 0; padding: 0px; border-radius: 6px; - display: block; + display: grid; + grid-template-columns: max-content; position: absolute; border: 1px solid var(--input-context-menu-border-color); background-color: var(--input-context-menu-bg-color); @@ -1564,7 +1577,6 @@ pluto-input > button.input_context_menu ul { position: relative; list-style: none; padding: 8px; - width: var(--width); height: 32px; display: flex; align-items: center; @@ -1632,6 +1644,12 @@ pluto-input > button.input_context_menu ul { .ctx_icon.delete { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle-outline.svg"); } +.ctx_icon.run_as_script { + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/document-lock-outline.svg"); +} +.ctx_icon.skip_as_script { + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/document-text-outline.svg"); +} .ctx_icon.copy_output { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/copy-outline.svg"); diff --git a/frontend/light_color.css b/frontend/light_color.css index fa33f154a7..de08581241 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -62,6 +62,7 @@ --selected-cell-bg-color: rgba(40, 78, 189, 0.24); --hover-scrollbar-color-1: rgba(0, 0, 0, 0.15); --hover-scrollbar-color-2: rgba(0, 0, 0, 0.05); + --skip-as-script-border-color: #ccc; /* Pluto shoulders */ --shoulder-hover-bg-color: rgba(0, 0, 0, 0.05); diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index a01902699b..d3bfbed267 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -3,11 +3,13 @@ import .ExpressionExplorer: SymbolsState, UsingsImports const METADATA_DISABLED_KEY = "disabled" const METADATA_SHOW_LOGS_KEY = "show_logs" +const METADATA_SKIP_AS_SCRIPT_KEY = "skip_as_script" # Make sure to keep this in sync with DEFAULT_CELL_METADATA in ../frontend/components/Editor.js const DEFAULT_CELL_METADATA = Dict{String, Any}( METADATA_DISABLED_KEY => false, METADATA_SHOW_LOGS_KEY => true, + METADATA_SKIP_AS_SCRIPT_KEY => false, ) Base.@kwdef struct CellOutput @@ -79,3 +81,4 @@ end "Returns whether or not the cell is **explicitely** disabled." is_disabled(c::Cell) = get(c.metadata, METADATA_DISABLED_KEY, DEFAULT_CELL_METADATA[METADATA_DISABLED_KEY]) can_show_logs(c::Cell) = get(c.metadata, METADATA_SHOW_LOGS_KEY, DEFAULT_CELL_METADATA[METADATA_SHOW_LOGS_KEY]) +is_skipped_as_script(c::Cell) = get(c.metadata, METADATA_SKIP_AS_SCRIPT_KEY, DEFAULT_CELL_METADATA[METADATA_SKIP_AS_SCRIPT_KEY]) diff --git a/src/notebook/saving and loading.jl b/src/notebook/saving and loading.jl index 00a915743d..5fd47c0da5 100644 --- a/src/notebook/saving and loading.jl +++ b/src/notebook/saving and loading.jl @@ -69,7 +69,8 @@ function save_notebook(io::IO, notebook::Notebook) end cell_running_disabled = is_disabled(c)::Bool - if cell_running_disabled || c.depends_on_disabled_cells + cell_skip_as_script = is_skipped_as_script(c)::Bool + if cell_skip_as_script || cell_running_disabled || c.depends_on_disabled_cells print(io, _disabled_prefix) print(io, replace(c.code, _cell_id_delimiter => "# ")) print(io, _disabled_suffix) diff --git a/test/Notebook.jl b/test/Notebook.jl index f3d2ef4016..dfc93fefee 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -46,6 +46,34 @@ function cell_metadata_notebook() ]) |> init_packages! end +function ingredients(path::String) + # this is from the Julia source code (evalfile in base/loading.jl) + # but with the modification that it returns the module instead of the last object + name = Symbol(basename(path)) + m = Module(name) + Core.eval(m, + Expr(:toplevel, + :(eval(x) = $(Expr(:core, :eval))($name, x)), + :(include(x) = $(Expr(:top, :include))($name, x)), + :(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, $name, x)), + :(include($path)))) + m +end + +function skip_as_script_notebook() + Notebook([ + Cell( + code="skipped_var = 10", + metadata=Dict( + "skip_as_script" => true, + ) |> create_cell_metadata, + ), + Cell( + code="non_skipped_var = 15", + ), + ]) |> init_packages! +end + function notebook_metadata_notebook() nb = Notebook([ Cell(code="n * (n + 1) / 2"), @@ -238,6 +266,31 @@ end WorkspaceManager.unmake_workspace((🍭, nb); verbose=false) end + + @testset "Skip as script" begin + 🍭 = ServerSession() + 🍭.options.evaluation.workspace_use_distributed = false + fakeclient = ClientSession(:fake, nothing) + 🍭.connected_clients[fakeclient.id] = fakeclient + + nb = skip_as_script_notebook() + update_run!(🍭, nb, nb.cells) + + save_notebook(nb) + + m = ingredients(nb.path) + @test !isdefined(m, :skipped_var) + @test m.non_skipped_var == 15 + + nb.cells[1].metadata["skip_as_script"] = false + save_notebook(nb) + + m = ingredients(nb.path) + @test m.skipped_var == 10 + @test m.non_skipped_var == 15 + + WorkspaceManager.unmake_workspace((🍭, nb); verbose=false) + end @testset "More Metadata" begin test_file_contents = """ From 20cac72b1e3bd120b364343ee6afafd42b0b3477 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 01:25:42 +0200 Subject: [PATCH 380/821] Allow customizing the :1234 "port hint" (#2173) --- src/Configuration.jl | 5 +++++ src/webserver/WebServer.jl | 7 ++++--- test/webserver.jl | 6 +++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Configuration.jl b/src/Configuration.jl index e211de4ef9..5ae65b5143 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -30,6 +30,7 @@ end const ROOT_URL_DEFAULT = nothing const HOST_DEFAULT = "127.0.0.1" const PORT_DEFAULT = nothing +const PORT_HINT_DEFAULT = 1234 const LAUNCH_BROWSER_DEFAULT = true const DISMISS_UPDATE_NOTIFICATION_DEFAULT = false const SHOW_FILE_SYSTEM_DEFAULT = true @@ -54,6 +55,7 @@ The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. - `host::String = "$HOST_DEFAULT"` Set to `"127.0.0.1"` (default) to run on *localhost*, which makes the server available to your computer and the local network (LAN). Set to `"0.0.0.0"` to make the server available to the entire network (internet). - `port::Union{Nothing,Integer} = $PORT_DEFAULT` When specified, this port will be used for the server. +- `port_hint::Integer = $PORT_HINT_DEFAULT` If the other setting `port` is not specified, then this setting (`port_hint`) will be used as the starting point in finding an available port to run the server on. - `launch_browser::Bool = $LAUNCH_BROWSER_DEFAULT` - `dismiss_update_notification::Bool = $DISMISS_UPDATE_NOTIFICATION_DEFAULT` If `false`, the Pluto frontend will check the Pluto.jl github releases for any new recommended updates, and show a notification if there are any. If `true`, this is disabled. - `show_file_system::Bool = $SHOW_FILE_SYSTEM_DEFAULT` @@ -74,6 +76,7 @@ The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. root_url::Union{Nothing,String} = ROOT_URL_DEFAULT host::String = HOST_DEFAULT port::Union{Nothing,Integer} = PORT_DEFAULT + port_hint::Integer = PORT_HINT_DEFAULT launch_browser::Bool = LAUNCH_BROWSER_DEFAULT dismiss_update_notification::Bool = DISMISS_UPDATE_NOTIFICATION_DEFAULT show_file_system::Bool = SHOW_FILE_SYSTEM_DEFAULT @@ -217,6 +220,7 @@ function from_flat_kwargs(; root_url::Union{Nothing,String} = ROOT_URL_DEFAULT, host::String = HOST_DEFAULT, port::Union{Nothing,Integer} = PORT_DEFAULT, + port_hint::Integer = PORT_HINT_DEFAULT, launch_browser::Bool = LAUNCH_BROWSER_DEFAULT, dismiss_update_notification::Bool = DISMISS_UPDATE_NOTIFICATION_DEFAULT, show_file_system::Bool = SHOW_FILE_SYSTEM_DEFAULT, @@ -250,6 +254,7 @@ function from_flat_kwargs(; root_url, host, port, + port_hint, launch_browser, dismiss_update_notification, show_file_system, diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index fdf1942204..5f967ba4bc 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -112,10 +112,10 @@ end const is_first_run = Ref(true) "Return a port and serversocket to use while taking into account the `favourite_port`." -function port_serversocket(hostIP::Sockets.IPAddr, favourite_port) +function port_serversocket(hostIP::Sockets.IPAddr, favourite_port, port_hint) local port, serversocket if favourite_port === nothing - port, serversocket = Sockets.listenany(hostIP, UInt16(1234)) + port, serversocket = Sockets.listenany(hostIP, UInt16(port_hint)) else port = UInt16(favourite_port) try @@ -143,8 +143,9 @@ function run(session::ServerSession, pluto_router) host = session.options.server.host hostIP = parse(Sockets.IPAddr, host) favourite_port = session.options.server.port + port_hint = session.options.server.port_hint - local port, serversocket = port_serversocket(hostIP, favourite_port) + local port, serversocket = port_serversocket(hostIP, favourite_port, port_hint) shutdown_server = Ref{Function}(() -> ()) diff --git a/test/webserver.jl b/test/webserver.jl index 7ff4777212..bbf68fd9ef 100644 --- a/test/webserver.jl +++ b/test/webserver.jl @@ -10,7 +10,11 @@ using Pluto.WorkspaceManager: WorkspaceManager, poll @testset "Exports" begin - @inferred Pluto.port_serversocket(Sockets.ip"0.0.0.0", nothing) + port, socket = + @inferred Pluto.port_serversocket(Sockets.ip"0.0.0.0", nothing, 5543) + + close(socket) + @test 5543 <= port < 5600 port = 13432 host = "localhost" From 4e7a48d7087036f40d03e88f51bb830fa90a1516 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 11:49:29 +0200 Subject: [PATCH 381/821] Only enable the skip_as_script feature behind a flag --- frontend/components/CellInput.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 4fcac96f82..598a5576dc 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -66,6 +66,7 @@ import { debug_syntax_plugin } from "./CellInput/debug_syntax_plugin.js" import { ScopeStateField } from "./CellInput/scopestate_statefield.js" export const ENABLE_CM_MIXED_PARSER = window.localStorage.getItem("ENABLE_CM_MIXED_PARSER") === "true" +export const ENABLE_SKIP_AS_SCRIPT = window.localStorage.getItem("ENABLE_SKIP_AS_SCRIPT") === "true" if (ENABLE_CM_MIXED_PARSER) { console.log(`YOU ENABLED THE CODEMIRROR MIXED LANGUAGE PARSER @@ -81,6 +82,11 @@ window.PLUTO_TOGGLE_CM_MIXED_PARSER = () => { window.localStorage.setItem("ENABLE_CM_MIXED_PARSER", String(!ENABLE_CM_MIXED_PARSER)) window.location.reload() } +// @ts-ignore +window.PLUTO_TOGGLE_SKIP_AS_SCRIPT = () => { + window.localStorage.setItem("ENABLE_SKIP_AS_SCRIPT", String(!ENABLE_SKIP_AS_SCRIPT)) + window.location.reload() +} export const pluto_syntax_colors = HighlightStyle.define( [ @@ -898,15 +904,17 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, skip_as_script, runnin Copy output ` : null} -
  • - ${skip_as_script ? html`` : html``} - ${skip_as_script ? html`Enable in file` : html`Disable in file`} -
  • + ${ENABLE_SKIP_AS_SCRIPT || skip_as_script + ? html`
  • + ${skip_as_script ? html`` : html``} + ${skip_as_script ? html`Enable in file` : html`Disable in file`} +
  • ` + : null} ` : html``} ` From 853b452e92950871a6c4b8c0f1c1b472e63a38d4 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 11:49:53 +0200 Subject: [PATCH 382/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f0011d8b9c..f6c6abd129 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.19.8" +version = "0.19.9" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 6c4732a1888c40a1e9aec580b9e685258d1fd9ed Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 12:44:57 +0200 Subject: [PATCH 383/821] update versions in sample notebooks --- sample/Basic mathematics.jl | 185 ++++++++++++++++++------------------ sample/Getting started.jl | 5 +- sample/JavaScript.jl | 10 +- sample/Plots.jl.jl | 36 +++---- sample/PlutoUI.jl.jl | 10 +- 5 files changed, 124 insertions(+), 122 deletions(-) diff --git a/sample/Basic mathematics.jl b/sample/Basic mathematics.jl index c60f2d467c..e4739fa32a 100644 --- a/sample/Basic mathematics.jl +++ b/sample/Basic mathematics.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.11.4 +# v0.19.9 using Markdown using InteractiveUtils @@ -7,102 +7,13 @@ using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el end end -# ╔═╡ e5e0a0da-d45c-11ea-1042-e9b5d0654d4f -md"Fix the value of `c` below to make it `c = a * b`" - -# ╔═╡ 4dff4b5e-d461-11ea-29c8-d548fdb5f08b -md"Edit the equation below to calculate the number of pizzas to order using the variables above for **people**, **avg**, and **slices**:" - -# ╔═╡ f907e46a-d471-11ea-07e5-f30e2aab3d08 -md"""The diameter of a pizza is often stated on a menu so let's define a **formula** to calculate the area of a pizza given the diameter **d**. - -We do this by writing a formula like this: `area(d) = pi * (d/2)^2` - -Let's write that below: -""" - -# ╔═╡ d9575e9c-d472-11ea-1eda-2d335d039f28 -md"""Now we have a function called **area** that we can pass any diameter and it will return the area of a pizza (or circle), let's try that with the pizza from before with `area(2*r)` to get the area of the pizza: -""" - -# ╔═╡ edb95b14-d473-11ea-3a5a-77382d31f941 -md"""## Finding the best pizza deal - -Let's see if a larger pizza is a better value by calculating the price per area. There are 4 sizes: small, medium, large, extra large with the following prices: - -Size | Diameter (inches) | Price ($) -:------- | :---------------: | --------: -small | 9 | 13.10 -medium | 13 | 20.95 -large | 15 | 24.90 -XL | 17 | 30.95 - -### 1. How many small pizzas is the same as one XL pizza? - -Edit the expression below: -""" - -# ╔═╡ 5b07b8fe-d475-11ea-01aa-6b88d6ed8a05 -md"""### 2. Calculate the cost per area of each pizza: -""" - -# ╔═╡ a42e4eb0-d474-11ea-316a-3d864451bc01 -md"Which size of pizza is the best deal? Write your answer below and assign it to the variable **best_value**." - -# ╔═╡ cb419286-d4ff-11ea-1d7f-af5c8574b775 -md"""### 3. Is this a good deal? - -San Marinos has a special **\"Buy two medium pizzas and save \$5\"**. Is this a better deal than buying a extra-large pizza? - -Calculate the total cost of two medium pizzas deal (saving \$5):""" - -# ╔═╡ 0d76d97c-d500-11ea-2433-e96c6fc43b05 -md"Calculate the total area of two medium pizzas:" - -# ╔═╡ 20a1e9cc-d500-11ea-3d9b-279c71bc20f1 -md"Now calculate cost per area by taking the total cost of two medium pizzas and divide by the total area:" - -# ╔═╡ 57f024ae-d500-11ea-1cc4-ed28348fdf93 -md"""Is it a better deal to get two medium pizzas for \$5 off or to just buy an extra-large?""" - -# ╔═╡ 180c8fdc-d503-11ea-04ca-bf2c07fd1c17 -md"""### 4. Advanced Problem - -A new worker at a pizza shop was getting paid for cutting pizza into pieces. The pieces of pizza could be any size. Calculate the maximum number of pieces the worker could make with two cuts of the pizza.""" - -# ╔═╡ 92b4a012-d503-11ea-15a2-1f3a446d3284 -md"Now what about 3 cuts across the pizza? What is the maximum number of pieces that can be made with **3 cuts**?" - -# ╔═╡ 2eb9a560-d507-11ea-3b8b-9d06678fe131 -md"Now, how many pieces can be made with **4 cuts**?" - -# ╔═╡ d1e3dec0-d507-11ea-1213-d37a9325ee2f -md"Are you starting to see a pattern? Can you figure out a formula for how many pieces of pizza can be made with \"n\" cuts? Make a table and fill in the number of pieces for a number of cuts and see if you can find the pattern: - -Cuts | Pieces -:--- | ------: -0 | 1 -1 | 2 -2 | 4 -3 | -4 | -" - -# ╔═╡ 97bfd13c-dcc2-11ea-0067-ad8c2c6517fc -md"To get an extra hint, figure out how many slices we can get from **5 cuts**:" - -# ╔═╡ e0cb2822-dcc2-11ea-2c85-5748bfe526dc -md"Have you found the pattern? Write down the formula below:" - -# ╔═╡ 03249876-d508-11ea-16bb-fd5afed37a1f -md"""##### Let's test your formula!""" - # ╔═╡ 14158eb0-d45c-11ea-088f-330e45412320 a = 2 @@ -119,6 +30,9 @@ First let's do some simple math with setting **a = $a**, **b = $b** and **c = a Type in the cells (with the coloured background) below and press **`Shift-Enter`** or the click the right-arrow button (▶️) to the right to execute the cell after changing the values." +# ╔═╡ e5e0a0da-d45c-11ea-1042-e9b5d0654d4f +md"Fix the value of `c` below to make it `c = a * b`" + # ╔═╡ 30f0f882-d45c-11ea-2adc-7d84ecf8a7a6 c = 10 @@ -156,6 +70,9 @@ Number of slices on a piece of pizza | slices """ +# ╔═╡ 4dff4b5e-d461-11ea-29c8-d548fdb5f08b +md"Edit the equation below to calculate the number of pizzas to order using the variables above for **people**, **avg**, and **slices**:" + # ╔═╡ 444e2fa4-d460-11ea-12aa-57e0576c2d66 pizzas = 1 @@ -171,15 +88,48 @@ The area of a pizza is ``A = \pi r^2``. Lets try calculating the area of a pizz # ╔═╡ 50f0f6d6-d471-11ea-304e-8f72e7ef9d7e A = r^2 +# ╔═╡ f907e46a-d471-11ea-07e5-f30e2aab3d08 +md"""The diameter of a pizza is often stated on a menu so let's define a **formula** to calculate the area of a pizza given the diameter **d**. + +We do this by writing a formula like this: `area(d) = pi * (d/2)^2` + +Let's write that below: +""" + # ╔═╡ cb36a9ee-d472-11ea-1835-bf7963137e18 area(d) = pi * (d / 2)^2 +# ╔═╡ d9575e9c-d472-11ea-1eda-2d335d039f28 +md"""Now we have a function called **area** that we can pass any diameter and it will return the area of a pizza (or circle), let's try that with the pizza from before with `area(2*r)` to get the area of the pizza: +""" + # ╔═╡ 04b010c0-d473-11ea-1767-136c7e26e122 A2 = area(r) +# ╔═╡ edb95b14-d473-11ea-3a5a-77382d31f941 +md"""## Finding the best pizza deal + +Let's see if a larger pizza is a better value by calculating the price per area. There are 4 sizes: small, medium, large, extra large with the following prices: + +Size | Diameter (inches) | Price ($) +:------- | :---------------: | --------: +small | 9 | 13.10 +medium | 13 | 20.95 +large | 15 | 24.90 +XL | 17 | 30.95 + +### 1. How many small pizzas is the same as one XL pizza? + +Edit the expression below: +""" + # ╔═╡ 637c26fa-d475-11ea-2c5b-2b0f4775b119 smalls_in_xl = 1 +# ╔═╡ 5b07b8fe-d475-11ea-01aa-6b88d6ed8a05 +md"""### 2. Calculate the cost per area of each pizza: +""" + # ╔═╡ 3823d09e-d474-11ea-194e-59b5805f303b small = 13.10 / area(9) @@ -192,35 +142,86 @@ large = 24.90 / area(15) # ╔═╡ 962e6b86-d474-11ea-11a6-a1d11e33ae42 xl = 30.95 / area(17) +# ╔═╡ a42e4eb0-d474-11ea-316a-3d864451bc01 +md"Which size of pizza is the best deal? Write your answer below and assign it to the variable **best_value**." + # ╔═╡ 16ec3f32-d4ff-11ea-20e2-5bc6dd5db083 best_value = small +# ╔═╡ cb419286-d4ff-11ea-1d7f-af5c8574b775 +md"""### 3. Is this a good deal? + +San Marinos has a special **\"Buy two medium pizzas and save \$5\"**. Is this a better deal than buying a extra-large pizza? + +Calculate the total cost of two medium pizzas deal (saving \$5):""" + # ╔═╡ f147b6cc-d4ff-11ea-05ad-6f5b441e5d1b two_medium_cost = 20.95 * 1 - 0 +# ╔═╡ 0d76d97c-d500-11ea-2433-e96c6fc43b05 +md"Calculate the total area of two medium pizzas:" + # ╔═╡ 19eb2a82-d500-11ea-3782-596adc689382 two_medium_area = 1 * area(13) +# ╔═╡ 20a1e9cc-d500-11ea-3d9b-279c71bc20f1 +md"Now calculate cost per area by taking the total cost of two medium pizzas and divide by the total area:" + # ╔═╡ 70e85498-d500-11ea-35af-474574f5c011 two_medium_deal = 1 +# ╔═╡ 57f024ae-d500-11ea-1cc4-ed28348fdf93 +md"""Is it a better deal to get two medium pizzas for \$5 off or to just buy an extra-large?""" + +# ╔═╡ 180c8fdc-d503-11ea-04ca-bf2c07fd1c17 +md"""### 4. Advanced Problem + +A new worker at a pizza shop was getting paid for cutting pizza into pieces. The pieces of pizza could be any size. Calculate the maximum number of pieces the worker could make with two cuts of the pizza.""" + # ╔═╡ 6494e270-d503-11ea-38a7-df96e7f0a241 cuts2 = 1 +# ╔═╡ 92b4a012-d503-11ea-15a2-1f3a446d3284 +md"Now what about 3 cuts across the pizza? What is the maximum number of pieces that can be made with **3 cuts**?" + # ╔═╡ a05aae8e-d506-11ea-190f-57e9ce53b8b9 cuts3 = 1 +# ╔═╡ 2eb9a560-d507-11ea-3b8b-9d06678fe131 +md"Now, how many pieces can be made with **4 cuts**?" + # ╔═╡ 5a8ede88-d507-11ea-30d9-c99a67243781 cuts4 = 1 +# ╔═╡ d1e3dec0-d507-11ea-1213-d37a9325ee2f +md"Are you starting to see a pattern? Can you figure out a formula for how many pieces of pizza can be made with \"n\" cuts? Make a table and fill in the number of pieces for a number of cuts and see if you can find the pattern: + +Cuts | Pieces +:--- | ------: +0 | 1 +1 | 2 +2 | 4 +3 | +4 | +" + +# ╔═╡ 97bfd13c-dcc2-11ea-0067-ad8c2c6517fc +md"To get an extra hint, figure out how many slices we can get from **5 cuts**:" + # ╔═╡ bae0cb62-dcc2-11ea-0667-512e1c407d40 cuts5 = 1 +# ╔═╡ e0cb2822-dcc2-11ea-2c85-5748bfe526dc +md"Have you found the pattern? Write down the formula below:" + # ╔═╡ f5f89724-d507-11ea-0a93-6d904f36bbe4 function pieces(n) return n end +# ╔═╡ 03249876-d508-11ea-16bb-fd5afed37a1f +md"""##### Let's test your formula!""" + # ╔═╡ bd9f3d24-d509-11ea-165d-3d465a0b4542 md"""Move the slider to change the number of cuts: diff --git a/sample/Getting started.jl b/sample/Getting started.jl index 3fba0f7f7b..cf3c51ccea 100644 --- a/sample/Getting started.jl +++ b/sample/Getting started.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.11.14 +# v0.19.9 using Markdown using InteractiveUtils @@ -7,8 +7,9 @@ using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el end end diff --git a/sample/JavaScript.jl b/sample/JavaScript.jl index 507078e35d..d26481309b 100644 --- a/sample/JavaScript.jl +++ b/sample/JavaScript.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.19.5 +# v0.19.9 using Markdown using InteractiveUtils @@ -1116,9 +1116,9 @@ uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[ColorTypes]] deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "0f4e115f6f34bbe43c19751c90a38b2f380637b9" +git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.11.3" +version = "0.11.4" [[Dates]] deps = ["Printf"] @@ -1207,9 +1207,9 @@ uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" [[Parsers]] deps = ["Dates"] -git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413" +git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.3.1" +version = "2.3.2" [[Pkg]] deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] diff --git a/sample/Plots.jl.jl b/sample/Plots.jl.jl index 2da3ddba77..bc41af069b 100644 --- a/sample/Plots.jl.jl +++ b/sample/Plots.jl.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.19.5 +# v0.19.9 using Markdown using InteractiveUtils @@ -215,7 +215,7 @@ PLUTO_PROJECT_TOML_CONTENTS = """ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] -Plots = "~1.29.0" +Plots = "~1.30.0" """ # ╔═╡ 00000000-0000-0000-0000-000000000002 @@ -269,9 +269,9 @@ version = "3.18.0" [[ColorTypes]] deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "0f4e115f6f34bbe43c19751c90a38b2f380637b9" +git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.11.3" +version = "0.11.4" [[ColorVectorSpace]] deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"] @@ -397,15 +397,15 @@ version = "3.3.6+0" [[GR]] deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "RelocatableFolders", "Serialization", "Sockets", "Test", "UUIDs"] -git-tree-sha1 = "b316fd18f5bc025fedcb708332aecb3e13b9b453" +git-tree-sha1 = "c98aea696662d09e215ef7cda5296024a9646c75" uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.64.3" +version = "0.64.4" [[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 = "1e5490a51b4e9d07e8b04836f6008f46b48aaa87" +git-tree-sha1 = "3a233eeeb2ca45842fe100e0413936834215abf5" uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.64.3+0" +version = "0.64.4+0" [[GeometryBasics]] deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"] @@ -459,9 +459,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[InverseFunctions]] deps = ["Test"] -git-tree-sha1 = "336cc738f03e069ef2cac55a104eb823455dca75" +git-tree-sha1 = "b3364212fb5d870f724876ffcd34dd8ec6d98918" uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.4" +version = "0.1.7" [[IrrationalConstants]] git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151" @@ -691,9 +691,9 @@ version = "8.44.0+0" [[Parsers]] deps = ["Dates"] -git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413" +git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.3.1" +version = "2.3.2" [[Pixman_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -719,9 +719,9 @@ version = "1.2.0" [[Plots]] deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "d457f881ea56bbfa18222642de51e0abf67b9027" +git-tree-sha1 = "0b727ac13565a2b665cc78db579e0093b869034e" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.29.0" +version = "1.30.0" [[Preferences]] deps = ["TOML"] @@ -814,9 +814,9 @@ version = "2.1.6" [[StaticArrays]] deps = ["LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "cd56bf18ed715e8b09f06ef8c6b781e6cdc49911" +git-tree-sha1 = "2bbd9f2e40afd197a1379aef05e0d85dba649951" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.4.4" +version = "1.4.7" [[Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -824,9 +824,9 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[StatsAPI]] deps = ["LinearAlgebra"] -git-tree-sha1 = "c82aaa13b44ea00134f8c9c89819477bd3986ecd" +git-tree-sha1 = "2c11d7290036fe7aac9038ff312d3b3a2a5bf89e" uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.3.0" +version = "1.4.0" [[StatsBase]] deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] diff --git a/sample/PlutoUI.jl.jl b/sample/PlutoUI.jl.jl index 5eb8139421..1c38006113 100644 --- a/sample/PlutoUI.jl.jl +++ b/sample/PlutoUI.jl.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.19.5 +# v0.19.9 using Markdown using InteractiveUtils @@ -644,9 +644,9 @@ uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[ColorTypes]] deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "0f4e115f6f34bbe43c19751c90a38b2f380637b9" +git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.11.3" +version = "0.11.4" [[Dates]] deps = ["Printf"] @@ -735,9 +735,9 @@ uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" [[Parsers]] deps = ["Dates"] -git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413" +git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.3.1" +version = "2.3.2" [[Pkg]] deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] From a3b54afd667f20b302020a678d4d78e323f7af01 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 13:08:15 +0200 Subject: [PATCH 384/821] frontmatter GUI: bump rebel-tag-input --- frontend/components/FrontmatterInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/FrontmatterInput.js b/frontend/components/FrontmatterInput.js index f49d2f237b..5b1f65d0ea 100644 --- a/frontend/components/FrontmatterInput.js +++ b/frontend/components/FrontmatterInput.js @@ -1,7 +1,7 @@ import { html, Component, useRef, useLayoutEffect, useState, useEffect } from "../imports/Preact.js" import { has_ctrl_or_cmd_pressed } from "../common/KeyboardShortcuts.js" -import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.4/lib/rebel-tag-input.mjs" +import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.5/lib/rebel-tag-input.mjs" //@ts-ignore import dialogPolyfill from "https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.esm.min.js" From 3df8390a2eda312bad42d7ee274e5837c313815f Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 13:29:56 +0200 Subject: [PATCH 385/821] fix bundle --- frontend/components/FrontmatterInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/FrontmatterInput.js b/frontend/components/FrontmatterInput.js index 5b1f65d0ea..5af47b3499 100644 --- a/frontend/components/FrontmatterInput.js +++ b/frontend/components/FrontmatterInput.js @@ -1,7 +1,7 @@ import { html, Component, useRef, useLayoutEffect, useState, useEffect } from "../imports/Preact.js" import { has_ctrl_or_cmd_pressed } from "../common/KeyboardShortcuts.js" -import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.5/lib/rebel-tag-input.mjs" +import "https://cdn.jsdelivr.net/gh/fonsp/rebel-tag-input@1.0.6/lib/rebel-tag-input.mjs" //@ts-ignore import dialogPolyfill from "https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.esm.min.js" From f56f9c4dcd8d096918a1cc520e4fe652d06dff4c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 15:30:17 +0200 Subject: [PATCH 386/821] =?UTF-8?q?=F0=9F=A5=95=20`Pluto.set=5Ffrontmatter?= =?UTF-8?q?!`=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/notebook/frontmatter.jl | 22 ++++++++++++++++++++++ test/Notebook.jl | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/src/notebook/frontmatter.jl b/src/notebook/frontmatter.jl index c5842848e4..e93f03e257 100644 --- a/src/notebook/frontmatter.jl +++ b/src/notebook/frontmatter.jl @@ -28,3 +28,25 @@ function frontmatter(abs_path::String; raise::Bool=false) end end end + +""" +```julia +set_frontmatter!(nb::Notebook, new_value::Dict) +``` + +Set the new frontmatter of the [`Notebook`](@ref). Use [`frontmatter(nb)`](@ref) to get the old dictionary. + +If you want to save the file, call [`save_notebook(nb)`](@ref) afterwards. + +`set_frontmatter!(nb, nothing)` will delete the frontmatter. + +""" +function set_frontmatter!(nb::Notebook, ::Nothing) + delete!(nb.metadata, "frontmatter") +end + +function set_frontmatter!(nb::Notebook, new_value::Dict) + nb.metadata["frontmatter"] = convert(FrontMatter, new_value) +end + + diff --git a/test/Notebook.jl b/test/Notebook.jl index dfc93fefee..e22ebe7bf1 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -343,6 +343,14 @@ end @test Pluto.frontmatter(nb) == Dict( "title" => "cool stuff", ) + + Pluto.set_frontmatter!(nb, Dict("a" => "b")) + @test Pluto.frontmatter(nb) == Dict("a" => "b") + + Pluto.set_frontmatter!(nb, nothing) + @test Pluto.frontmatter(nb) == Dict() + Pluto.set_frontmatter!(nb, nothing) + @test Pluto.frontmatter(nb) == Dict() end @testset "I/O overloaded" begin From 96eb096e83afc5488481c743e3eccea62e7aed71 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 15:32:29 +0200 Subject: [PATCH 387/821] /uploadnotebook endpoint: clear_frontmatter query option --- src/webserver/SessionActions.jl | 11 ++++++++++- src/webserver/Static.jl | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index e8b2d31419..420d8408a6 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -41,7 +41,13 @@ function open_url(session::ServerSession, url::AbstractString; kwargs...) end "Open the notebook at `path` into `session::ServerSession` and run it. Returns the `Notebook`." -function open(session::ServerSession, path::AbstractString; run_async=true, compiler_options=nothing, as_sample=false, notebook_id::UUID=uuid1()) +function open(session::ServerSession, path::AbstractString; + run_async::Bool=true, + compiler_options=nothing, + as_sample::Bool=false, + clear_frontmatter::Bool=false, + notebook_id::UUID=uuid1() +) path = maybe_convert_path_to_wsl(path) if as_sample new_filename = "sample " * without_pluto_file_extension(basename(path)) @@ -64,6 +70,9 @@ function open(session::ServerSession, path::AbstractString; run_async=true, comp if compiler_options !== nothing nb.compiler_options = compiler_options end + if clear_frontmatter + set_frontmatter!(nb, nothing) + end session.notebooks[nb.notebook_id] = nb for c in nb.cells diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index 1c1b2513ca..0aa511d027 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -327,11 +327,13 @@ function http_router_for(session::ServerSession) query = HTTP.queryparams(uri) save_path = SessionActions.save_upload(request.body; filename_base=get(query, "name", nothing)) + try_launch_notebook_response( SessionActions.open, save_path; as_redirect=false, as_sample=false, + clear_frontmatter=!isnothing(get(query, "clear_frontmatter", nothing)), title="Failed to load notebook", advice="The contents could not be read as a Pluto notebook file. When copying contents from somewhere else, make sure that you copy the entire notebook file. You can also report this error!" ) From 5b987fd59405576355615f3aff6601a3036431da Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 15:37:05 +0200 Subject: [PATCH 388/821] fixiefix --- src/webserver/SessionActions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index 420d8408a6..1904c63017 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -1,6 +1,6 @@ module SessionActions -import ..Pluto: ServerSession, Notebook, Cell, emptynotebook, tamepath, new_notebooks_directory, without_pluto_file_extension, numbered_until_new, cutename, readwrite, update_save_run!, update_from_file, wait_until_file_unchanged, putnotebookupdates!, putplutoupdates!, load_notebook, clientupdate_notebook_list, WorkspaceManager, try_event_call, NewNotebookEvent, OpenNotebookEvent, ShutdownNotebookEvent, @asynclog, ProcessStatus, maybe_convert_path_to_wsl +import ..Pluto: Pluto, ServerSession, Notebook, Cell, emptynotebook, tamepath, new_notebooks_directory, without_pluto_file_extension, numbered_until_new, cutename, readwrite, update_save_run!, update_from_file, wait_until_file_unchanged, putnotebookupdates!, putplutoupdates!, load_notebook, clientupdate_notebook_list, WorkspaceManager, try_event_call, NewNotebookEvent, OpenNotebookEvent, ShutdownNotebookEvent, @asynclog, ProcessStatus, maybe_convert_path_to_wsl using FileWatching import ..Pluto.DownloadCool: download_cool import HTTP @@ -71,7 +71,7 @@ function open(session::ServerSession, path::AbstractString; nb.compiler_options = compiler_options end if clear_frontmatter - set_frontmatter!(nb, nothing) + Pluto.set_frontmatter!(nb, nothing) end session.notebooks[nb.notebook_id] = nb From 9787ebbb329f25a02b804ab060e1d859cb4cec9d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 17:39:13 +0200 Subject: [PATCH 389/821] =?UTF-8?q?=F0=9F=8C=9F=20Featured=20notebooks=20&?= =?UTF-8?q?=20new=20main=20menu=20(#2048)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: merlijnkersten <50485980+merlijnkersten@users.noreply.github.com> --- README.md | 8 +- frontend-bundler/package.json | 4 +- frontend/common/Binder.js | 21 +- frontend/common/Bond.js | 3 +- frontend/common/RunLocal.js | 80 ++++++ .../{BinderButton.js => EditOrRunButton.js} | 19 ++ frontend/components/Editor.js | 32 ++- frontend/components/Popup.js | 10 +- frontend/components/welcome/Featured.js | 167 +++++++++++ frontend/components/welcome/FeaturedCard.js | 93 +++++++ frontend/components/welcome/Open.js | 33 +-- frontend/components/welcome/Recent.js | 11 +- frontend/components/welcome/Welcome.js | 25 +- frontend/dark_color.css | 9 +- frontend/editor.css | 19 +- frontend/editor.js | 11 +- frontend/featured_sources.js | 8 + frontend/img/favicon_unsaturated_bg.svg | 18 ++ frontend/index.css | 233 ++++++++++++---- frontend/index.html | 53 +++- frontend/index.js | 2 +- frontend/light_color.css | 7 + frontend/sample.html | 44 --- frontend/welcome.css | 262 ++++++++++++++++++ 24 files changed, 1012 insertions(+), 160 deletions(-) create mode 100644 frontend/common/RunLocal.js rename frontend/components/{BinderButton.js => EditOrRunButton.js} (93%) create mode 100644 frontend/components/welcome/Featured.js create mode 100644 frontend/components/welcome/FeaturedCard.js create mode 100644 frontend/featured_sources.js create mode 100755 frontend/img/favicon_unsaturated_bg.svg delete mode 100644 frontend/sample.html create mode 100644 frontend/welcome.css diff --git a/README.md b/README.md index 22d5c17def..824fb1b002 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ Lastly, here's _**one more feature**_: Pluto notebooks have a `@bind` macro to c
    -You don't need to know HTML to use it! The [PlutoUI package](https://github.com/fonsp/PlutoUI.jl) contains basic inputs like sliders and buttons. Pluto's interactivity is very easy to use, you will learn more from the sample notebooks inside Pluto! +You don't need to know HTML to use it! The [PlutoUI package](https://github.com/fonsp/PlutoUI.jl) contains basic inputs like sliders and buttons. Pluto's interactivity is very easy to use, you will learn more from the featured notebooks inside Pluto! -But for those who want to dive deeper - you can use HTML, JavaScript and CSS to write your own widgets! Custom update events can be fired by dispatching a `new CustomEvent("input")`, making it compatible with the [`viewof` operator of observablehq](https://observablehq.com/@observablehq/a-brief-introduction-to-viewof). Have a look at the JavaScript sample notebook inside Pluto! +But for those who want to dive deeper - you can use HTML, JavaScript and CSS to write your own widgets! Custom update events can be fired by dispatching a `new CustomEvent("input")`, making it compatible with the [`viewof` operator of observablehq](https://observablehq.com/@observablehq/a-brief-introduction-to-viewof). Have a look at the JavaScript featured notebook inside Pluto!
    @@ -160,9 +160,9 @@ Pluto.jl is open source! Specifically, it is [MIT Licensed](https://github.com/f If you want to reference Pluto.jl in scientific writing, you can use our DOI: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4792401.svg)](https://doi.org/10.5281/zenodo.4792401) -### Sample notebooks +### Featured notebooks -The included sample notebooks have a more permissive license: the [Unlicense](https://github.com/fonsp/Pluto.jl/blob/main/sample/LICENSE). This means that you can use sample notebook code however you like - you do not need to credit us! +Unless otherwise specified, the included featured notebooks have a more permissive license: the [Unlicense](https://github.com/fonsp/Pluto.jl/blob/main/sample/LICENSE). This means that you can use them however you like - you do not need to credit us! Your notebook files are _yours_, you also do not need to credit us. Have fun! diff --git a/frontend-bundler/package.json b/frontend-bundler/package.json index 0ee5bd1416..c05068d4c9 100644 --- a/frontend-bundler/package.json +++ b/frontend-bundler/package.json @@ -6,8 +6,8 @@ "version": "1.0.0", "description": "", "scripts": { - "start": "cd ../frontend && parcel --dist-dir ../frontend-dist --config ../frontend-bundler/.parcelrc editor.html index.html error.jl.html sample.html", - "build": "cd ../frontend && parcel build --no-source-maps --public-url . --dist-dir ../frontend-dist --config ../frontend-bundler/.parcelrc editor.html index.html error.jl.html sample.html && node ../frontend-bundler/add_sri.js ../frontend-dist/editor.html", + "start": "cd ../frontend && parcel --dist-dir ../frontend-dist --config ../frontend-bundler/.parcelrc editor.html index.html error.jl.html", + "build": "cd ../frontend && parcel build --no-source-maps --public-url . --dist-dir ../frontend-dist --config ../frontend-bundler/.parcelrc editor.html index.html error.jl.html && node ../frontend-bundler/add_sri.js ../frontend-dist/editor.html", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/frontend/common/Binder.js b/frontend/common/Binder.js index 8ac593a9f9..2ecbcd3e99 100644 --- a/frontend/common/Binder.js +++ b/frontend/common/Binder.js @@ -124,10 +124,17 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) let open_response = new Response() if (launch_params.notebookfile.startsWith("data:")) { - open_response = await fetch(with_token(new URL("notebookupload", binder_session_url)), { - method: "POST", - body: await (await fetch(new Request(launch_params.notebookfile, { integrity: launch_params.notebookfile_integrity }))).arrayBuffer(), - }) + open_response = await fetch( + with_token( + with_query_params(new URL("notebookupload", binder_session_url), { + name: new URLSearchParams(window.location.search).get("name"), + }) + ), + { + method: "POST", + body: await (await fetch(new Request(launch_params.notebookfile, { integrity: launch_params.notebookfile_integrity }))).arrayBuffer(), + } + ) } else { for (const [p1, p2] of [ ["path", launch_params.notebookfile], @@ -145,6 +152,12 @@ export const start_binder = async ({ setStatePromise, connect, launch_params }) } } + if (!open_response.ok) { + let b = await open_response.blob() + window.location.href = URL.createObjectURL(b) + return + } + // Opening a notebook gives us the notebook ID, which means that we have a running session! Time to connect. const new_notebook_id = await open_response.text() diff --git a/frontend/common/Bond.js b/frontend/common/Bond.js index a3a89af925..6efc6b2b4d 100644 --- a/frontend/common/Bond.js +++ b/frontend/common/Bond.js @@ -152,7 +152,7 @@ export const set_bound_elements_to_their_value = (bond_nodes, bond_values) => { export const add_bonds_disabled_message_handler = (bond_nodes, invalidation) => { bond_nodes.forEach((bond_node) => { const listener = (e) => { - if (e.target.closest(".bonds_disabled.offer_binder")) { + if (e.target.closest(".bonds_disabled:where(.offer_binder, .offer_local)")) { open_pluto_popup({ type: "info", source_element: e.target, @@ -163,6 +163,7 @@ export const add_bonds_disabled_message_handler = (bond_nodes, invalidation) => //@ts-ignore window.open_edit_or_run_popup() e.preventDefault() + window.dispatchEvent(new CustomEvent("close pluto popup")) }} >Run this notebook diff --git a/frontend/common/RunLocal.js b/frontend/common/RunLocal.js new file mode 100644 index 0000000000..546c0f7d86 --- /dev/null +++ b/frontend/common/RunLocal.js @@ -0,0 +1,80 @@ +import immer from "../imports/immer.js" +import { BackendLaunchPhase } from "./Binder.js" +import { timeout_promise } from "./PlutoConnection.js" +import { with_query_params } from "./URLTools.js" + +// This file is very similar to `start_binder` in Binder.js + +/** + * + * @param {{ + * launch_params: import("../components/Editor.js").LaunchParameters, + * setStatePromise: any, + * connect: () => Promise, + * }} props + */ +export const start_local = async ({ setStatePromise, connect, launch_params }) => { + try { + if (launch_params.pluto_server_url == null || launch_params.notebookfile == null) throw Error("Invalid launch parameters for starting locally.") + + await setStatePromise( + immer((state) => { + state.backend_launch_phase = BackendLaunchPhase.created + state.disable_ui = false + }) + ) + + const with_token = (x) => String(x) + const binder_session_url = new URL(launch_params.pluto_server_url, window.location.href) + + let open_response + + // We download the notebook file contents, and then upload them to the Pluto server. + const notebook_contents = await ( + await fetch(new Request(launch_params.notebookfile, { integrity: launch_params.notebookfile_integrity ?? undefined })) + ).arrayBuffer() + + open_response = await fetch( + with_token( + with_query_params(new URL("notebookupload", binder_session_url), { + name: new URLSearchParams(window.location.search).get("name"), + clear_frontmatter: "yesplease", + }) + ), + { + method: "POST", + body: notebook_contents, + } + ) + + if (!open_response.ok) { + let b = await open_response.blob() + window.location.href = URL.createObjectURL(b) + return + } + + const new_notebook_id = await open_response.text() + const edit_url = with_query_params(new URL("edit", binder_session_url), { id: new_notebook_id }) + console.info("notebook_id:", new_notebook_id) + + window.history.replaceState({}, "", edit_url) + + await setStatePromise( + immer((state) => { + state.notebook.notebook_id = new_notebook_id + state.backend_launch_phase = BackendLaunchPhase.notebook_running + }) + ) + console.log("Connecting WebSocket") + + const connect_promise = connect() + await timeout_promise(connect_promise, 20_000).catch((e) => { + console.error("Failed to establish connection within 20 seconds. Navigating to the edit URL directly.", e) + + window.parent.location.href = with_token(edit_url) + }) + } catch (err) { + console.error("Failed to initialize binder!", err) + alert("Something went wrong! 😮\n\nWe failed to open this notebook. Please try again with a different browser, or come back later.") + } +} diff --git a/frontend/components/BinderButton.js b/frontend/components/EditOrRunButton.js similarity index 93% rename from frontend/components/BinderButton.js rename to frontend/components/EditOrRunButton.js index 725ec61122..181008b02e 100644 --- a/frontend/components/BinderButton.js +++ b/frontend/components/EditOrRunButton.js @@ -1,6 +1,25 @@ import { BackendLaunchPhase } from "../common/Binder.js" import { html, useEffect, useState, useRef } from "../imports/Preact.js" +export const RunLocalButton = ({ show, start_local }) => { + //@ts-ignore + window.open_edit_or_run_popup = () => { + start_local() + } + + return html`
    + +
    ` +} + export const BinderButton = ({ offer_binder, start_binder, notebookfile }) => { const [popupOpen, setPopupOpen] = useState(false) const [showCopyPopup, setShowCopyPopup] = useState(false) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index c265fed812..77759440bb 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -25,19 +25,20 @@ import { has_ctrl_or_cmd_pressed, ctrl_or_cmd_name, is_mac_keyboard, in_textarea import { PlutoActionsContext, PlutoBondsContext, PlutoJSInitializingContext, SetWithEmptyCallback } from "../common/PlutoContext.js" import { start_binder, BackendLaunchPhase, count_stat } from "../common/Binder.js" import { setup_mathjax } from "../common/SetupMathJax.js" -import { BinderButton } from "./BinderButton.js" +import { BinderButton, RunLocalButton } from "./EditOrRunButton.js" import { slider_server_actions, nothing_actions } from "../common/SliderServerClient.js" import { ProgressBar } from "./ProgressBar.js" import { IsolatedCell } from "./Cell.js" import { RawHTMLContainer } from "./CellOutput.js" import { RecordingPlaybackUI, RecordingUI } from "./RecordingUI.js" import { HijackExternalLinksToOpenInNewTab } from "./HackySideStuff/HijackExternalLinksToOpenInNewTab.js" +import { start_local } from "../common/RunLocal.js" import { FrontMatterInput } from "./FrontmatterInput.js" // This is imported asynchronously - uncomment for development // import environment from "../common/Environment.js" -export const default_path = "..." +export const default_path = "" const DEBUG_DIFFING = false // Be sure to keep this in sync with DEFAULT_CELL_METADATA in Cell.jl @@ -88,6 +89,7 @@ const statusmap = (/** @type {EditorState} */ state, /** @type {LaunchParameters static_preview: state.static_preview, bonds_disabled: !(state.connected || state.initializing || launch_params.slider_server_url != null), offer_binder: state.backend_launch_phase === BackendLaunchPhase.wait_for_user && launch_params.binder_url != null, + offer_local: state.backend_launch_phase === BackendLaunchPhase.wait_for_user && launch_params.pluto_server_url != null, binder: launch_params.binder_url != null && state.backend_launch_phase != null, code_differs: state.notebook.cell_order.some( (cell_id) => state.cell_inputs_local[cell_id] != null && state.notebook.cell_inputs[cell_id].code !== state.cell_inputs_local[cell_id].code @@ -191,6 +193,7 @@ const first_true_key = (obj) => { * preamble_html: string?, * isolated_cell_ids: string[]?, * binder_url: string?, + * pluto_server_url: string?, * slider_server_url: string?, * recording_url: string?, * recording_url_integrity: string?, @@ -288,7 +291,10 @@ export class Editor extends Component { disable_ui: launch_params.disable_ui, static_preview: launch_params.statefile != null, - backend_launch_phase: launch_params.notebookfile != null && launch_params.binder_url != null ? BackendLaunchPhase.wait_for_user : null, + backend_launch_phase: + launch_params.notebookfile != null && (launch_params.binder_url != null || launch_params.pluto_server_url != null) + ? BackendLaunchPhase.wait_for_user + : null, binder_session_url: null, binder_session_token: null, connected: false, @@ -1212,7 +1218,11 @@ patch: ${JSON.stringify( initializing: false, }) // view stats on https://stats.plutojl.org/ - count_stat(`article-view`) + if (this.state.pluto_server_url != null) { + count_stat(`article-view`) + } else { + count_stat(`article-view`) + } } else { this.connect() } @@ -1315,6 +1325,9 @@ patch: ${JSON.stringify( <${PlutoActionsContext.Provider} value=${this.actions}> <${PlutoBondsContext.Provider} value=${this.state.notebook.bonds}> <${PlutoJSInitializingContext.Provider} value=${this.js_init_set}> + <${Scroller} active=${this.state.scroller} /> <${ProgressBar} notebook=${this.state.notebook} backend_launch_phase=${this.state.backend_launch_phase} status=${status}/>
    @@ -1408,7 +1421,16 @@ patch: ${JSON.stringify( /> ${ - status.offer_binder + status.offer_local + ? html`<${RunLocalButton} + start_local=${() => + start_local({ + setStatePromise: this.setStatePromise, + connect: this.connect, + launch_params: launch_params, + })} + />` + : status.offer_binder ? html`<${BinderButton} offer_binder=${status.offer_binder} start_binder=${() => diff --git a/frontend/components/Popup.js b/frontend/components/Popup.js index 7b142e08fe..e33e128ffb 100644 --- a/frontend/components/Popup.js +++ b/frontend/components/Popup.js @@ -55,6 +55,10 @@ export const Popup = ({ notebook }) => { set_recent_event(e.detail) } + const close = () => { + set_recent_event(null) + } + useEffect(() => { const onpointerdown = (e) => { if (e.target == null) return @@ -62,19 +66,21 @@ export const Popup = ({ notebook }) => { if (recent_source_element_ref.current == null) return if (recent_source_element_ref.current.contains(e.target)) return - set_recent_event(null) + close() } const onkeydown = (e) => { if (e.key === "Escape") { - set_recent_event(null) + close() } } window.addEventListener("open pluto popup", open) + window.addEventListener("close pluto popup", close) window.addEventListener("pointerdown", onpointerdown) document.addEventListener("keydown", onkeydown) return () => { window.removeEventListener("open pluto popup", open) + window.removeEventListener("close pluto popup", close) window.removeEventListener("pointerdown", onpointerdown) document.removeEventListener("keydown", onkeydown) } diff --git a/frontend/components/welcome/Featured.js b/frontend/components/welcome/Featured.js new file mode 100644 index 0000000000..5457e6904a --- /dev/null +++ b/frontend/components/welcome/Featured.js @@ -0,0 +1,167 @@ +import featured_sources from "../../featured_sources.js" +import _ from "../../imports/lodash.js" +import { html, useEffect, useState } from "../../imports/Preact.js" +import { FeaturedCard } from "./FeaturedCard.js" + +const run = (f) => f() + +/** + * @typedef SourceManifestNotebookEntry + * @type {{ + * id: String, + * hash: String, + * html_path: String, + * statefile_path: String, + * notebookfile_path: String, + * frontmatter: Record, + * }} + */ + +/** + * @typedef SourceManifestCollectionEntry + * @type {{ + * title: String?, + * description: String?, + * tags: Array?, + * }} + */ + +/** + * @typedef SourceManifest + * @type {{ + * notebooks: Record, + * collections: Array?, + * pluto_version: String, + * julia_version: String, + * format_version: String, + * source_url: String, + * title: String?, + * description: String?, + * }} + */ + +const placeholder_data = [ + { + title: "Featured Notebooks", + description: "These notebooks from the Julia community show off what you can do with Pluto. Give it a try, you might learn something new!", + collections: [ + { + title: "Loading...", + tags: [], + }, + ], + notebooks: [], + }, +] + +const offline_html = html` + +` + +export const Featured = () => { + // Option 1: Dynamically load source list from a json: + // const [sources, set_sources] = useState(/** @type{Array<{url: String, integrity: String?}>?} */ (null)) + // useEffect(() => { + // run(async () => { + // const data = await (await fetch("featured_sources.json")).json() + + // set_sources(data.sources) + // }) + // }, []) + + // Option 2: From a JS file. This means that the source list can be bundled together. + const sources = featured_sources.sources + + const [source_data, set_source_data] = useState(/** @type{Array} */ ([])) + + useEffect(() => { + if (sources != null) { + const promises = sources.map(async ({ url, integrity }) => { + const data = await (await fetch(new Request(url, { integrity: integrity ?? undefined }))).json() + + if (data.format_version !== "1") { + throw new Error(`Invalid format version: ${data.format_version}`) + } + + set_source_data((old) => [ + ...old, + { + ...data, + source_url: url, + }, + ]) + }) + + Promise.any(promises).catch((e) => { + console.error("All featured sources failed to load: ", e) + set_waited_too_long(true) + }) + } + }, [sources]) + + useEffect(() => { + if (source_data?.length > 0) { + console.log("Sources:", source_data) + } + }, [source_data]) + + const [waited_too_long, set_waited_too_long] = useState(false) + useEffect(() => { + setTimeout(() => { + set_waited_too_long(true) + }, 8 * 1000) + }, []) + + const no_data = !(source_data?.length > 0) + + return no_data && waited_too_long + ? offline_html + : html` + ${(no_data ? placeholder_data : source_data).map( + (data) => html` + + ` + )} + ` +} + +const collection = (/** @type {SourceManifestNotebookEntry[]} */ notebooks, /** @type {String[]} */ tags) => { + const nbs = notebooks.filter((notebook) => tags.some((t) => (notebook.frontmatter?.tags ?? []).includes(t))) + + return /** @type {SourceManifestNotebookEntry[]} */ (_.sortBy(nbs, [(nb) => Number(nb?.frontmatter?.order), "id"])) +} diff --git a/frontend/components/welcome/FeaturedCard.js b/frontend/components/welcome/FeaturedCard.js new file mode 100644 index 0000000000..b12f0529dc --- /dev/null +++ b/frontend/components/welcome/FeaturedCard.js @@ -0,0 +1,93 @@ +import { base64url_to_base64 } from "../../common/PlutoHash.js" +import { with_query_params } from "../../common/URLTools.js" +import _ from "../../imports/lodash.js" +import { html, useEffect, useState, useMemo } from "../../imports/Preact.js" + +const transparent_svg = "data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E" + +const str_to_degree = (s) => ([...s].reduce((a, b) => a + b.charCodeAt(0), 0) * 79) % 360 + +/** + * @param {{ + * entry: import("./Featured.js").SourceManifestNotebookEntry, + * source_url: String, + * }} props + */ +export const FeaturedCard = ({ entry, source_url }) => { + const title = entry.frontmatter?.title + const u = (x) => (x == null ? null : new URL(x, source_url).href) + + const href = with_query_params(`editor.html`, { + statefile: u(entry.statefile_path), + notebookfile: u(entry.notebookfile_path), + notebookfile_integrity: `sha256-${base64url_to_base64(entry.hash)}`, + disable_ui: `true`, + pluto_server_url: `.`, + name: title == null ? null : `sample ${title}`, + }) + + const author = author_info(entry.frontmatter) + + return html` + + + ${author?.name == null + ? null + : html` + + `} +

    ${entry.frontmatter.title}

    +

    ${entry.frontmatter.description}

    +
    + ` +} + +/** + * @typedef AuthorInfo + * @type {{ + * name: string?, + * url: string?, + * image: string?, + * }} + */ + +const author_info = (frontmatter) => + author_info_item(frontmatter.author) ?? + author_info_item({ + name: frontmatter.author_name, + url: frontmatter.author_url, + image: frontmatter.author_image, + }) + +/** + * @returns {AuthorInfo?} + */ +const author_info_item = (x) => { + if (x instanceof Array) { + return author_info_item(x[0]) + } else if (x == null) { + return null + } else if (typeof x === "string") { + return { + name: x, + url: null, + image: null, + } + } else if (x instanceof Object) { + let { name, image, url } = x + + if (image == null && url != null) { + image = url + ".png?size=48" + } + + return { + name, + url, + image, + } + } else { + return null + } +} diff --git a/frontend/components/welcome/Open.js b/frontend/components/welcome/Open.js index bb9abdd9e8..3c513fd90a 100644 --- a/frontend/components/welcome/Open.js +++ b/frontend/components/welcome/Open.js @@ -1,5 +1,5 @@ import _ from "../../imports/lodash.js" -import { html, Component, useEffect, useState, useMemo } from "../../imports/Preact.js" +import { html } from "../../imports/Preact.js" import { FilePicker } from "../FilePicker.js" import { PasteHandler } from "../PasteHandler.js" @@ -28,27 +28,22 @@ export const Open = ({ client, connected, CustomPicker, show_samples }) => { } const picker = CustomPicker ?? { - text: "Open from file", + text: "Open a notebook", placeholder: "Enter path or URL...", } - return html`

    New session:

    - <${PasteHandler} /> -
      - ${show_samples && html`
    • Open a sample notebook
    • `} -
    • Create a new notebook
    • -
    • - ${picker.text}: - <${FilePicker} - key=${picker.placeholder} - client=${client} - value="" - on_submit=${on_open_path} - button_label="Open" - placeholder=${picker.placeholder} - /> -
    • -
    ` + return html`<${PasteHandler} /> +

    ${picker.text}

    +
    + <${FilePicker} + key=${picker.placeholder} + client=${client} + value="" + on_submit=${on_open_path} + button_label="Open" + placeholder=${picker.placeholder} + /> +
    ` } // /open will execute a script from your hard drive, so we include a token in the URL to prevent a mean person from getting a bad file on your computer _using another hypothetical intrusion_, and executing it using Pluto diff --git a/frontend/components/welcome/Recent.js b/frontend/components/welcome/Recent.js index 7bcfdfca28..815987456e 100644 --- a/frontend/components/welcome/Recent.js +++ b/frontend/components/welcome/Recent.js @@ -181,7 +181,7 @@ export const Recent = ({ client, connected, remote_notebooks, CustomRecent }) => })} > if (CustomRecent == null) { return html` -

    Recent sessions:

    -
      +

      My work

      +
      ` diff --git a/frontend/components/welcome/Welcome.js b/frontend/components/welcome/Welcome.js index 4c20bd170b..8733e92b46 100644 --- a/frontend/components/welcome/Welcome.js +++ b/frontend/components/welcome/Welcome.js @@ -6,6 +6,7 @@ import { create_pluto_connection } from "../../common/PlutoConnection.js" import { new_update_message } from "../../common/NewUpdateMessage.js" import { Open } from "./Open.js" import { Recent } from "./Recent.js" +import { Featured } from "./Featured.js" // This is imported asynchronously - uncomment for development // import environment from "../../common/Environment.js" @@ -72,8 +73,26 @@ export const Welcome = () => { const { show_samples, CustomRecent, CustomPicker } = extended_components return html` - <${Open} client=${client_ref.current} connected=${connected} CustomPicker=${CustomPicker} show_samples=${show_samples} /> -
      - <${Recent} client=${client_ref.current} connected=${connected} remote_notebooks=${remote_notebooks} CustomRecent=${CustomRecent} /> +
      +

      welcome to

      + +
      +
      +
      + <${Recent} client=${client_ref.current} connected=${connected} remote_notebooks=${remote_notebooks} CustomRecent=${CustomRecent} /> +
      +
      +
      +
      + <${Open} client=${client_ref.current} connected=${connected} CustomPicker=${CustomPicker} show_samples=${show_samples} /> +
      +
      + ` } diff --git a/frontend/dark_color.css b/frontend/dark_color.css index 85d722c801..6447a4923f 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -139,7 +139,7 @@ --footer-atag-color: rgb(114, 161, 223); --footer-input-border-color: #6c6c6c; --footer-filepicker-button-color: black; - --footer-filepicker-focus-color: #9d9d9d; + --footer-filepicker-focus-color: #c1c1c1; --footnote-border-color: rgba(114, 225, 231, 0.15); /* undo delete cell*/ @@ -185,6 +185,13 @@ /* Landing colors */ --index-text-color: rgb(199, 199, 199); --index-clickable-text-color: rgb(235, 235, 235); + --index-card-bg: #313131; + --welcome-mywork-bg: var(--header-bg-color); + --welcome-newnotebook-bg: rgb(68 72 102); + --welcome-recentnotebook-bg: #3b3b3b; + --welcome-recentnotebook-border: #6e6e6e; + --welcome-open-bg: hsl(233deg 20% 33%); + --welcome-card-author-backdrop: #0000006b; /* docs binding */ --docs-binding-bg: #323431; diff --git a/frontend/editor.css b/frontend/editor.css index 93978ededc..3ea685ec19 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -1069,7 +1069,7 @@ pluto-output:not(.rich_output) > div > pre { display: flex; } -.bonds_disabled.offer_binder bond { +.bonds_disabled:where(.offer_binder, .offer_local) bond { opacity: 0.6; filter: grayscale(1); } @@ -1209,6 +1209,7 @@ pluto-cell.code_differs > pluto-input > .cm-editor { /* UI */ +button.floating_back_button, .overlay-button button, pluto-cell > button, pluto-input > button, @@ -2226,7 +2227,7 @@ footer a { footer input { margin: 0px; border: 2px solid var(--footer-input-border-color); - background: transparent; + background: var(--white); font-family: inherit; font-size: inherit; border-radius: 3px 0 0 3px; @@ -2606,6 +2607,7 @@ nav#slide_controls > button { padding: 5px; } +button.floating_back_button > span::after, nav#slide_controls > button > span::after { content: " " !important; display: block; @@ -2613,6 +2615,7 @@ nav#slide_controls > button > span::after { width: 30px; background-size: 30px 30px; } +button.floating_back_button > span::after, nav#slide_controls > button.prev > span::after { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/arrow-back-outline.svg"); filter: var(--image-filters); @@ -2622,6 +2625,18 @@ nav#slide_controls > button.next > span::after { filter: var(--image-filters); } +button.floating_back_button { + display: none; +} + +.static_preview.offer_local button.floating_back_button { + display: flex; + position: fixed; + z-index: 1000; + left: 1em; + top: 1em; +} + /* CODEMIRROR HINTS */ .cm-editor .cm-tooltip { diff --git a/frontend/editor.js b/frontend/editor.js index 72ab6c4155..a923ca7825 100644 --- a/frontend/editor.js +++ b/frontend/editor.js @@ -48,6 +48,8 @@ const launch_params = { //@ts-ignore binder_url: url_params.get("binder_url") ?? window.pluto_binder_url, //@ts-ignore + pluto_server_url: url_params.get("pluto_server_url") ?? window.pluto_pluto_server_url, + //@ts-ignore slider_server_url: url_params.get("slider_server_url") ?? window.pluto_slider_server_url, //@ts-ignore recording_url: url_params.get("recording_url") ?? window.pluto_recording_url, @@ -95,6 +97,13 @@ export const empty_notebook_state = ({ notebook_id }) => ({ nbpkg: null, }) +/** + * + * @param {import("./components/Editor.js").NotebookData} state + * @returns {import("./components/Editor.js").NotebookData} + */ +const without_path_entries = (state) => ({ ...state, path: default_path, shortpath: "" }) + /** * * @param {{ @@ -114,7 +123,7 @@ const EditorLoader = ({ launch_params }) => { ;(async () => { const r = await fetch(new Request(launch_params.statefile, { integrity: launch_params.statefile_integrity })) const data = await read_Uint8Array_with_progress(r, set_statefile_download_progress) - const state = unpack(data) + const state = without_path_entries(unpack(data)) initial_notebook_state_ref.current = state set_ready_for_editor(true) })() diff --git a/frontend/featured_sources.js b/frontend/featured_sources.js new file mode 100644 index 0000000000..250ea2b81c --- /dev/null +++ b/frontend/featured_sources.js @@ -0,0 +1,8 @@ +export default { + sources: [ + { + url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@a0158dceac9ec602aaf4d8e0d53ddd10b8b56854/pluto_export.json", + integrity: "sha256-6LMXdEg2AvJcgkO1ne6tf75UFC8Vob6YqRAx6ytrfk8=", + }, + ], +} diff --git a/frontend/img/favicon_unsaturated_bg.svg b/frontend/img/favicon_unsaturated_bg.svg new file mode 100755 index 0000000000..1adc29e099 --- /dev/null +++ b/frontend/img/favicon_unsaturated_bg.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/frontend/index.css b/frontend/index.css index 007a7028a9..65bceeef8a 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -7,24 +7,16 @@ @import url("light_color.css"); @import url("dark_color.css"); +@import url("https://cdn.jsdelivr.net/npm/open-props@1.3.16/open-props.min.css"); + * { box-sizing: border-box; } - -#title { - /* position: absolute; */ - /* left: 0; */ - /* right: 0; */ - margin-top: 34vh; - /* bottom: 0; */ - /* margin: auto; */ - display: block; - width: 100%; - text-align: center; +html { + font-size: 17px; } -#title h1, -#title h2 { +#title h1 { font-style: italic; font-size: 2em; letter-spacing: 0.08em; @@ -32,15 +24,13 @@ font-family: "Vollkorn", serif; color: var(--pluto-output-h-color); margin: 0px; - border-bottom: solid 2px var(--rule-color); -} - -#title h2 { - font-size: 1.4em; -} + padding: 4rem 1rem 3rem 1rem; + /* flex: 1 1 auto; */ /* max-width: 920px; */ +text-align: center;} -#title img { +#title h1 img { height: 1.2em; + width: 4.9em; margin-bottom: -0.27em; /* margin-right: -1.5em; */ margin-left: 0.1em; @@ -55,31 +45,24 @@ body { background: var(--main-bg-color); } -main { - width: 15em; - margin: 0 auto; - margin-top: 20vh; - text-align: left; - font-family: "Roboto Mono", monospace; - color: var(--index-text-color); -} - p { - color: var(--index-clickable-text-color); + color: var(--index-text-color); } ul { - padding-left: 0.5em; + padding-left: 0; list-style: none; } li { white-space: nowrap; - margin-bottom: 0.9em; + padding: 0.4em; + border-bottom: 1px solid var(--welcome-recentnotebook-border); } a { color: inherit; + color: var(--index-clickable-text-color); } @@ -96,7 +79,8 @@ a { pluto-filepicker { display: flex; flex-direction: row; - margin-top: 0.3rem; + /* margin-top: 0.3rem; */ + background: var(--white); } pluto-filepicker .cm-editor { @@ -105,16 +89,19 @@ pluto-filepicker .cm-editor { width: 100%; font-style: normal; font-weight: 500; - font-family: "Roboto Mono", monospace; + + font-family: var(--inter-ui-font-stack); font-size: 0.75rem; letter-spacing: 1px; background: none; color: var(--nav-filepicker-color); - border: 2px solid var(--nav-filepicker-border-color); + border: 2px solid var(--footer-filepicker-focus-color); border-radius: 3px; border-right: none; border-top-right-radius: 0; border-bottom-right-radius: 0; + flex: 1 1 100%; + width: 0px; /* min-width: 0px; */ } pluto-filepicker .cm-scroller { @@ -154,11 +141,14 @@ pluto-filepicker button:disabled { } .cm-tooltip-autocomplete { - max-height: calc(20 * 16px); box-sizing: content-box; z-index: 100; } +.cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul { + max-height: max(3em, min(50dvh, 20em)); +} + .cm-tooltip.cm-completionInfo.cm-completionInfo-right:empty { /* https://github.com/codemirror/codemirror.next/issues/574 */ display: none; @@ -166,10 +156,10 @@ pluto-filepicker button:disabled { .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { /* this is the line height rounded to an integer to prevent jiggle */ - height: 16px; + height: 18px; overflow-y: hidden; /* font-size: 16px; */ - line-height: 16px; + /* line-height: 16px; */ border-radius: 3px; margin-bottom: unset; } @@ -180,9 +170,7 @@ pluto-filepicker button:disabled { } .cm-editor .cm-completionIcon { - opacity: 1; - width: 1em; - transform: translateY(-1.5px); + display: none; } .cm-completionIcon::before { @@ -215,9 +203,10 @@ pluto-filepicker button:disabled { } .cm-editor .cm-tooltip-autocomplete .cm-completionLabel { - font-family: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace !important; + font-family: var(--inter-ui-font-stack); + font-weight: 400; font-variant-ligatures: none; - font-size: 0.75rem; + font-size: 0.8rem; } body.nosessions ul#new ~ * { @@ -225,7 +214,12 @@ body.nosessions ul#new ~ * { } #recent { - margin-bottom: 8em; + scrollbar-gutter: stable;background: var(--welcome-recentnotebook-bg); /* margin-bottom: 8em; */ + max-height: 16em; + overflow-y: auto; + overflow-x: hidden;border-radius: 0.4rem; + box-shadow: -2px 4px 9px 0px #00000012; + border: 0.2rem solid #d5d5d5; } #recent > li.recent { @@ -244,28 +238,30 @@ body.nosessions ul#new ~ * { color: var(--ui-button-color); } -#recent button > span::after { - display: block; +span.ionicon::after { + display: inline-block; content: " "; - background-size: 17px 17px; - height: 17px; - width: 17px; - margin-bottom: -3px; + background-size: 1rem 1rem; + height: 1rem; + width: 1rem; + margin-bottom: -0.17rem; + filter: var(--image-filters); } #recent li.running button > span::after { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle.svg"); - filter: var(--image-filters); } #recent li.recent button > span::after { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/caret-forward-circle-outline.svg"); - filter: var(--image-filters); } #recent li.transitioning button > span::after { background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/ellipsis-horizontal-outline.svg"); - filter: var(--image-filters); +} + +#recent li.new span::after { + background-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/add-outline.svg"); } loading-bar { @@ -294,3 +290,134 @@ body.loading loading-bar { right: 0vw; } } + +:root { + --card-width: 15rem; +} + +featured-card { + --card-color: hsl(var(--card-color-hue), 77%, 82%); + --card-border-radius: 10px; + --card-border-width: 3px; + + display: block; + /* width: var(--card-width); */ + border: var(--card-border-width) solid var(--card-color); + border-radius: var(--card-border-radius); + margin: 10px; + padding-bottom: 0.3rem; + box-shadow: 0px 2px 6px 0px #00000014; + font-family: var(--inter-ui-font-stack); + position: relative; + word-break: break-word; + hyphens: auto; + background: var(--index-card-bg); + max-width: var(--card-width); +} + +.card-list { + display: grid; + /* grid-auto-columns: 50px; */ + place-items: center; + align-items: stretch; + grid-template-columns: repeat(auto-fit, minmax(var(--card-width), 1fr)); + gap: 0rem; + justify-items: stretch; +} + +featured-card .banner img { + --zz: calc(var(--card-border-radius) - var(--card-border-width)); + width: 100%; + /* height: 8rem; */ + aspect-ratio: 3/2; + object-fit: cover; + /* background-color: hsl(16deg 100% 66%); */ + background: var(--card-color); + border-radius: var(--zz) var(--zz) 0 0; + flex: 1 1 200px; + min-width: 0; +} + +featured-card .author img { + --size: 1.6em; + /* margin: 0.4em 0.4em; */ + /* margin-bottom: -0.4em; */ + width: var(--size); + height: var(--size); + object-fit: cover; + border-radius: 100%; + background: #b6b6b6; + display: inline-block; + overflow: hidden; +} + +featured-card a { + text-decoration: none; + /* font-weight: 800; */ +} + +featured-card a.banner { + display: flex; +} + +featured-card .author { + font-weight: 600; +} + +featured-card h3 a { + padding: 0.6em; + padding-bottom: 0; + -webkit-line-clamp: 2; + display: inline-block; + display: -webkit-inline-box; + -webkit-box-orient: vertical; + overflow: hidden; + background: var(--index-card-bg); + border-radius: 0.6em; + /* border-top-left-radius: 0; */ +} + +featured-card p { + margin: 0.3rem 0.8rem; + /* padding-top: 0; */ + /* margin-block: 0; */ + color: #838383; + -webkit-line-clamp: 4; + display: inline-block; + display: -webkit-inline-box; + -webkit-box-orient: vertical; + overflow: hidden; +} + +featured-card h3 { + margin: -1.1rem 0rem 0rem 0rem; +} + +featured-card.big { + grid-column-end: span 2; + grid-row-end: span 2; + /* width: 2000px; */ +} + +featured-card.big .banner img { + height: 16rem; +} + +featured-card.special::before { + content: "New!"; + font-size: 1.4rem; + font-weight: 700; + text-transform: uppercase; + font-style: italic; + display: block; + background: #fcf492; + color: #833bc6; + text-shadow: 0 0 1px #ff6767; + position: absolute; + transform: translateY(calc(-100% - -15px)) rotate(-5deg); + padding: 2px 19px; + left: -9px; + /* right: 51px; */ + /* border: 2px solid #ffca62; */ + pointer-events: none; +} diff --git a/frontend/index.html b/frontend/index.html index a032146059..e7fda8c032 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - ⚡ Pluto.jl ⚡ + Pluto.jl + @@ -29,21 +30,43 @@ - + + + -
      -

      welcome to

      -
      -

      New session:

      - -
      + +
      +

      welcome to

      +
      +
      +

      My work

      + +
      +
      +
      +
      +

      Open a notebook

      +

      Loading...

      +
      +
      +
      diff --git a/frontend/index.js b/frontend/index.js index 15efdd148a..4ba142026d 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -3,4 +3,4 @@ import "./common/NodejsCompatibilityPolyfill.js" import { Welcome } from "./components/welcome/Welcome.js" -render(html`<${Welcome} />`, document.querySelector("main")) +render(html`<${Welcome} />`, document.querySelector("#app")) diff --git a/frontend/light_color.css b/frontend/light_color.css index de08581241..bcfa8a11ea 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -189,6 +189,13 @@ --index-text-color: hsl(0, 0, 60); --index-clickable-text-color: hsl(0, 0, 30); --docs-binding-bg: #8383830a; + --index-card-bg: white; + --welcome-mywork-bg: hsl(35deg 66% 93%); + --welcome-newnotebook-bg: whitesmoke; + --welcome-recentnotebook-bg: white; + --welcome-recentnotebook-border: #dfdfdf; + --welcome-open-bg: #fbfbfb; + --welcome-card-author-backdrop: #ffffffb0; /* HTML in codemirror! */ --cm-html-color: #48b685; diff --git a/frontend/sample.html b/frontend/sample.html deleted file mode 100644 index e7fb445210..0000000000 --- a/frontend/sample.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - ⚡ Pluto.jl ⚡ - - - - - - - - - - - - - - - - -
      -

      welcome to

      -
      -

      Samples:

      - -
      -
      -
      - - - \ No newline at end of file diff --git a/frontend/welcome.css b/frontend/welcome.css new file mode 100644 index 0000000000..94eaaa6fc7 --- /dev/null +++ b/frontend/welcome.css @@ -0,0 +1,262 @@ +@import url("https://cdn.jsdelivr.net/npm/inter-ui@3.19.3/inter-latin.css"); + +:root { + --pluto-cell-spacing: 17px; + /* use the value "contextual" to enable contextual ligatures `document.documentElement.style.setProperty('--pluto-operator-ligatures', 'contextual');` + for julia mono see here: https://cormullion.github.io/pages/2020-07-26-JuliaMono/#contextual_and_stylistic_alternates_and_ligatures */ + --pluto-operator-ligatures: none; + --julia-mono-font-stack: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace; + --sans-serif-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --lato-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif; + --inter-ui-font-stack: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif; + + color-scheme: light dark; +} + +html { + font-family: var(--inter-ui-font-stack); + font-size: 17px; +} + +main { + display: block; + max-width: 1200px; + padding: 1rem; + margin: 0 auto; +} +/* font-size: 1.7em; */ + +header { + background-color: #f5efd2; /* background-image: linear-gradient(to top, white, #fff0), var(--noise-4); */ + /* filter: var(--noise-filter-1); */ + display: flex; + background-size: cover; + justify-content: center; + padding: 1.3rem; +} +header h1 { + font-weight: 500; + + /* font-style: italic; */ + text-align: center; + /* color: white; */ +} + +section#mywork, +section#open { + /* --c1: rgb(255 255 255 / 20%); */ + /* --c2: rgb(255 255 255 / 7%); */ + /* background-color: #92add9; */ + /* --grad-stops: transparent 9%, var(--c1) 10%, var(--c1) 12%, transparent 13%, transparent 29%, var(--c2) 30%, var(--c2) 31%, transparent 32%, transparent 49%, + var(--c2) 50%, var(--c2) 51%, transparent 52%, transparent 69%, var(--c2) 70%, var(--c2) 71%, transparent 72%, transparent 89%, var(--c2) 90%, + var(--c2) 91%, transparent 92%, transparent; */ + /* background-size: 40px 40px; */ + /* background-position: 20px 20px; */ + /* background-image: linear-gradient(360deg, var(--grad-stops)), linear-gradient(90deg, var(--grad-stops)); */ + /* background: #f5f5f6; */ + /* position: relative; */ + /* background: url("https://computationalthinking.mit.edu/Spring21/homepage/bg.svg"); */ /* background-size: cover; */ /* background-position: 0% 70%; */ + background: var(--welcome-mywork-bg); + /* background: var(--header-bg-color); */ + position: relative; +} + +.pluto-logo { + font-style: normal; + font-weight: 800; + color: inherit; + /* padding: 0.3em; */ + display: flex; + flex-direction: row; + padding: 0.5em; + align-items: center; + gap: 0.5ch; + font-family: var(--inter-ui-font-stack); + transform: translateY(0.23em); +} + +.pluto-logo img { + height: 1.2em; + width: 1.2em; +} + +#new { + background: var(--welcome-open-bg); + box-shadow: -2px 4px 9px 0px #00000012; + padding: 1.3rem; + border-radius: 0.6rem; + margin: 1rem; + /* border: 0.3rem solid #d6e0d8; */ + display: flex; + flex-direction: column; + align-content: stretch; +} + +section { + display: flex; /* overflow: hidden; */ /* place-items: center; */ /* margin: 0rem 0rem; */ + + flex-direction: row; + justify-content: center; +} + +section > div { + margin: 1rem 1rem; + max-width: 614px; /* margin: auto; */ + flex: 1 1 auto; +} + +.pluto-logo { + background: white; + border-radius: 0.4em; + display: flex; + flex: 0 1 auto; + transform: none; + font-size: 1.6rem; +} + +section#open { + /* background: #f5f5f6; */ + /* box-shadow: inset 1px 1px 20px red; */ + position: relative; +} + +section#featured > div { + max-width: 900px; +} + +header > div { + max-width: 62rem; /* margin: 0 auto; */ + flex: 1 1 auto; + display: flex; + z-index: 1; +} + +section#mywork::before, +section#open::after { + --c: hsl(196deg 20% 26% / 6%); + content: ""; + height: 50px; + top: 0px; + left: 0; + right: 0; + position: absolute; + display: block; + background: linear-gradient(0deg, transparent, var(--c)); + pointer-events: none; + z-index: 0; +} + +:where(#mywork, #open) h2 { + /* color: black; */ + --off: 4px; + --offm: -4px; + --oc: #ffffff; + /* text-shadow: var(--off) 0 var(--oc), var(--off) var(--off) var(--oc), 0 var(--off) var(--oc), var(--offm) var(--off) var(--oc), var(--offm) 0 var(--oc), + var(--offm) var(--offm) var(--oc), 0 var(--offm) var(--oc), var(--off) var(--offm) var(--oc); */ + display: inline-block; /* background: #fffffffc; */ /* padding: 0.4em; */ + border-radius: 0.4em; + /* color: white; */ /* text-transform: uppercase; */ + margin: 2rem 0rem 0rem 0; +} + +section#open::after { + top: unset; + bottom: 0; + background: linear-gradient(0deg, var(--c), transparent); +} + +div#app { + /* background: url(https://computationalthinking.mit.edu/Spring21/homepage/bg.svg); */ + background-size: cover; + background-position: 0% 77%; +} + +section#featured { + /* background: white; */ +} + +.new a { + text-decoration: none; /* font-weight: 700; */ + font-weight: 500; + font-style: italic; +} + +li.new { + position: sticky; + background: var(--welcome-newnotebook-bg); + top: 0; + z-index: 2; +} + +h1 { + font-size: 2.8rem; + margin-block-end: 0em; +} + +.collection { + margin: 6em 0; +} + +.collection h2 { + font-size: 2.5rem; + font-weight: 600; + margin: 0; +} + +#featured p { + max-width: 54ch; + color: #757575; +} + +.author { + position: absolute; + top: 0.3em; + right: 0.3em; + background: var(--welcome-card-author-backdrop); + /* background: hsl(var(--card-color-hue) 34% 46% / 59%); */ + backdrop-filter: blur(15px); + color: black; + border-radius: 117px; + /* height: 2.5em; */ + padding: 0.3em; + padding-right: 0.8em; + display: flex; +} + +.author a { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.4ch; +} + +#github img { + aspect-ratio: 1; + filter: var(--image-filters); + width: 2rem; +} + +a#github { + display: block; + position: absolute; + top: 0.5rem; + right: 0.5rem; +} + +.show-scrollbar::-webkit-scrollbar { + width: 10px; + opacity: 0.1; +} +.show-scrollbar::-webkit-scrollbar-track { +} +.show-scrollbar::-webkit-scrollbar-thumb { + /* height: 11px; */ + background-color: var(--black); + opacity: 0.6; + border-radius: 1000px; +} +.show-scrollbar::-webkit-scrollbar-thumb:hover { + opacity: 1; +} From 4758c4e73e472808b1dad1b765fc12bfb3cd4071 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 16 Jun 2022 17:56:13 +0200 Subject: [PATCH 390/821] fix bundle --- frontend/featured_sources.js | 1 + frontend/index.css | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/featured_sources.js b/frontend/featured_sources.js index 250ea2b81c..e9573ba776 100644 --- a/frontend/featured_sources.js +++ b/frontend/featured_sources.js @@ -1,4 +1,5 @@ export default { + // check out https://github.com/JuliaPluto/pluto-developer-instructions/blob/main/How%20to%20update%20the%20featured%20notebooks.md to learn more sources: [ { url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@a0158dceac9ec602aaf4d8e0d53ddd10b8b56854/pluto_export.json", diff --git a/frontend/index.css b/frontend/index.css index 65bceeef8a..da21f1be91 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -7,8 +7,6 @@ @import url("light_color.css"); @import url("dark_color.css"); -@import url("https://cdn.jsdelivr.net/npm/open-props@1.3.16/open-props.min.css"); - * { box-sizing: border-box; } @@ -26,7 +24,8 @@ html { margin: 0px; padding: 4rem 1rem 3rem 1rem; /* flex: 1 1 auto; */ /* max-width: 920px; */ -text-align: center;} + text-align: center; +} #title h1 img { height: 1.2em; @@ -214,10 +213,12 @@ body.nosessions ul#new ~ * { } #recent { - scrollbar-gutter: stable;background: var(--welcome-recentnotebook-bg); /* margin-bottom: 8em; */ + scrollbar-gutter: stable; + background: var(--welcome-recentnotebook-bg); /* margin-bottom: 8em; */ max-height: 16em; overflow-y: auto; - overflow-x: hidden;border-radius: 0.4rem; + overflow-x: hidden; + border-radius: 0.4rem; box-shadow: -2px 4px 9px 0px #00000012; border: 0.2rem solid #d5d5d5; } From bc257883b63dc45859a74b21050e56e60b424432 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sat, 25 Jun 2022 15:00:24 +0200 Subject: [PATCH 391/821] fix cell paste at specific cell (#2189) --- frontend/common/Serialization.js | 6 ++---- frontend/components/CellInput.js | 1 - frontend/components/CellInput/pluto_paste_plugin.js | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/frontend/common/Serialization.js b/frontend/common/Serialization.js index 912ea43ddc..c45c1cc67d 100644 --- a/frontend/common/Serialization.js +++ b/frontend/common/Serialization.js @@ -1,5 +1,3 @@ -import { in_textarea_or_input } from "./KeyboardShortcuts.js" - /** * Serialize an array of cells into a string form (similar to the .jl file). * @@ -53,9 +51,9 @@ export function deserialize_repl(repl_session) { .filter((s) => s !== "") } -export const detect_deserializer = (topaste, check_in_textarea_or_input = true) => +export const detect_deserializer = (topaste) => topaste.trim().startsWith(JULIA_REPL_PROMPT) ? deserialize_repl - : check_in_textarea_or_input && !in_textarea_or_input() && topaste.match(/# ╔═╡ ........-....-....-....-............/g)?.length + : topaste.match(/# ╔═╡ ........-....-....-....-............/g)?.length ? deserialize_cells : null diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 598a5576dc..e328bb0e50 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -6,7 +6,6 @@ import { utf8index_to_ut16index } from "../common/UnicodeTools.js" import { PlutoActionsContext } from "../common/PlutoContext.js" import { get_selected_doc_from_state } from "./CellInput/LiveDocsFromCursor.js" import { go_to_definition_plugin, GlobalDefinitionsFacet } from "./CellInput/go_to_definition_plugin.js" -import { detect_deserializer } from "../common/Serialization.js" import { EditorState, diff --git a/frontend/components/CellInput/pluto_paste_plugin.js b/frontend/components/CellInput/pluto_paste_plugin.js index 6dd8a11948..c38a9de720 100644 --- a/frontend/components/CellInput/pluto_paste_plugin.js +++ b/frontend/components/CellInput/pluto_paste_plugin.js @@ -14,7 +14,7 @@ export let pluto_paste_plugin = ({ pluto_actions, cell_id }) => { event.stopPropagation() const topaste = event.clipboardData.getData("text/plain") - const deserializer = detect_deserializer(topaste, false) + const deserializer = detect_deserializer(topaste) if (deserializer == null) { return false } From dc4bd39e98bc5c6bacad243cf86261949e28d54b Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Sun, 26 Jun 2022 17:39:39 +0200 Subject: [PATCH 392/821] Track dependency of `skipped_as_script` cells (#2178) * first julia-side implementation * First JS implementation * Update dependency even without reactive_run * differentiate popup text * whitespaces * fix typescript test error * Fix/add test * fix typo * Set the tooltip only on the left border. * move flag to cell_results * differentiate directly and indirectly skipped cells visually * move jump functions from RunArea.js to Cell.js * Add jump to upstream skipped cell, change text * clean on_jump * use plain color for dependents on skip as script this is to prevent confusion with running cells which have the same gradient background. * update light theme too! * remove skip_as_script experimental flag * prevent infinite recursion and use lazy search Co-authored-by: Paul --- frontend/components/Cell.js | 92 +++++++++++++++++++++++++++++- frontend/components/CellInput.js | 26 +++------ frontend/components/Editor.js | 1 + frontend/components/Notebook.js | 3 +- frontend/components/RunArea.js | 26 +-------- frontend/dark_color.css | 3 +- frontend/editor.css | 21 +++++-- frontend/light_color.css | 3 +- src/evaluation/Run.jl | 14 +++++ src/notebook/Cell.jl | 2 + src/notebook/saving and loading.jl | 4 +- src/webserver/Dynamic.jl | 16 ++++++ test/Notebook.jl | 8 ++- 13 files changed, 162 insertions(+), 57 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index d42fb3c572..17a5aa806b 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -29,6 +29,63 @@ const useCellApi = (node_ref, published_object_keys, pluto_actions) => { return cell_api_ready } +/** + * @param {String} a_cell_id + * @param {import("./Editor.js").NotebookData} notebook + * @returns {Array} + */ +const upstream_of = (a_cell_id, notebook) => Object.values(notebook?.cell_dependencies?.[a_cell_id]?.upstream_cells_map || {}).flatMap((x) => x) + +/** + * @param {String} a_cell_id + * @param {import("./Editor.js").NotebookData} notebook + * @param {Function} predicate + * @param {Set} explored + * @returns {String | null} + */ +const find_upstream_of = (a_cell_id, notebook, predicate, explored = new Set([])) => { + if (explored.has(a_cell_id)) return null + explored.add(a_cell_id) + + if (predicate(a_cell_id)) { + return a_cell_id + } + + for (let upstream of upstream_of(a_cell_id, notebook)) { + const upstream_val = find_upstream_of(upstream, notebook, predicate, explored) + if (upstream_val !== null) { + return upstream_val + } + } + + return null +} + +/** + * @param {String} flag_name + * @returns {Function} + */ +const hasTargetBarrier = (flag_name) => { + return (a_cell_id, notebook) => { + return notebook?.cell_inputs?.[a_cell_id].metadata[flag_name] + } +} + +const on_jump = (hasBarrier, pluto_actions, cell_id) => () => { + const notebook = pluto_actions.get_notebook() || {} + const barrier_cell_id = find_upstream_of(cell_id, notebook, (c) => hasBarrier(c, notebook)) + if (barrier_cell_id !== null) { + window.dispatchEvent( + new CustomEvent("cell_focus", { + detail: { + cell_id: barrier_cell_id, + line: 0, // 1-based to 0-based index + }, + }) + ) + } +} + /** * @param {{ * cell_result: import("./Editor.js").CellResultData, @@ -44,7 +101,7 @@ const useCellApi = (node_ref, published_object_keys, pluto_actions) => { * */ export const Cell = ({ cell_input: { cell_id, code, code_folded, metadata }, - cell_result: { queued, running, runtime, errored, output, logs, published_object_keys, depends_on_disabled_cells }, + cell_result: { queued, running, runtime, errored, output, logs, published_object_keys, depends_on_disabled_cells, depends_on_skipped_cells }, cell_dependencies, cell_input_local, notebook_id, @@ -155,6 +212,16 @@ export const Cell = ({ pluto_actions.set_and_run_multiple(pluto_actions.get_selected_cells(cell_id, selected)) set_waiting_to_run_smart(true) }, [pluto_actions, cell_id, selected, set_waiting_to_run_smart]) + + const skip_as_script_jump = useCallback( + on_jump(hasTargetBarrier("skip_as_script"), pluto_actions, cell_id), + [pluto_actions, cell_id], + ) + const disabled_jump = useCallback( + on_jump(hasTargetBarrier("disabled"), pluto_actions, cell_id), + [pluto_actions, cell_id], + ) + return html` 0, hooked_up: output?.has_pluto_hook_features ?? false, @@ -232,6 +300,7 @@ export const Cell = ({ running=${running} code_differs=${class_code_differs} queued=${queued} + on_jump=${disabled_jump} />
    ` : html``} ` diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 77759440bb..3cd6ed7780 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -147,6 +147,7 @@ const first_true_key = (obj) => { * upstream_cells_map: { string: [string]}, * precedence_heuristic: ?number, * depends_on_disabled_cells: boolean, + * depends_on_skipped_cells: boolean, * output: { * body: string, * persist_js_state: boolean, diff --git a/frontend/components/Notebook.js b/frontend/components/Notebook.js index c97af65383..1448297a1d 100644 --- a/frontend/components/Notebook.js +++ b/frontend/components/Notebook.js @@ -41,7 +41,7 @@ let CellMemo = ({ global_definition_locations, }) => { const { body, last_run_timestamp, mime, persist_js_state, rootassignee } = cell_result?.output || {} - const { queued, running, runtime, errored, depends_on_disabled_cells, logs } = cell_result || {} + const { queued, running, runtime, errored, depends_on_disabled_cells, logs, depends_on_skipped_cells } = cell_result || {} const { cell_id, code, code_folded, metadata } = cell_input || {} return useMemo(() => { return html` @@ -66,6 +66,7 @@ let CellMemo = ({ ...Object.keys(metadata), ...Object.values(metadata), depends_on_disabled_cells, + depends_on_skipped_cells, queued, running, runtime, diff --git a/frontend/components/RunArea.js b/frontend/components/RunArea.js index 8552b9bc37..e17303a8c4 100644 --- a/frontend/components/RunArea.js +++ b/frontend/components/RunArea.js @@ -4,37 +4,13 @@ import { html, useContext, useEffect, useMemo, useState } from "../imports/Preac import { in_textarea_or_input } from "../common/KeyboardShortcuts.js" import { PlutoActionsContext } from "../common/PlutoContext.js" -const upstream_of = (a_cell_id, notebook) => Object.values(notebook?.cell_dependencies?.[a_cell_id]?.upstream_cells_map || {}).flatMap((x) => x) - -const all_upstreams_of = (a_cell_id, notebook) => { - const upstreams = upstream_of(a_cell_id, notebook) - if (upstreams.length === 0) return [] - return [...upstreams, ...upstreams.flatMap((v) => all_upstreams_of(v, notebook))] -} -const hasBarrier = (a_cell_id, notebook) => { - return notebook?.cell_inputs?.[a_cell_id].metadata.disabled -} - -export const RunArea = ({ runtime, running, queued, code_differs, on_run, on_interrupt, depends_on_disabled_cells, running_disabled, cell_id }) => { +export const RunArea = ({ runtime, running, queued, code_differs, on_run, on_interrupt, depends_on_disabled_cells, running_disabled, on_jump }) => { const on_save = on_run /* because disabled cells save without running */ const local_time_running_ms = useMillisSinceTruthy(running) const local_time_running_ns = local_time_running_ms == null ? null : 1e6 * local_time_running_ms const pluto_actions = useContext(PlutoActionsContext) - const on_jump = () => { - const notebook = pluto_actions.get_notebook() || {} - const barrier_cell_id = all_upstreams_of(cell_id, notebook).find((c) => hasBarrier(c, notebook)) - barrier_cell_id && - window.dispatchEvent( - new CustomEvent("cell_focus", { - detail: { - cell_id: barrier_cell_id, - line: 0, // 1-based to 0-based index - }, - }) - ) - } const action = running || queued ? "interrupt" : running_disabled ? "save" : depends_on_disabled_cells && !code_differs ? "jump" : "run" const fmap = { diff --git a/frontend/dark_color.css b/frontend/dark_color.css index 6447a4923f..c29eb738b0 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -59,7 +59,8 @@ --selected-cell-bg-color: rgb(42 115 205 / 78%); --hover-scrollbar-color-1: rgba(0, 0, 0, 0.15); --hover-scrollbar-color-2: rgba(0, 0, 0, 0.05); - --skip-as-script-border-color: #888; + --skip-as-script-background-color: #888; + --depends-on-skip-as-script-background-color: #666; /* Pluto shoulders */ --shoulder-hover-bg-color: rgba(255, 255, 255, 0.05); diff --git a/frontend/editor.css b/frontend/editor.css index 3ea685ec19..95fbf24223 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -1110,16 +1110,27 @@ pluto-cell.running_disabled > pluto-output { } /* SKIP AS SCRIPT CELLS */ -pluto-cell.skip_as_script .skip_as_script_marker { +pluto-cell.skip_as_script .skip_as_script_marker, +pluto-cell.depends_on_skipped_cells .depends_on_skipped_marker { display: block; position: absolute; top: 0; bottom: 0; - left: 0; cursor: help; - right: -4.85px; - border-radius: 5px; - border-right: 5px solid var(--skip-as-script-border-color); + z-index: 20; + right: -3px; + width: 4px; + border-radius: 0px 4px 4px 0px; + background-color: var(--skip-as-script-background-color); +} + +pluto-cell.depends_on_skipped_cells .depends_on_skipped_marker { + background-color: var(--depends-on-skip-as-script-background-color); +} + +pluto-cell.skip_as_script pluto-input .cm-editor, +pluto-cell.depends_on_skipped_cells pluto-input .cm-editor { + border-bottom-right-radius: 0px; } /* SELECTION */ diff --git a/frontend/light_color.css b/frontend/light_color.css index bcfa8a11ea..a8b832b94a 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -62,7 +62,8 @@ --selected-cell-bg-color: rgba(40, 78, 189, 0.24); --hover-scrollbar-color-1: rgba(0, 0, 0, 0.15); --hover-scrollbar-color-2: rgba(0, 0, 0, 0.05); - --skip-as-script-border-color: #ccc; + --skip-as-script-background-color: #ccc; + --depends-on-skip-as-script-background-color: #eee; /* Pluto shoulders */ --shoulder-hover-bg-color: rgba(0, 0, 0, 0.05); diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 556665fcba..6349ebf813 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -119,6 +119,9 @@ function run_reactive_core!( cell.depends_on_disabled_cells = true end + # find (indirectly) skipped cells and update their status + update_skipped_cells_dependency!(notebook, new_topology) + to_run = setdiff(to_run_raw, indirectly_deactivated) # change the bar on the sides of cells to "queued" @@ -698,3 +701,14 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...) return true end + +function update_skipped_cells_dependency!(notebook::Notebook, topology::NotebookTopology=notebook.topology) + skipped_cells = filter(is_skipped_as_script, notebook.cells) + indirectly_skipped = collect(topological_order(topology, skipped_cells)) + for cell in indirectly_skipped + cell.depends_on_skipped_cells = true + end + for cell in setdiff(notebook.cells, indirectly_skipped) + cell.depends_on_skipped_cells = false + end +end \ No newline at end of file diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index d3bfbed267..e90b4194d3 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -56,6 +56,7 @@ Base.@kwdef mutable struct Cell cell_dependencies::CellDependencies{Cell}=CellDependencies{Cell}(Dict{Symbol,Vector{Cell}}(), Dict{Symbol,Vector{Cell}}(), 99) depends_on_disabled_cells::Bool=false + depends_on_skipped_cells::Bool=false metadata::Dict{String,Any}=copy(DEFAULT_CELL_METADATA) end @@ -82,3 +83,4 @@ end is_disabled(c::Cell) = get(c.metadata, METADATA_DISABLED_KEY, DEFAULT_CELL_METADATA[METADATA_DISABLED_KEY]) can_show_logs(c::Cell) = get(c.metadata, METADATA_SHOW_LOGS_KEY, DEFAULT_CELL_METADATA[METADATA_SHOW_LOGS_KEY]) is_skipped_as_script(c::Cell) = get(c.metadata, METADATA_SKIP_AS_SCRIPT_KEY, DEFAULT_CELL_METADATA[METADATA_SKIP_AS_SCRIPT_KEY]) +must_be_commented_in_file(c::Cell) = is_disabled(c) || is_skipped_as_script(c) || c.depends_on_disabled_cells || c.depends_on_skipped_cells \ No newline at end of file diff --git a/src/notebook/saving and loading.jl b/src/notebook/saving and loading.jl index 5fd47c0da5..abc9209577 100644 --- a/src/notebook/saving and loading.jl +++ b/src/notebook/saving and loading.jl @@ -68,9 +68,7 @@ function save_notebook(io::IO, notebook::Notebook) end end - cell_running_disabled = is_disabled(c)::Bool - cell_skip_as_script = is_skipped_as_script(c)::Bool - if cell_skip_as_script || cell_running_disabled || c.depends_on_disabled_cells + if must_be_commented_in_file(c) print(io, _disabled_prefix) print(io, replace(c.code, _cell_id_delimiter => "# ")) print(io, _disabled_suffix) diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index cbf38a0f67..e8bf3dbfcd 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -138,6 +138,7 @@ function notebook_to_js(notebook::Notebook) "errored" => cell.errored, "runtime" => cell.runtime, "logs" => FirebaseyUtils.AppendonlyMarker(cell.logs), + "depends_on_skipped_cells" => cell.depends_on_skipped_cells, ) for (id, cell) in notebook.cells_dict), "cell_order" => notebook.cell_order, @@ -312,10 +313,25 @@ responses[:update_notebook] = function response_update_notebook(🙋::ClientRequ push!(changes, current_changes...) end + # We put a flag to check whether any patch changes the skip_as_script metadata. This is to eventually trigger a notebook updated if no reactive_run is part of this update + skip_as_script_changed = any(patches) do patch + path = patch.path + metadata_idx = findfirst(isequal("metadata"), path) + if metadata_idx === nothing + false + else + isequal(path[metadata_idx+1], "skip_as_script") + end + end + # If CodeChanged ∈ changes, then the client will also send a request like run_multiple_cells, which will trigger a file save _before_ running the cells. # In the future, we should get rid of that request, and save the file here. For now, we don't save the file here, to prevent unnecessary file IO. # (You can put a log in save_notebook to track how often the file is saved) if FileChanged() ∈ changes && CodeChanged() ∉ changes + if skip_as_script_changed + # If skip_as_script has changed but no cell run is happening we want to update the notebook dependency here before saving the file + update_skipped_cells_dependency!(notebook) + end save_notebook(🙋.session, notebook) end diff --git a/test/Notebook.jl b/test/Notebook.jl index e22ebe7bf1..24ee129a0c 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -1,5 +1,5 @@ using Test -import Pluto: Notebook, ServerSession, ClientSession, Cell, load_notebook, load_notebook_nobackup, save_notebook, WorkspaceManager, cutename, numbered_until_new, readwrite, without_pluto_file_extension, update_run!, get_metadata_no_default, is_disabled, create_cell_metadata +import Pluto: Notebook, ServerSession, ClientSession, Cell, load_notebook, load_notebook_nobackup, save_notebook, WorkspaceManager, cutename, numbered_until_new, readwrite, without_pluto_file_extension, update_run!, get_metadata_no_default, is_disabled, create_cell_metadata, update_skipped_cells_dependency! import Pluto.WorkspaceManager: poll, WorkspaceManager import Random import Pkg @@ -71,6 +71,9 @@ function skip_as_script_notebook() Cell( code="non_skipped_var = 15", ), + Cell( + code="dependent_var = skipped_var + 1", + ), ]) |> init_packages! end @@ -280,14 +283,17 @@ end 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) save_notebook(nb) m = ingredients(nb.path) @test m.skipped_var == 10 @test m.non_skipped_var == 15 + @test m.dependent_var == 11 WorkspaceManager.unmake_workspace((🍭, nb); verbose=false) end From 78d8e8c1c6294f89ca7fc1df8bd72b56f93f72c3 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 26 Jun 2022 17:58:30 +0200 Subject: [PATCH 393/821] add data_url --- src/webserver/data_url.jl | 110 +++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/src/webserver/data_url.jl b/src/webserver/data_url.jl index fca7186191..04be62a041 100644 --- a/src/webserver/data_url.jl +++ b/src/webserver/data_url.jl @@ -1,18 +1,44 @@ ### A Pluto.jl notebook ### -# v0.17.1 +# v0.19.9 using Markdown using InteractiveUtils +# ╔═╡ b987a8a2-6ab0-4e88-af3c-d7f2778af657 +# ╠═╡ show_logs = false +# ╠═╡ skip_as_script = true +#=╠═╡ +begin + import Pkg + + # create a local environment for this notebook + # used to install and load PlutoTest + local_env = mktempdir() + Pkg.activate(local_env) + Pkg.add(name="PlutoTest", version="0.2") + pushfirst!(LOAD_PATH, local_env) + + # activate Pluto's environment, used to load HTTP.jl + Pkg.activate(Base.current_project(@__FILE__)) + using PlutoTest +end + ╠═╡ =# + # ╔═╡ cc180e7e-46c3-11ec-3fff-05e1b5c77986 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" # Download Data URLs """ + ╠═╡ =# # ╔═╡ 2385dd3b-15f8-4790-907f-e0576a56c4c0 +# ╠═╡ skip_as_script = true +#=╠═╡ random_data = rand(UInt8, 30) + ╠═╡ =# # ╔═╡ d8ed6d44-33cd-4c9d-828b-d237d43769f5 # try @@ -21,38 +47,8 @@ random_data = rand(UInt8, 30) # e # end |> typeof -# ╔═╡ e1610184-5d16-499b-883e-7ef92f402ebb -function is_inside_pluto(m::Module) - if isdefined(m, :PlutoForceDisplay) - return m.PlutoForceDisplay - else - isdefined(m, :PlutoRunner) && parentmodule(m) == Main - end -end - -# ╔═╡ b987a8a2-6ab0-4e88-af3c-d7f2778af657 +# ╔═╡ b3f685a3-b52d-4190-9196-6977a7e76aa1 begin - if is_inside_pluto(@__MODULE__) - import Pkg - - # create a local environment for this notebook - # used to install and load PlutoTest - local_env = mktempdir() - Pkg.activate(local_env) - Pkg.add(name="PlutoTest", version="0.2") - pushfirst!(LOAD_PATH, local_env) - - # activate Pluto's environment, used to load HTTP.jl - Pkg.activate(Base.current_project(@__FILE__)) - using PlutoTest - else - if !isdefined(@__MODULE__, Symbol("@test")) - macro test(e...) nothing; end - macro test_throws(e...) nothing; end - macro test_broken(e...) nothing; end - macro testset(e...) nothing; end - end - end import HTTP.URIs import Base64 end @@ -97,49 +93,75 @@ end # ╔═╡ 6e1dd79c-a7bf-44d6-bfa6-ced75b45170a download_cool_string(args...) = read(download_cool(args...), String) -# ╔═╡ 3630b4bc-ff63-426d-b95d-ae4e4f9ccd88 -download_cool_data(args...) = read(download_cool(args...)) - # ╔═╡ 6339496d-11be-40d0-b4e5-9247e5199367 +#=╠═╡ @test download_cool_string("data:,Hello%2C%20World%21") == "Hello, World!" + ╠═╡ =# # ╔═╡ bf7b4241-9cb0-4d90-9ded-b527bf220803 +#=╠═╡ @test download_cool_string("data:text/plain,Hello%2C%20World%21") == "Hello, World!" + ╠═╡ =# # ╔═╡ d6e01532-a8e4-4173-a270-eae37c8002c7 +#=╠═╡ @test download_cool_string("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==") == "Hello, World!" + ╠═╡ =# # ╔═╡ b0ba1add-f452-4a44-ab23-becbc610e2b9 +#=╠═╡ @test download_cool_string("data:;base64,SGVsbG8sIFdvcmxkIQ==") == "Hello, World!" + ╠═╡ =# # ╔═╡ e630e261-1c2d-4117-9c44-dd49199fa3de +#=╠═╡ @test download_cool_string("data:,hello") == "hello" + ╠═╡ =# # ╔═╡ 4bb75573-09bd-4ce7-b76f-34c0249d7b88 +#=╠═╡ @test download_cool_string("data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E") == "

    Hello, World!

    " + ╠═╡ =# # ╔═╡ 301eee81-7715-4d39-89aa-37bffde3557f +#=╠═╡ @test download_cool_string("data:text/html,") == "" - -# ╔═╡ ae296e09-08dd-4ee8-87ac-eb2bf24b28b9 -random_data_url = "data:asf;base64,$( - Base64.base64encode(random_data) -)" - -# ╔═╡ 2eabfa58-2d8f-4479-9c00-a58b934638d9 -@test download_cool_data(random_data_url) == random_data + ╠═╡ =# # ╔═╡ 525b2cb6-b7b9-436e-898e-a951e6a1f2f1 +#=╠═╡ @test occursin("reactive", download_cool_string("https://raw.githubusercontent.com/fonsp/Pluto.jl/v0.17.1/README.md")) + ╠═╡ =# + +# ╔═╡ 3630b4bc-ff63-426d-b95d-ae4e4f9ccd88 +download_cool_data(args...) = read(download_cool(args...)) # ╔═╡ 40b48818-e191-4509-85ad-b9ff745cd0cb +#=╠═╡ @test_throws Exception download_cool("data:xoxo;base10,asdfasdfasdf") + ╠═╡ =# # ╔═╡ 1f175fcd-8b94-4f13-a912-02a21c95f8ca +#=╠═╡ @test_throws Exception download_cool("data:text/plain;base10,asdfasdfasdf") + ╠═╡ =# # ╔═╡ a4f671e6-0e23-4753-9301-048b2ef505e3 +#=╠═╡ @test_throws Exception download_cool("data:asdfasdfasdf") + ╠═╡ =# + +# ╔═╡ ae296e09-08dd-4ee8-87ac-eb2bf24b28b9 +#=╠═╡ +random_data_url = "data:asf;base64,$( + Base64.base64encode(random_data) +)" + ╠═╡ =# + +# ╔═╡ 2eabfa58-2d8f-4479-9c00-a58b934638d9 +#=╠═╡ +@test download_cool_data(random_data_url) == random_data + ╠═╡ =# # ╔═╡ Cell order: # ╟─cc180e7e-46c3-11ec-3fff-05e1b5c77986 @@ -161,5 +183,5 @@ random_data_url = "data:asf;base64,$( # ╠═1f175fcd-8b94-4f13-a912-02a21c95f8ca # ╠═a4f671e6-0e23-4753-9301-048b2ef505e3 # ╠═d8ed6d44-33cd-4c9d-828b-d237d43769f5 -# ╟─e1610184-5d16-499b-883e-7ef92f402ebb +# ╠═b3f685a3-b52d-4190-9196-6977a7e76aa1 # ╠═b987a8a2-6ab0-4e88-af3c-d7f2778af657 From f4365c8a151fa4a57439ba57af6c946597f3562b Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 26 Jun 2022 22:57:47 +0200 Subject: [PATCH 394/821] Skip pluto cells in src notebooks (#2191) --- src/webserver/Firebasey.jl | 654 ++++++++++++++++++++------------ src/webserver/FirebaseyUtils.jl | 369 +++++++++++++----- 2 files changed, 685 insertions(+), 338 deletions(-) diff --git a/src/webserver/Firebasey.jl b/src/webserver/Firebasey.jl index ee36b912bd..863ef08118 100644 --- a/src/webserver/Firebasey.jl +++ b/src/webserver/Firebasey.jl @@ -1,35 +1,95 @@ ### A Pluto.jl notebook ### -# v0.17.5 +# v0.19.9 using Markdown using InteractiveUtils +# ╔═╡ e748600a-2de1-11eb-24be-d5f0ecab8fa4 +# ╠═╡ show_logs = false +# ╠═╡ skip_as_script = true +#=╠═╡ +# Only define this in Pluto - assume we are `using Test` otherwise +begin + import Pkg + Pkg.activate(mktempdir()) + Pkg.add(Pkg.PackageSpec(name="PlutoTest")) + using PlutoTest +end + ╠═╡ =# + +# ╔═╡ 3e07f976-6cd0-4841-9762-d40337bb0645 +# ╠═╡ skip_as_script = true +#=╠═╡ +using Markdown: @md_str + ╠═╡ =# + # ╔═╡ d948dc6e-2de1-11eb-19e7-cb3bb66353b6 +# ╠═╡ skip_as_script = true +#=╠═╡ md"# Diffing" + ╠═╡ =# # ╔═╡ 1a6e1853-6db1-4074-bce0-5f274351cece +# ╠═╡ skip_as_script = true +#=╠═╡ md""" We define a _diffing system_ for Julia `Dict`s, which is analogous to the diffing system of immer.js. This notebook is part of Pluto's source code (included in `src/webserver/Dynamic.jl`). """ + ╠═╡ =# # ╔═╡ 49fc1f97-3b8f-4297-94e5-2e24c001d35c +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ## Example Computing a diff: """ + ╠═╡ =# + +# ╔═╡ d8e73b90-24c5-4e50-830b-b1dbe6224c8e +# ╠═╡ skip_as_script = true +#=╠═╡ +dict_1 = Dict{String,Any}( + "a" => 1, + "b" => Dict( + "c" => [3,4], + "d" => 99, + ), + "e" => "hello!" +); + ╠═╡ =# + +# ╔═╡ 19646596-b35b-44fa-bfcf-891f9ffb748c +# ╠═╡ skip_as_script = true +#=╠═╡ +dict_2 = Dict{String,Any}( + "a" => 1, + "b" => Dict( + "c" => [3,4,5], + "d" => 99, + "🏝" => "👍", + ), +); + ╠═╡ =# # ╔═╡ 9d2c07d9-16a9-4b9f-a375-2adb6e5b907a +# ╠═╡ skip_as_script = true +#=╠═╡ md""" Applying a set of patches: """ + ╠═╡ =# # ╔═╡ 336bfd4f-8a8e-4a2d-be08-ee48d6a9f747 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ## JSONPatch objects """ + ╠═╡ =# # ╔═╡ db116c0a-2de1-11eb-2a56-872af797c547 abstract type JSONPatch end @@ -73,7 +133,10 @@ const Patches = Vector{JSONPatch} const NoChanges = Patches() # ╔═╡ aad7ab32-eecf-4aad-883d-1c802cad6c0c +# ╠═╡ skip_as_script = true +#=╠═╡ md"### ==" + ╠═╡ =# # ╔═╡ 732fd744-acdb-4507-b1de-6866ec5563dd Base.hash(a::AddPatch) = hash([AddPatch, a.value, a.path]) @@ -115,8 +178,44 @@ function Base.:(==)(a::MovePatch, b::MovePatch) a.path == b.path && a.from == b.from end +# ╔═╡ 5ddfd616-db20-451b-bc1e-2ad52e0e2777 +#=╠═╡ +@test Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) == + Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) + ╠═╡ =# + +# ╔═╡ 24e93923-eab9-4a7b-9bc7-8d8a1209a78f +#=╠═╡ +@test ReplacePatch(["asd"], Dict("a" => 2)) == + ReplacePatch(["asd"], Dict("a" => 2)) + ╠═╡ =# + +# ╔═╡ 09ddf4d9-5ccb-4530-bfab-d11b864e872a +#=╠═╡ +@test Base.hash(RemovePatch(["asd"])) == Base.hash(RemovePatch(["asd"])) + ╠═╡ =# + +# ╔═╡ d9e764db-94fc-44f7-8c2e-3d63f4809617 +#=╠═╡ +@test RemovePatch(["asd"]) == RemovePatch(["asd"]) + ╠═╡ =# + +# ╔═╡ 99df99ad-aad5-4275-97d4-d1ceeb2f8d15 +#=╠═╡ +@test Base.hash(RemovePatch(["aasd"])) != Base.hash(RemovePatch(["asd"])) + ╠═╡ =# + +# ╔═╡ 2d665639-7274-495a-ae9d-f358a8219bb7 +#=╠═╡ +@test Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) != + Base.hash(AddPatch(["asd"], Dict("a" => 2))) + ╠═╡ =# + # ╔═╡ f658a72d-871d-49b3-9b73-7efedafbd7a6 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### convert(::Type{Dict}, ::JSONPatch)" + ╠═╡ =# # ╔═╡ 230bafe2-aaa7-48f0-9fd1-b53956281684 function Base.convert(::Type{Dict}, patch::AddPatch) @@ -148,25 +247,46 @@ function Base.convert(::Type{JSONPatch}, patch_dict::Dict) end # ╔═╡ 07eeb122-6706-4544-a007-1c8d6581eec8 +# ╠═╡ skip_as_script = true +#=╠═╡ Base.convert(Dict, AddPatch([:x, :y], 10)) + ╠═╡ =# # ╔═╡ c59b30b9-f702-41f1-bb2e-1736c8cd5ede +# ╠═╡ skip_as_script = true +#=╠═╡ Base.convert(Dict, RemovePatch([:x, :y])) + ╠═╡ =# # ╔═╡ 7feeee3a-3aec-47ce-b8d7-74a0d9b0b381 +# ╠═╡ skip_as_script = true +#=╠═╡ Base.convert(Dict, ReplacePatch([:x, :y], 10)) + ╠═╡ =# # ╔═╡ 6d67f8a5-0e0c-4b6e-a267-96b34d580946 +# ╠═╡ skip_as_script = true +#=╠═╡ add_patch = AddPatch(["counter"], 10) + ╠═╡ =# # ╔═╡ 56b28842-4a67-44d7-95e7-55d457a44fb1 +# ╠═╡ skip_as_script = true +#=╠═╡ remove_patch = RemovePatch(["counter"]) + ╠═╡ =# # ╔═╡ f10e31c0-1d2c-4727-aba5-dd676a10041b +# ╠═╡ skip_as_script = true +#=╠═╡ replace_patch = ReplacePatch(["counter"], 10) + ╠═╡ =# # ╔═╡ 3a99e22d-42d6-4b2d-9381-022b41b0e852 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### wrappath" + ╠═╡ =# # ╔═╡ 831d84a6-1c71-4e68-8c7c-27d9093a82c4 function wrappath(path::PatchPath, patches::Vector{JSONPatch}) @@ -201,7 +321,10 @@ function wrappath(path, patch::MovePatch) end # ╔═╡ daf9ec12-2de1-11eb-3a8d-59d9c2753134 +# ╠═╡ skip_as_script = true +#=╠═╡ md"## Diff" + ╠═╡ =# # ╔═╡ 0b50f6b2-8e85-4565-9f04-f99c913b4592 const use_triple_equals_for_arrays = Ref(false) @@ -297,40 +420,213 @@ function diff(o1::Nothing, o2::Nothing) NoChanges end +# ╔═╡ 7ca087b8-73ac-49ea-9c5a-2971f0da491f +#=╠═╡ +example_patches = diff(dict_1, dict_2) + ╠═╡ =# + # ╔═╡ 59b46bfe-da74-43af-9c11-cb0bdb2c13a2 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ### Dict example """ + ╠═╡ =# + +# ╔═╡ 200516da-8cfb-42fe-a6b9-cb4730168923 +# ╠═╡ skip_as_script = true +#=╠═╡ +celldict1 = Dict(:x => 1, :y => 2, :z => 3) + ╠═╡ =# + +# ╔═╡ 76326e6c-b95a-4b2d-a78c-e283e5fadbe2 +# ╠═╡ skip_as_script = true +#=╠═╡ +celldict2 = Dict(:x => 1, :y => 2, :z => 4) + ╠═╡ =# + +# ╔═╡ 664cd334-91c7-40dd-a2bf-0da720307cfc +# ╠═╡ skip_as_script = true +#=╠═╡ +notebook1 = Dict( + :x => 1, + :y => 2, +) + ╠═╡ =# + +# ╔═╡ b7fa5625-6178-4da8-a889-cd4f014f43ba +# ╠═╡ skip_as_script = true +#=╠═╡ +notebook2 = Dict( + :y => 4, + :z => 5 +) + ╠═╡ =# + +# ╔═╡ dbdd1df0-2de1-11eb-152f-8d1af1ad02fe +#=╠═╡ +notebook1_to_notebook2 = diff(notebook1, notebook2) + ╠═╡ =# # ╔═╡ 3924953f-787a-4912-b6ee-9c9d3030f0f0 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ### Large Dict example 1 """ + ╠═╡ =# + +# ╔═╡ 80689881-1b7e-49b2-af97-9e3ab639d006 +# ╠═╡ skip_as_script = true +#=╠═╡ +big_array = rand(UInt8, 1_000_000) + ╠═╡ =# + +# ╔═╡ fd22b6af-5fd2-428a-8291-53e223ea692c +# ╠═╡ skip_as_script = true +#=╠═╡ +big_string = repeat('a', 1_000_000); + ╠═╡ =# + +# ╔═╡ bcd5059b-b0d2-49d8-a756-92349aa56aca +#=╠═╡ +large_dict_1 = Dict{String,Any}( + "cell_$(i)" => Dict{String,Any}( + "x" => 1, + "y" => big_array, + "z" => big_string, + ) + for i in 1:10 +); + ╠═╡ =# + +# ╔═╡ e7fd6bab-c114-4f3e-b9ad-1af2d1147770 +#=╠═╡ +begin + large_dict_2 = Dict{String,Any}( + "cell_$(i)" => Dict{String,Any}( + "x" => 1, + "y" => big_array, + "z" => big_string, + ) + for i in 1:10 + ) + large_dict_2["cell_5"]["y"] = [2,20] + delete!(large_dict_2, "cell_2") + large_dict_2["hello"] = Dict("a" => 1, "b" => 2) + large_dict_2 +end; + ╠═╡ =# + +# ╔═╡ 43c36ab7-e9ac-450a-8abe-435412f2be1d +#=╠═╡ +diff(large_dict_1, large_dict_2) + ╠═╡ =# # ╔═╡ 1cf22fe6-4b58-4220-87a1-d7a18410b4e8 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" With `===` comparison for arrays: """ + ╠═╡ =# # ╔═╡ ffb01ab4-e2e3-4fa4-8c0b-093d2899a536 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ### Large Dict example 2 """ + ╠═╡ =# + +# ╔═╡ 8188de75-ae6e-48aa-9495-111fd27ffd26 +# ╠═╡ skip_as_script = true +#=╠═╡ +many_items_1 = Dict{String,Any}( + "cell_$(i)" => Dict{String,Any}( + "x" => 1, + "y" => [2,3], + "z" => "four", + ) + for i in 1:100 +) + ╠═╡ =# + +# ╔═╡ fdc427f0-dfe8-4114-beca-48fc15434534 +#=╠═╡ +@test isempty(diff(many_items_1, many_items_1)) + ╠═╡ =# + +# ╔═╡ d807195e-ba27-4015-92a7-c9294d458d47 +#=╠═╡ +begin + many_items_2 = deepcopy(many_items_1) + many_items_2["cell_5"]["y"][2] = 20 + delete!(many_items_2, "cell_2") + many_items_2["hello"] = Dict("a" => 1, "b" => 2) + many_items_2 +end + ╠═╡ =# + +# ╔═╡ 2e91a1a2-469c-4123-a0d7-3dcc49715738 +#=╠═╡ +diff(many_items_1, many_items_2) + ╠═╡ =# + +# ╔═╡ b8061c1b-dd03-4cd1-b275-90359ae2bb39 +fairly_equal(a,b) = Set(a) == Set(b) + +# ╔═╡ 2983f6d4-c1ca-4b66-a2d3-f858b0df2b4c +#=╠═╡ +@test fairly_equal(diff(large_dict_1, large_dict_2), [ + ReplacePatch(["cell_5","y"], [2,20]), + RemovePatch(["cell_2"]), + AddPatch(["hello"], Dict("b" => 2, "a" => 1)), +]) + ╠═╡ =# + +# ╔═╡ 61b81430-d26e-493c-96da-b6818e58c882 +#=╠═╡ +@test fairly_equal(diff(many_items_1, many_items_2), [ + ReplacePatch(["cell_5","y"], [2,20]), + RemovePatch(["cell_2"]), + AddPatch(["hello"], Dict("b" => 2, "a" => 1)), +]) + ╠═╡ =# + +# ╔═╡ aeab3363-08ba-47c2-bd33-04a004ed72c4 +#=╠═╡ +diff(many_items_1, many_items_1) + ╠═╡ =# + +# ╔═╡ 62de3e79-4b4e-41df-8020-769c3c255c3e +#=╠═╡ +@test isempty(diff(many_items_1, many_items_1)) + ╠═╡ =# # ╔═╡ c7de406d-ccfe-41cf-8388-6bd2d7c42d64 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### Struct example" + ╠═╡ =# # ╔═╡ b9cc11ae-394b-44b9-bfbe-541d7720ead0 +# ╠═╡ skip_as_script = true +#=╠═╡ struct Cell id code folded end + ╠═╡ =# # ╔═╡ c3c675be-9178-4176-afe0-30501786b72c +#=╠═╡ deep_diff(old::Cell, new::Cell) = diff(Deep(old), Deep(new)) + ╠═╡ =# # ╔═╡ 02585c72-1d92-4526-98c2-1ca07aad87a3 +#=╠═╡ function direct_diff(old::Cell, new::Cell) changes = [] if old.id ≠ new.id @@ -344,15 +640,25 @@ function direct_diff(old::Cell, new::Cell) end changes end + ╠═╡ =# # ╔═╡ 2d084dd1-240d-4443-a8a2-82ae6e0b8900 +# ╠═╡ skip_as_script = true +#=╠═╡ cell1 = Cell(1, 2, 3) + ╠═╡ =# # ╔═╡ 3e05200f-071a-4ebe-b685-ff980f07cde7 +# ╠═╡ skip_as_script = true +#=╠═╡ cell2 = Cell(1, 2, 4) + ╠═╡ =# # ╔═╡ dd312598-2de1-11eb-144c-f92ed6484f5d +# ╠═╡ skip_as_script = true +#=╠═╡ md"## Update" + ╠═╡ =# # ╔═╡ d2af2a4b-8982-4e43-9fd7-0ecfdfb70511 const strict_applypatch = Ref(false) @@ -392,7 +698,10 @@ function applypatch!(value, patches::Array{JSONPatch}) end # ╔═╡ 3e285076-1d97-4728-87cf-f71b22569e57 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### applypatch! AddPatch" + ╠═╡ =# # ╔═╡ dd87ca7e-2de1-11eb-2ec3-d5721c32f192 function applypatch!(value, patch::AddPatch) @@ -420,10 +729,16 @@ function applypatch!(value, patch::AddPatch) end # ╔═╡ a11e4082-4ff4-4c1b-9c74-c8fa7dcceaa6 +# ╠═╡ skip_as_script = true +#=╠═╡ md"*Should throw in strict mode:*" + ╠═╡ =# # ╔═╡ be6b6fc4-e12a-4cef-81d8-d5115fda50b7 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### applypatch! ReplacePatch" + ╠═╡ =# # ╔═╡ 6509d62e-77b6-499c-8dab-4a608e44720a function applypatch!(value, patch::ReplacePatch) @@ -451,10 +766,16 @@ function applypatch!(value, patch::ReplacePatch) end # ╔═╡ f1dde1bd-3fa4-48b7-91ed-b2f98680fcc1 +# ╠═╡ skip_as_script = true +#=╠═╡ md"*Should throw in strict mode:*" + ╠═╡ =# # ╔═╡ f3ef354b-b480-4b48-8358-46dbf37e1d95 +# ╠═╡ skip_as_script = true +#=╠═╡ md"### applypatch! RemovePatch" + ╠═╡ =# # ╔═╡ ddaf5b66-2de1-11eb-3348-b905b94a984b function applypatch!(value, patch::RemovePatch) @@ -481,250 +802,83 @@ function applypatch!(value, patch::RemovePatch) return value end -# ╔═╡ df41caa7-f0fc-4b0d-ab3d-ebdab4804040 -md"*Should throw in strict mode:*" - -# ╔═╡ e55d1cea-2de1-11eb-0d0e-c95009eedc34 -md"## Testing" - -# ╔═╡ b05fcb88-3781-45d0-9f24-e88c339a72e5 -macro test2(expr) - quote nothing end -end - -# ╔═╡ e8d0c98a-2de1-11eb-37b9-e1df3f5cfa25 -md"## `@skip_as_script`" - -# ╔═╡ e907d862-2de1-11eb-11a9-4b3ac37cb0f3 -function is_inside_pluto(m::Module) - if isdefined(m, :PlutoForceDisplay) - return m.PlutoForceDisplay - else - isdefined(m, :PlutoRunner) && parentmodule(m) == Main - end -end - -# ╔═╡ e924a0be-2de1-11eb-2170-71d56e117af2 -""" - @skip_as_script expression - -Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects. -""" -macro skip_as_script(ex) - if is_inside_pluto(__module__) - esc(ex) - else - nothing - end -end - -# ╔═╡ d8e73b90-24c5-4e50-830b-b1dbe6224c8e -@skip_as_script dict_1 = Dict{String,Any}( - "a" => 1, - "b" => Dict( - "c" => [3,4], - "d" => 99, - ), - "e" => "hello!" -); - -# ╔═╡ 19646596-b35b-44fa-bfcf-891f9ffb748c -@skip_as_script dict_2 = Dict{String,Any}( - "a" => 1, - "b" => Dict( - "c" => [3,4,5], - "d" => 99, - "🏝" => "👍", - ), -); - -# ╔═╡ 7ca087b8-73ac-49ea-9c5a-2971f0da491f -@skip_as_script example_patches = diff(dict_1, dict_2) - # ╔═╡ e65d483a-4c13-49ba-bff1-1d54de78f534 -@skip_as_script let +#=╠═╡ +let dict_1_copy = deepcopy(dict_1) applypatch!(dict_1_copy, example_patches) end - -# ╔═╡ 200516da-8cfb-42fe-a6b9-cb4730168923 -@skip_as_script celldict1 = Dict(:x => 1, :y => 2, :z => 3) - -# ╔═╡ 76326e6c-b95a-4b2d-a78c-e283e5fadbe2 -@skip_as_script celldict2 = Dict(:x => 1, :y => 2, :z => 4) - -# ╔═╡ 664cd334-91c7-40dd-a2bf-0da720307cfc -@skip_as_script notebook1 = Dict( - :x => 1, - :y => 2, -) - -# ╔═╡ b7fa5625-6178-4da8-a889-cd4f014f43ba -@skip_as_script notebook2 = Dict( - :y => 4, - :z => 5 -) - -# ╔═╡ dbdd1df0-2de1-11eb-152f-8d1af1ad02fe -@skip_as_script notebook1_to_notebook2 = diff(notebook1, notebook2) - -# ╔═╡ 80689881-1b7e-49b2-af97-9e3ab639d006 -@skip_as_script big_array = rand(UInt8, 1_000_000) - -# ╔═╡ fd22b6af-5fd2-428a-8291-53e223ea692c -@skip_as_script big_string = repeat('a', 1_000_000); - -# ╔═╡ bcd5059b-b0d2-49d8-a756-92349aa56aca -@skip_as_script large_dict_1 = Dict{String,Any}( - "cell_$(i)" => Dict{String,Any}( - "x" => 1, - "y" => big_array, - "z" => big_string, - ) - for i in 1:10 -); - -# ╔═╡ e7fd6bab-c114-4f3e-b9ad-1af2d1147770 -@skip_as_script begin - large_dict_2 = Dict{String,Any}( - "cell_$(i)" => Dict{String,Any}( - "x" => 1, - "y" => big_array, - "z" => big_string, - ) - for i in 1:10 - ) - large_dict_2["cell_5"]["y"] = [2,20] - delete!(large_dict_2, "cell_2") - large_dict_2["hello"] = Dict("a" => 1, "b" => 2) - large_dict_2 -end; - -# ╔═╡ 43c36ab7-e9ac-450a-8abe-435412f2be1d -@skip_as_script diff(large_dict_1, large_dict_2) - -# ╔═╡ 8188de75-ae6e-48aa-9495-111fd27ffd26 -@skip_as_script many_items_1 = Dict{String,Any}( - "cell_$(i)" => Dict{String,Any}( - "x" => 1, - "y" => [2,3], - "z" => "four", - ) - for i in 1:100 -) - -# ╔═╡ d807195e-ba27-4015-92a7-c9294d458d47 -@skip_as_script begin - many_items_2 = deepcopy(many_items_1) - many_items_2["cell_5"]["y"][2] = 20 - delete!(many_items_2, "cell_2") - many_items_2["hello"] = Dict("a" => 1, "b" => 2) - many_items_2 -end - -# ╔═╡ 2e91a1a2-469c-4123-a0d7-3dcc49715738 -@skip_as_script diff(many_items_1, many_items_2) - -# ╔═╡ b8061c1b-dd03-4cd1-b275-90359ae2bb39 -@skip_as_script fairly_equal(a,b) = Set(a) == Set(b) - -# ╔═╡ aeab3363-08ba-47c2-bd33-04a004ed72c4 -@skip_as_script diff(many_items_1, many_items_1) - -# ╔═╡ c2c2b057-a88f-4cc6-ada4-fc55ac29931e -"The opposite of `@skip_as_script`" -macro only_as_script(ex) is_inside_pluto(__module__) ? nothing : esc(ex) end - -# ╔═╡ e748600a-2de1-11eb-24be-d5f0ecab8fa4 -# Only define this in Pluto - assume we are `using Test` otherwise -begin - @skip_as_script begin - import Pkg - Pkg.activate(mktempdir()) - Pkg.add(Pkg.PackageSpec(name="PlutoTest")) - using PlutoTest - end - # Do nothing inside pluto (so we don't need to have Test as dependency) - # test/Firebasey is `using Test` before including this file - @only_as_script begin - if !isdefined(@__MODULE__, Symbol("@test")) - macro test(e...) nothing; end - macro test_throws(e...) nothing; end - macro test_broken(e...) nothing; end - macro testset(e...) nothing; end - end - end -end - -# ╔═╡ 5ddfd616-db20-451b-bc1e-2ad52e0e2777 -@test Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) == - Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) - -# ╔═╡ 24e93923-eab9-4a7b-9bc7-8d8a1209a78f -@test ReplacePatch(["asd"], Dict("a" => 2)) == - ReplacePatch(["asd"], Dict("a" => 2)) - -# ╔═╡ 09ddf4d9-5ccb-4530-bfab-d11b864e872a -@test Base.hash(RemovePatch(["asd"])) == Base.hash(RemovePatch(["asd"])) - -# ╔═╡ d9e764db-94fc-44f7-8c2e-3d63f4809617 -@test RemovePatch(["asd"]) == RemovePatch(["asd"]) - -# ╔═╡ 99df99ad-aad5-4275-97d4-d1ceeb2f8d15 -@test Base.hash(RemovePatch(["aasd"])) != Base.hash(RemovePatch(["asd"])) - -# ╔═╡ 2d665639-7274-495a-ae9d-f358a8219bb7 -@test Base.hash(ReplacePatch(["asd"], Dict("a" => 2))) != - Base.hash(AddPatch(["asd"], Dict("a" => 2))) + ╠═╡ =# # ╔═╡ 595fdfd4-3960-4fbd-956c-509c4cf03473 -@skip_as_script @test applypatch!(deepcopy(notebook1), notebook1_to_notebook2) == notebook2 - -# ╔═╡ 2983f6d4-c1ca-4b66-a2d3-f858b0df2b4c -@skip_as_script @test fairly_equal(diff(large_dict_1, large_dict_2), [ - ReplacePatch(["cell_5","y"], [2,20]), - RemovePatch(["cell_2"]), - AddPatch(["hello"], Dict("b" => 2, "a" => 1)), -]) - -# ╔═╡ fdc427f0-dfe8-4114-beca-48fc15434534 -@skip_as_script @test isempty(diff(many_items_1, many_items_1)) - -# ╔═╡ 61b81430-d26e-493c-96da-b6818e58c882 -@skip_as_script @test fairly_equal(diff(many_items_1, many_items_2), [ - ReplacePatch(["cell_5","y"], [2,20]), - RemovePatch(["cell_2"]), - AddPatch(["hello"], Dict("b" => 2, "a" => 1)), -]) - -# ╔═╡ 62de3e79-4b4e-41df-8020-769c3c255c3e -@skip_as_script @test isempty(diff(many_items_1, many_items_1)) +#=╠═╡ +@test applypatch!(deepcopy(notebook1), notebook1_to_notebook2) == notebook2 + ╠═╡ =# # ╔═╡ c3e4738f-4568-4910-a211-6a46a9d447ee -@skip_as_script @test applypatch!(Dict(:y => "x"), AddPatch([:x], "-")) == Dict(:y => "x", :x => "-") +#=╠═╡ +@test applypatch!(Dict(:y => "x"), AddPatch([:x], "-")) == Dict(:y => "x", :x => "-") + ╠═╡ =# # ╔═╡ 0f094932-10e5-40f9-a3fc-db27a85b4999 -@skip_as_script @test applypatch!(Dict(:x => "x"), AddPatch([:x], "-")) == Dict(:x => "-") +#=╠═╡ +@test applypatch!(Dict(:x => "x"), AddPatch([:x], "-")) == Dict(:x => "-") + ╠═╡ =# # ╔═╡ a560fdca-ee12-469c-bda5-62d7203235b8 +#=╠═╡ @test applypatch!(Dict(:x => "x"), ReplacePatch([:x], "-")) == Dict(:x => "-") + ╠═╡ =# # ╔═╡ 01e3417e-334e-4a8d-b086-4bddc42737b3 +#=╠═╡ @test applypatch!(Dict(:y => "x"), ReplacePatch([:x], "-")) == Dict(:x => "-", :y => "x") + ╠═╡ =# # ╔═╡ 96a80a23-7c56-4c41-b489-15bc1c4e3700 -@skip_as_script @test applypatch!(Dict(:x => "x"), RemovePatch([:x])) == Dict() +#=╠═╡ +@test applypatch!(Dict(:x => "x"), RemovePatch([:x])) == Dict() + ╠═╡ =# + +# ╔═╡ df41caa7-f0fc-4b0d-ab3d-ebdab4804040 +# ╠═╡ skip_as_script = true +#=╠═╡ +md"*Should throw in strict mode:*" + ╠═╡ =# # ╔═╡ fac65755-2a2a-4a3c-b5a8-fc4f6d256754 -@skip_as_script @test applypatch!(Dict(:y => "x"), RemovePatch([:x])) == Dict(:y => "x") +#=╠═╡ +@test applypatch!(Dict(:y => "x"), RemovePatch([:x])) == Dict(:y => "x") + ╠═╡ =# + +# ╔═╡ e55d1cea-2de1-11eb-0d0e-c95009eedc34 +# ╠═╡ skip_as_script = true +#=╠═╡ +md"## Testing" + ╠═╡ =# + +# ╔═╡ b05fcb88-3781-45d0-9f24-e88c339a72e5 +# ╠═╡ skip_as_script = true +#=╠═╡ +macro test2(expr) + quote nothing end +end + ╠═╡ =# # ╔═╡ e7e8d076-2de1-11eb-0214-8160bb81370a -@skip_as_script @test notebook1 == deepcopy(notebook1) +#=╠═╡ +@test notebook1 == deepcopy(notebook1) + ╠═╡ =# # ╔═╡ ee70e282-36d5-4772-8585-f50b9a67ca54 +# ╠═╡ skip_as_script = true +#=╠═╡ md"## Track" + ╠═╡ =# # ╔═╡ a3e8fe70-cbf5-4758-a0f2-d329d138728c +# ╠═╡ skip_as_script = true +#=╠═╡ function prettytime(time_ns::Number) suffices = ["ns", "μs", "ms", "s"] @@ -747,8 +901,11 @@ function prettytime(time_ns::Number) end return "$(roundedtime) $(suffix)" end + ╠═╡ =# # ╔═╡ 0e1c6442-9040-49d9-b754-173583db7ba2 +# ╠═╡ skip_as_script = true +#=╠═╡ begin Base.@kwdef struct Tracked expr @@ -829,8 +986,11 @@ begin end Tracked end + ╠═╡ =# # ╔═╡ 7618aef7-1884-4e32-992d-0fd988e1ab20 +# ╠═╡ skip_as_script = true +#=╠═╡ macro track(expr) times_ran_expr = :(1) expr_to_show = expr @@ -864,27 +1024,41 @@ macro track(expr) ) end end + ╠═╡ =# # ╔═╡ 7b8ab89b-bf56-4ddf-b220-b4881f4a2050 -@skip_as_script @track Base.convert(JSONPatch, convert(Dict, add_patch)) == add_patch +#=╠═╡ +@track Base.convert(JSONPatch, convert(Dict, add_patch)) == add_patch + ╠═╡ =# # ╔═╡ 48ccd28a-060d-4214-9a39-f4c4e506d1aa -@skip_as_script @track Base.convert(JSONPatch, convert(Dict, remove_patch)) == remove_patch +#=╠═╡ +@track Base.convert(JSONPatch, convert(Dict, remove_patch)) == remove_patch + ╠═╡ =# # ╔═╡ 34d86e02-dd34-4691-bb78-3023568a5d16 -@skip_as_script @track Base.convert(JSONPatch, convert(Dict, replace_patch)) == replace_patch +#=╠═╡ +@track Base.convert(JSONPatch, convert(Dict, replace_patch)) == replace_patch + ╠═╡ =# # ╔═╡ 95ff676d-73c8-44cb-ac35-af94418737e9 -@skip_as_script @track for _ in 1:100 diff(celldict1, celldict2) end +#=╠═╡ +@track for _ in 1:100 diff(celldict1, celldict2) end + ╠═╡ =# # ╔═╡ 8c069015-d922-4c60-9340-8d65c80b1a06 -@skip_as_script @track for _ in 1:1000 diff(large_dict_1, large_dict_1) end +#=╠═╡ +@track for _ in 1:1000 diff(large_dict_1, large_dict_1) end + ╠═╡ =# # ╔═╡ bc9a0822-1088-4ee7-8c79-98e06fd50f11 -@skip_as_script @track for _ in 1:1000 diff(large_dict_1, large_dict_2) end +#=╠═╡ +@track for _ in 1:1000 diff(large_dict_1, large_dict_2) end + ╠═╡ =# # ╔═╡ ddf1090c-5239-41df-ae4d-70aeb3a75f2b -@skip_as_script let +#=╠═╡ +let old = use_triple_equals_for_arrays[] use_triple_equals_for_arrays[] = true @@ -893,9 +1067,11 @@ end use_triple_equals_for_arrays[] = old result end + ╠═╡ =# # ╔═╡ 88009db3-f40e-4fd0-942a-c7f4a7eecb5a -@skip_as_script let +#=╠═╡ +let old = use_triple_equals_for_arrays[] use_triple_equals_for_arrays[] = true @@ -904,21 +1080,32 @@ end use_triple_equals_for_arrays[] = old result end + ╠═╡ =# # ╔═╡ c287009f-e864-45d2-a4d0-a525c988a6e0 -@skip_as_script @track for _ in 1:1000 diff(many_items_1, many_items_1) end +#=╠═╡ +@track for _ in 1:1000 diff(many_items_1, many_items_1) end + ╠═╡ =# # ╔═╡ 67a1ae27-f7df-4f84-8809-1cc6a9bcd1ce -@skip_as_script @track for _ in 1:1000 diff(many_items_1, many_items_2) end +#=╠═╡ +@track for _ in 1:1000 diff(many_items_1, many_items_2) end + ╠═╡ =# # ╔═╡ fa959806-3264-4dd5-9f94-ba369697689b -@skip_as_script @track for _ in 1:1000 direct_diff(cell2, cell1) end +#=╠═╡ +@track for _ in 1:1000 direct_diff(cell2, cell1) end + ╠═╡ =# # ╔═╡ a9088341-647c-4fe1-ab85-d7da049513ae -@skip_as_script @track for _ in 1:1000 diff(Deep(cell1), Deep(cell2)) end +#=╠═╡ +@track for _ in 1:1000 diff(Deep(cell1), Deep(cell2)) end + ╠═╡ =# # ╔═╡ 1a26eed8-670c-43bf-9726-2db84b1afdab -@skip_as_script @track sleep(0.1) +#=╠═╡ +@track sleep(0.1) + ╠═╡ =# # ╔═╡ Cell order: # ╟─d948dc6e-2de1-11eb-19e7-cb3bb66353b6 @@ -1049,13 +1236,10 @@ end # ╟─df41caa7-f0fc-4b0d-ab3d-ebdab4804040 # ╟─fac65755-2a2a-4a3c-b5a8-fc4f6d256754 # ╟─e55d1cea-2de1-11eb-0d0e-c95009eedc34 +# ╠═3e07f976-6cd0-4841-9762-d40337bb0645 # ╠═e748600a-2de1-11eb-24be-d5f0ecab8fa4 # ╠═b05fcb88-3781-45d0-9f24-e88c339a72e5 # ╠═e7e8d076-2de1-11eb-0214-8160bb81370a -# ╟─e8d0c98a-2de1-11eb-37b9-e1df3f5cfa25 -# ╟─e907d862-2de1-11eb-11a9-4b3ac37cb0f3 -# ╟─e924a0be-2de1-11eb-2170-71d56e117af2 -# ╠═c2c2b057-a88f-4cc6-ada4-fc55ac29931e # ╟─ee70e282-36d5-4772-8585-f50b9a67ca54 # ╟─1a26eed8-670c-43bf-9726-2db84b1afdab # ╟─0e1c6442-9040-49d9-b754-173583db7ba2 diff --git a/src/webserver/FirebaseyUtils.jl b/src/webserver/FirebaseyUtils.jl index 1033870053..fe9448060a 100644 --- a/src/webserver/FirebaseyUtils.jl +++ b/src/webserver/FirebaseyUtils.jl @@ -1,9 +1,22 @@ ### A Pluto.jl notebook ### -# v0.19.3 +# v0.19.9 using Markdown using InteractiveUtils +# ╔═╡ 092c4b11-8b75-446f-b3ad-01fa858daebb +# ╠═╡ show_logs = false +# ╠═╡ skip_as_script = true +#=╠═╡ +# Only define this in Pluto using skip_as_script = true +begin + import Pkg + Pkg.activate(mktempdir()) + Pkg.add(Pkg.PackageSpec(name="PlutoTest")) + using PlutoTest +end + ╠═╡ =# + # ╔═╡ 058a3333-0567-43b7-ac5f-1f6688325a08 begin """ @@ -32,9 +45,34 @@ begin (String(name) => getfield(a, name), i + 1) end end - + end +# ╔═╡ 55975e53-f70f-4b70-96d2-b144f74e7cde +# ╠═╡ skip_as_script = true +#=╠═╡ +struct A + x + y + z +end + ╠═╡ =# + +# ╔═╡ d7e0de85-5cb2-4036-a2e3-ca416ea83737 +#=╠═╡ +id1 = ImmutableMarker(A(1,"asdf",3)) + ╠═╡ =# + +# ╔═╡ 08350326-526e-4c34-ab27-df9fbf69243e +#=╠═╡ +id2 = ImmutableMarker(A(1,"asdf",4)) + ╠═╡ =# + +# ╔═╡ aa6192e8-410f-4924-8250-4775e21b1590 +#=╠═╡ +id1d, id2d = Dict(id1), Dict(id2) + ╠═╡ =# + # ╔═╡ 273c7c85-8178-44a7-99f0-581754aeb8c8 begin """ @@ -58,11 +96,16 @@ begin end # ╔═╡ ef7032d1-a666-48a6-a56e-df175f5ed832 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ## ImmutableMarker """ + ╠═╡ =# # ╔═╡ 183cef1f-bfe9-42cd-8239-49e9ed00a7b6 +# ╠═╡ skip_as_script = true +#=╠═╡ md""" ## AppendonlyMarker(s) @@ -71,54 +114,12 @@ We make a new type with a specific diff function. It might be very specific per problem, but that's fine for performance problems (I think). It also keeps the performance solutions as separate modules/packages to whatever it is you're actually modeling. """ - -# ╔═╡ 2284ae12-5b8c-4542-81fa-c4d34f2483e7 -# @skip_as_script @test length([AppendonlyMarker([1,2,3], 1)...]) == 1 - -# ╔═╡ 971709de-074e-49cf-8bd4-9c675b037dfd -md"## `@skip_as_script`" - -# ╔═╡ 9fce9aa9-d3c6-4134-9692-8a8756fa3cff -function is_inside_pluto(m::Module) - if isdefined(m, :PlutoForceDisplay) - return m.PlutoForceDisplay - else - isdefined(m, :PlutoRunner) && parentmodule(m) == Main - end -end - -# ╔═╡ db9167c4-7ba7-42e1-949b-0ad18e2d7b25 -""" - @skip_as_script expression - -Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects. -""" -macro skip_as_script(ex) - if is_inside_pluto(__module__) - esc(ex) - else - nothing - end -end - -# ╔═╡ 55975e53-f70f-4b70-96d2-b144f74e7cde -@skip_as_script struct A - x - y - z -end - -# ╔═╡ d7e0de85-5cb2-4036-a2e3-ca416ea83737 -@skip_as_script id1 = ImmutableMarker(A(1,"asdf",3)) - -# ╔═╡ 08350326-526e-4c34-ab27-df9fbf69243e -@skip_as_script id2 = ImmutableMarker(A(1,"asdf",4)) - -# ╔═╡ aa6192e8-410f-4924-8250-4775e21b1590 -@skip_as_script id1d, id2d = Dict(id1), Dict(id2) + ╠═╡ =# # ╔═╡ 35d3bcd7-af51-466a-b4c4-cc055e74d01d -@skip_as_script appendonly_1, appendonly_2 = let +# ╠═╡ skip_as_script = true +#=╠═╡ +appendonly_1, appendonly_2 = let array_1 = [1,2,3,4] appendonly_1 = AppendonlyMarker(array_1) push!(array_1, 5) @@ -126,9 +127,12 @@ end appendonly_1, appendonly_2 end; + ╠═╡ =# # ╔═╡ 1017f6cc-58ac-4c7b-a6d0-a03f5e387f1b -@skip_as_script appendonly_1_large, appendonly_2_large = let +# ╠═╡ skip_as_script = true +#=╠═╡ +appendonly_1_large, appendonly_2_large = let large_array_1 = [ Dict{String,Any}( "x" => 1, @@ -143,47 +147,33 @@ end; appendonly_1, appendonly_2 end; - -# ╔═╡ 33facb62-af28-4e94-946d-545637f320e9 -"The opposite of `@skip_as_script`" -macro only_as_script(ex) is_inside_pluto(__module__) ? nothing : esc(ex) end - -# ╔═╡ 092c4b11-8b75-446f-b3ad-01fa858daebb -# Only define this in Pluto - assume we are `using Test` otherwise -begin - @skip_as_script begin - import Pkg - Pkg.activate(mktempdir()) - Pkg.add(Pkg.PackageSpec(name="PlutoTest")) - using PlutoTest - end - # Do nothing inside pluto (so we don't need to have Test as dependency) - # test/Firebasey is `using Test` before including this file - @only_as_script begin - if !isdefined(@__MODULE__, Symbol("@test")) - macro test(e...) nothing; end - macro test_throws(e...) nothing; end - macro test_broken(e...) nothing; end - macro testset(e...) nothing; end - end - end -end + ╠═╡ =# # ╔═╡ 06492e8d-4500-4efe-80ee-55bf1ee2348c -@skip_as_script @test length([AppendonlyMarker([1,2,3])...]) == 3 +#=╠═╡ +@test length([AppendonlyMarker([1,2,3])...]) == 3 + ╠═╡ =# + +# ╔═╡ 2284ae12-5b8c-4542-81fa-c4d34f2483e7 +# @test length([AppendonlyMarker([1,2,3], 1)...]) == 1 # ╔═╡ dc5cd268-9cfb-49bf-87fb-5b7db4fa6e3c +# ╠═╡ skip_as_script = true +#=╠═╡ md"## Import Firebasey when running inside notebook" + ╠═╡ =# # ╔═╡ 0c2f23d8-8e98-47b7-9c4f-5daa70a6c7fb # OH how I wish I would put in the time to refactor with fromFile or SOEMTGHINLAS LDKJ JULIA WHY ARE YOU LIKE THIS GROW UP -@skip_as_script Firebasey = let - wrapper_module = Module() - Core.eval(wrapper_module, :(module Firebasey - include("Firebasey.jl") - end - )) - wrapper_module.Firebasey +if !@isdefined(Firebasey) + Firebasey = let + wrapper_module = Module() + Core.eval(wrapper_module, :(module Firebasey + include("Firebasey.jl") + end + )) + wrapper_module.Firebasey + end end # ╔═╡ 2903d17e-c6fd-4cea-8585-4db26a00b0e7 @@ -212,28 +202,201 @@ function Firebasey.diff(a::ImmutableMarker, b::ImmutableMarker) end # ╔═╡ 138d2cc2-59ba-4f76-bf66-ecdb98cf4fd5 -@skip_as_script Firebasey.diff(id1, id2) +#=╠═╡ +Firebasey.diff(id1, id2) + ╠═╡ =# # ╔═╡ 8537488d-2ff9-42b7-8bfc-72d43fca713f -@skip_as_script @test Firebasey.diff(appendonly_1, appendonly_2) == [Firebasey.AddPatch([5], 5)] +#=╠═╡ +@test Firebasey.diff(appendonly_1, appendonly_2) == [Firebasey.AddPatch([5], 5)] + ╠═╡ =# -# ╔═╡ 70179239-357a-424d-bac3-3a1431aff536 -var"@track" = Firebasey.var"@track" +# ╔═╡ 721e3c90-15ae-43f2-9234-57b38e3e6b69 +# ╠═╡ skip_as_script = true +#=╠═╡ +md""" +## Track +""" + ╠═╡ =# + +# ╔═╡ e830792c-c809-4fde-ae55-8ae01b4c04b9 +# ╠═╡ skip_as_script = true +#=╠═╡ +function prettytime(time_ns::Number) + suffices = ["ns", "μs", "ms", "s"] + + current_amount = time_ns + suffix = "" + for current_suffix in suffices + if current_amount >= 1000.0 + current_amount = current_amount / 1000.0 + else + suffix = current_suffix + break + end + end + + # const roundedtime = time_ns.toFixed(time_ns >= 100.0 ? 0 : 1) + roundedtime = if current_amount >= 100.0 + round(current_amount; digits=0) + else + round(current_amount; digits=1) + end + return "$(roundedtime) $(suffix)" +end + ╠═╡ =# + +# ╔═╡ 16b03608-0f5f-421a-bab4-89365528b0b4 +# ╠═╡ skip_as_script = true +#=╠═╡ +begin + Base.@kwdef struct Tracked + expr + value + time + bytes + times_ran = 1 + which = nothing + code_info = nothing + end + function Base.show(io::IO, mime::MIME"text/html", value::Tracked) + times_ran = if value.times_ran === 1 + "" + else + """ ($(value.times_ran)×)""" + end + # method = sprint(show, MIME("text/plain"), value.which) + code_info = if value.code_info ≠ nothing + codelength = length(value.code_info.first.code) + "$(codelength) frames in @code_typed" + else + "" + end + color = if value.time > 1 + "red" + elseif value.time > 0.001 + "orange" + elseif value.time > 0.0001 + "blue" + else + "green" + end + + + show(io, mime, HTML(""" +
    +
    +
    +
    + $(value.expr) +
    + $(prettytime(value.time * 1e9 / value.times_ran)) + $(times_ran) +
    +
    $(code_info)
    + +
    + +
    + """)) + end + Tracked +end + ╠═╡ =# + +# ╔═╡ 875fd249-37cc-49da-8a7d-381fe0e21063 +#=╠═╡ +macro track(expr) + times_ran_expr = :(1) + expr_to_show = expr + if expr.head == :for + @assert expr.args[1].head == :(=) + times_ran_expr = expr.args[1].args[2] + expr_to_show = expr.args[2].args[2] + end + + Tracked # reference so that baby Pluto understands + + quote + local times_ran = length($(esc(times_ran_expr))) + local value, time, bytes = @timed $(esc(expr)) + + local method = nothing + local code_info = nothing + try + # Uhhh + method = @which $(expr_to_show) + code_info = @code_typed $(expr_to_show) + catch nothing end + Tracked( + expr=$(QuoteNode(expr_to_show)), + value=value, + time=time, + bytes=bytes, + times_ran=times_ran, + which=method, + code_info=code_info + ) + end +end + ╠═╡ =# # ╔═╡ a5f43f47-6189-413f-95a0-d98f927bb7ce -@skip_as_script @track for _ in 1:1000 Firebasey.diff(id1, id1) end +#=╠═╡ +@track for _ in 1:1000 Firebasey.diff(id1, id1) end + ╠═╡ =# # ╔═╡ ab5089cc-fec8-43b9-9aa4-d6fa96e231e0 -@skip_as_script @track for _ in 1:1000 Firebasey.diff(id1d, id1d) end +#=╠═╡ +@track for _ in 1:1000 Firebasey.diff(id1d, id1d) end + ╠═╡ =# # ╔═╡ a84dcdc3-e9ed-4bf5-9bec-c9cbfc267c17 -@skip_as_script @track for _ in 1:1000 Firebasey.diff(id1, id2) end +#=╠═╡ +@track for _ in 1:1000 Firebasey.diff(id1, id2) end + ╠═╡ =# # ╔═╡ f696bb85-0bbd-43c9-99ea-533816bc8e0d -@skip_as_script @track for _ in 1:1000 Firebasey.diff(id1d, id2d) end +#=╠═╡ +@track for _ in 1:1000 Firebasey.diff(id1d, id2d) end + ╠═╡ =# # ╔═╡ 37fe8c10-09f0-4f72-8cfd-9ce044c78c13 -@skip_as_script @track for _ in 1:1000 Firebasey.diff(appendonly_1_large, appendonly_2_large) end +#=╠═╡ +@track for _ in 1:1000 Firebasey.diff(appendonly_1_large, appendonly_2_large) end + ╠═╡ =# + +# ╔═╡ 9862ee48-48a0-4178-8ec4-306792827e17 +#=╠═╡ +@track sleep(0.1) + ╠═╡ =# # ╔═╡ Cell order: # ╟─ef7032d1-a666-48a6-a56e-df175f5ed832 @@ -244,24 +407,24 @@ var"@track" = Firebasey.var"@track" # ╠═08350326-526e-4c34-ab27-df9fbf69243e # ╠═138d2cc2-59ba-4f76-bf66-ecdb98cf4fd5 # ╠═aa6192e8-410f-4924-8250-4775e21b1590 -# ╠═a5f43f47-6189-413f-95a0-d98f927bb7ce -# ╠═ab5089cc-fec8-43b9-9aa4-d6fa96e231e0 -# ╠═a84dcdc3-e9ed-4bf5-9bec-c9cbfc267c17 -# ╠═f696bb85-0bbd-43c9-99ea-533816bc8e0d +# ╟─a5f43f47-6189-413f-95a0-d98f927bb7ce +# ╟─ab5089cc-fec8-43b9-9aa4-d6fa96e231e0 +# ╟─a84dcdc3-e9ed-4bf5-9bec-c9cbfc267c17 +# ╟─f696bb85-0bbd-43c9-99ea-533816bc8e0d # ╟─183cef1f-bfe9-42cd-8239-49e9ed00a7b6 # ╠═273c7c85-8178-44a7-99f0-581754aeb8c8 # ╠═2903d17e-c6fd-4cea-8585-4db26a00b0e7 # ╠═35d3bcd7-af51-466a-b4c4-cc055e74d01d # ╠═1017f6cc-58ac-4c7b-a6d0-a03f5e387f1b -# ╠═06492e8d-4500-4efe-80ee-55bf1ee2348c +# ╟─06492e8d-4500-4efe-80ee-55bf1ee2348c # ╠═2284ae12-5b8c-4542-81fa-c4d34f2483e7 -# ╠═8537488d-2ff9-42b7-8bfc-72d43fca713f -# ╠═37fe8c10-09f0-4f72-8cfd-9ce044c78c13 -# ╟─971709de-074e-49cf-8bd4-9c675b037dfd -# ╟─9fce9aa9-d3c6-4134-9692-8a8756fa3cff -# ╟─db9167c4-7ba7-42e1-949b-0ad18e2d7b25 -# ╟─33facb62-af28-4e94-946d-545637f320e9 +# ╟─8537488d-2ff9-42b7-8bfc-72d43fca713f +# ╟─37fe8c10-09f0-4f72-8cfd-9ce044c78c13 # ╟─dc5cd268-9cfb-49bf-87fb-5b7db4fa6e3c # ╠═0c2f23d8-8e98-47b7-9c4f-5daa70a6c7fb -# ╠═70179239-357a-424d-bac3-3a1431aff536 # ╠═092c4b11-8b75-446f-b3ad-01fa858daebb +# ╟─721e3c90-15ae-43f2-9234-57b38e3e6b69 +# ╟─9862ee48-48a0-4178-8ec4-306792827e17 +# ╟─16b03608-0f5f-421a-bab4-89365528b0b4 +# ╟─875fd249-37cc-49da-8a7d-381fe0e21063 +# ╟─e830792c-c809-4fde-ae55-8ae01b4c04b9 From 4b577270a1c0777d053150c723573f2a02707f73 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 30 Jun 2022 22:33:56 +0200 Subject: [PATCH 395/821] Fix slide controls (#2194) * Fix slide controls * Update new_notebook_test.js --- frontend/components/SlideControls.js | 2 +- test/frontend/__tests__/slide_controls.js | 63 +++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/frontend/__tests__/slide_controls.js diff --git a/frontend/components/SlideControls.js b/frontend/components/SlideControls.js index 9d7e4f3542..60a6380eb0 100644 --- a/frontend/components/SlideControls.js +++ b/frontend/components/SlideControls.js @@ -2,7 +2,7 @@ import { html } from "../imports/Preact.js" export const SlideControls = () => { const calculate_slide_positions = (/** @type {Event} */ e) => { - const notebook_node = /** @type {HTMLElement?} */ (e.target)?.closest("pluto-notebook") + const notebook_node = /** @type {HTMLElement?} */ (e.target)?.closest("pluto-editor")?.querySelector("pluto-notebook") if (!notebook_node) return [] const height = window.innerHeight diff --git a/test/frontend/__tests__/slide_controls.js b/test/frontend/__tests__/slide_controls.js new file mode 100644 index 0000000000..0d7d7530bb --- /dev/null +++ b/test/frontend/__tests__/slide_controls.js @@ -0,0 +1,63 @@ +import puppeteer from "puppeteer" +import { setupPage, waitForContent } from "../helpers/common" +import { createNewNotebook, getPlutoUrl, manuallyEnterCells, prewarmPluto, waitForNoUpdateOngoing } from "../helpers/pluto" + +describe("slideControls", () => { + let browser = null + let page = null + + beforeAll(async () => { + browser = await puppeteer.launch({ + headless: process.env.HEADLESS !== "false", + args: ["--no-sandbox", "--disable-setuid-sandbox"], + devtools: false, + }) + + let page = await browser.newPage() + setupPage(page) + await prewarmPluto(browser, page) + await page.close() + }) + beforeEach(async () => { + page = await browser.newPage() + setupPage(page) + await page.goto(getPlutoUrl(), { waitUntil: "networkidle0" }) + await createNewNotebook(page) + await page.waitForSelector("pluto-input", { visible: true }) + }) + afterAll(async () => { + await page.close() + await browser.close() + }) + + it("should create titles", async () => { + const cells = ['md"# Slide 1"', 'md"# Slide 2"'] + const plutoCellIds = await manuallyEnterCells(page, cells) + await page.waitForSelector(".runallchanged", { visible: true, polling: 200, timeout: 0 }) + await page.click(".runallchanged") + await waitForNoUpdateOngoing(page, { polling: 100 }) + const content = await waitForContent(page, `pluto-cell[id="${plutoCellIds[1]}"] pluto-output`) + expect(content).toBe("Slide 2") + + const slide_1_title = await page.$(`pluto-cell[id="${plutoCellIds[0]}"] pluto-output h1`) + const slide_2_title = await page.$(`pluto-cell[id="${plutoCellIds[1]}"] pluto-output h1`) + + expect(await slide_2_title.isIntersectingViewport()).toBe(true) + expect(await slide_1_title.isIntersectingViewport()).toBe(true) + + /* @ts-ignore */ + await page.evaluate(() => window.present()) + + await page.click(".changeslide.next") + expect(await slide_1_title.isIntersectingViewport()).toBe(true) + expect(await slide_2_title.isIntersectingViewport()).toBe(false) + + await page.click(".changeslide.next") + expect(await slide_1_title.isIntersectingViewport()).toBe(false) + expect(await slide_2_title.isIntersectingViewport()).toBe(true) + + await page.click(".changeslide.prev") + expect(await slide_1_title.isIntersectingViewport()).toBe(true) + expect(await slide_2_title.isIntersectingViewport()).toBe(false) + }) +}) From 87184bb92dcd1b877f93511cc69d21d396beb392 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 12 Jul 2022 22:23:41 +0200 Subject: [PATCH 396/821] Update to HTTP.jl 1.0 (#2185) * Update to HTTP.jl 1.0 [HTTP.jl](https://github.com/JuliaWeb/HTTP.jl) is nearing its 1.0 release! This PR is a first draft at updating Pluto to use it. * Use Accept-Encoding: identity in test/compiletimes.jl * use https://github.com/JuliaWeb/HTTP.jl/pull/857 for testing * Allow using IO instead of stream for tests * soften HTTP.jl constraint for now * update HTTP.jl to 1.0.2 * remove test install * close serversocket --- Project.toml | 2 +- src/webserver/PutUpdates.jl | 29 ++++-- src/webserver/Static.jl | 32 +++--- src/webserver/WebServer.jl | 180 ++++++++++++++++------------------ src/webserver/WebSocketFix.jl | 80 --------------- test/compiletimes.jl | 2 +- 6 files changed, 125 insertions(+), 200 deletions(-) delete mode 100644 src/webserver/WebSocketFix.jl diff --git a/Project.toml b/Project.toml index f6c6abd129..efcdff0eb7 100644 --- a/Project.toml +++ b/Project.toml @@ -31,7 +31,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] Configurations = "0.15, 0.16, 0.17" FuzzyCompletions = "0.3, 0.4, 0.5" -HTTP = "^0.9.1" +HTTP = "^1.0.2" HypertextLiteral = "0.7, 0.8, 0.9" MIMEs = "0.1" MsgPack = "1.1" diff --git a/src/webserver/PutUpdates.jl b/src/webserver/PutUpdates.jl index ea541777d0..2fd44f074b 100644 --- a/src/webserver/PutUpdates.jl +++ b/src/webserver/PutUpdates.jl @@ -16,7 +16,9 @@ function serialize_message_to_stream(io::IO, message::UpdateMessage) end function serialize_message(message::UpdateMessage) - sprint(serialize_message_to_stream, message) + io = IOBuffer() + serialize_message_to_stream(io, message) + take!(io) end "Send `messages` to all clients connected to the `notebook`." @@ -65,18 +67,29 @@ end # https://github.com/JuliaWeb/HTTP.jl/issues/382 const flushtoken = Token() +function send_message(stream::HTTP.WebSocket, msg) + HTTP.send(stream, serialize_message(msg)) +end +function send_message(stream::IO, msg) + write(stream, serialize_message(msg)) +end + +function is_stream_open(stream::HTTP.WebSocket) + !HTTP.WebSockets.isclosed(stream) +end +function is_stream_open(io::IO) + isopen(io) +end + function flushclient(client::ClientSession) take!(flushtoken) while isready(client.pendingupdates) next_to_send = take!(client.pendingupdates) - + try if client.stream !== nothing - if isopen(client.stream) - if client.stream isa HTTP.WebSockets.WebSocket - client.stream.frame_type = HTTP.WebSockets.WS_BINARY - end - write(client.stream, serialize_message(next_to_send)) + if is_stream_open(client.stream) + send_message(client.stream, next_to_send) else put!(flushtoken) return false @@ -112,4 +125,4 @@ end function flushallclients(session::ServerSession) flushallclients(session, values(session.connected_clients)) -end \ No newline at end of file +end diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index 0aa511d027..e4aecb908e 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -158,11 +158,11 @@ function http_router_for(session::ServerSession) # require_secret_for_access == false # Access to all 'risky' endpoints is still restricted to requests that have the secret cookie, but visiting `/` is allowed, and it will set the cookie. From then on the security situation is identical to # secret_for_access == true - HTTP.@register(router, "GET", "/", with_authentication( + HTTP.register!(router, "GET", "/", with_authentication( create_serve_onefile(project_relative_path(frontend_directory(), "index.html")); required=security.require_secret_for_access )) - HTTP.@register(router, "GET", "/edit", with_authentication( + HTTP.register!(router, "GET", "/edit", with_authentication( create_serve_onefile(project_relative_path(frontend_directory(), "editor.html")); required=security.require_secret_for_access || security.require_secret_for_open_links, @@ -170,8 +170,8 @@ function http_router_for(session::ServerSession) # the /edit page also uses with_authentication, but this is not how access to notebooks is secured: this is done by requiring the WS connection to be authenticated. # we still use it for /edit to do the cookie stuff, and show a more helpful error, instead of the WS never connecting. - HTTP.@register(router, "GET", "/ping", r -> HTTP.Response(200, "OK!")) - HTTP.@register(router, "GET", "/possible_binder_token_please", r -> session.binder_token === nothing ? HTTP.Response(200,"") : HTTP.Response(200, session.binder_token)) + HTTP.register!(router, "GET", "/ping", r -> HTTP.Response(200, "OK!")) + HTTP.register!(router, "GET", "/possible_binder_token_please", r -> session.binder_token === nothing ? HTTP.Response(200,"") : HTTP.Response(200, session.binder_token)) function try_launch_notebook_response(action::Function, path_or_url::AbstractString; title="", advice="", home_url="./", as_redirect=true, action_kwargs...) try @@ -192,8 +192,8 @@ function http_router_for(session::ServerSession) ) do request::HTTP.Request notebook_response(SessionActions.new(session); as_redirect=(request.method == "GET")) end - HTTP.@register(router, "GET", "/new", serve_newfile) - HTTP.@register(router, "POST", "/new", serve_newfile) + HTTP.register!(router, "GET", "/new", serve_newfile) + HTTP.register!(router, "POST", "/new", serve_newfile) # This is not in Dynamic.jl because of bookmarks, how HTML works, # real loading bars and the rest; Same for CustomLaunchEvent @@ -242,8 +242,8 @@ function http_router_for(session::ServerSession) end end - HTTP.@register(router, "GET", "/open", serve_openfile) - HTTP.@register(router, "POST", "/open", serve_openfile) + HTTP.register!(router, "GET", "/open", serve_openfile) + HTTP.register!(router, "POST", "/open", serve_openfile) serve_sample = with_authentication(; required=security.require_secret_for_access || @@ -262,8 +262,8 @@ function http_router_for(session::ServerSession) advice="Please report this error!" ) end - HTTP.@register(router, "GET", "/sample/*", serve_sample) - HTTP.@register(router, "POST", "/sample/*", serve_sample) + HTTP.register!(router, "GET", "/sample/*", serve_sample) + HTTP.register!(router, "POST", "/sample/*", serve_sample) notebook_from_uri(request) = let uri = HTTP.URI(request.target) @@ -285,7 +285,7 @@ function http_router_for(session::ServerSession) return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) end end - HTTP.@register(router, "GET", "/notebookfile", serve_notebookfile) + HTTP.register!(router, "GET", "/notebookfile", serve_notebookfile) serve_statefile = with_authentication(; required=security.require_secret_for_access || @@ -301,7 +301,7 @@ function http_router_for(session::ServerSession) return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) end end - HTTP.@register(router, "GET", "/statefile", serve_statefile) + HTTP.register!(router, "GET", "/statefile", serve_statefile) serve_notebookexport = with_authentication(; required=security.require_secret_for_access || @@ -317,7 +317,7 @@ function http_router_for(session::ServerSession) return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) end end - HTTP.@register(router, "GET", "/notebookexport", serve_notebookexport) + HTTP.register!(router, "GET", "/notebookexport", serve_notebookexport) serve_notebookupload = with_authentication(; required=security.require_secret_for_access || @@ -338,15 +338,15 @@ function http_router_for(session::ServerSession) advice="The contents could not be read as a Pluto notebook file. When copying contents from somewhere else, make sure that you copy the entire notebook file. You can also report this error!" ) end - HTTP.@register(router, "POST", "/notebookupload", serve_notebookupload) + HTTP.register!(router, "POST", "/notebookupload", serve_notebookupload) function serve_asset(request::HTTP.Request) uri = HTTP.URI(request.target) filepath = project_relative_path(frontend_directory(), relpath(HTTP.unescapeuri(uri.path), "/")) asset_response(filepath; cacheable=should_cache(filepath)) end - HTTP.@register(router, "GET", "/*", serve_asset) - HTTP.@register(router, "GET", "/favicon.ico", create_serve_onefile(project_relative_path(frontend_directory(allow_bundled=false), "img", "favicon.ico"))) + HTTP.register!(router, "GET", "/**", serve_asset) + HTTP.register!(router, "GET", "/favicon.ico", create_serve_onefile(project_relative_path(frontend_directory(allow_bundled=false), "img", "favicon.ico"))) return router end diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 5f967ba4bc..4fef488f25 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -4,8 +4,6 @@ import HTTP import Sockets import .PkgCompat -include("./WebSocketFix.jl") - function open_in_default_browser(url::AbstractString)::Bool try if Sys.isapple() @@ -27,11 +25,13 @@ end isurl(s::String) = startswith(s, "http://") || startswith(s, "https://") -swallow_exception(f, exception_type::Type{T}) where T = - try f() +function swallow_exception(f, exception_type::Type{T}) where {T} + try + f() catch e isa(e, T) || rethrow(e) end +end """ Pluto.run() @@ -67,19 +67,19 @@ end # Deprecation errors -function run(host::String, port::Union{Nothing,Integer}=nothing; kwargs...) +function run(host::String, port::Union{Nothing,Integer} = nothing; kwargs...) @error """run(host, port) is deprecated in favor of: - + run(;host="$host", port=$port) - + """ end function run(port::Integer; kwargs...) @error "Oopsie! This is the old command to launch Pluto. The new command is: - + Pluto.run() - + without the port as argument - it will choose one automatically. If you need to specify the port, use: Pluto.run(port=$port) @@ -88,27 +88,17 @@ end # open notebook(s) on startup -open_notebook!(session:: ServerSession, notebook:: Nothing) = Nothing +open_notebook!(session::ServerSession, notebook::Nothing) = Nothing -open_notebook!(session:: ServerSession, notebook:: AbstractString) = SessionActions.open(session, notebook) +open_notebook!(session::ServerSession, notebook::AbstractString) = SessionActions.open(session, notebook) -function open_notebook!(session:: ServerSession, notebook:: AbstractVector{<: AbstractString}) +function open_notebook!(session::ServerSession, notebook::AbstractVector{<:AbstractString}) for nb in notebook SessionActions.open(session, nb) end end -""" - run(session::ServerSession) - -Specifiy the [`Pluto.ServerSession`](@ref) to run the web server on, which includes the configuration. Passing a session as argument allows you to start the web server with some notebooks already running. See [`SessionActions`](@ref) to learn more about manipulating a `ServerSession`. -""" -function run(session::ServerSession) - pluto_router = http_router_for(session) - Base.invokelatest(run, session, pluto_router) -end - const is_first_run = Ref(true) "Return a port and serversocket to use while taking into account the `favourite_port`." @@ -127,16 +117,23 @@ function port_serversocket(hostIP::Sockets.IPAddr, favourite_port, port_hint) return port, serversocket end -function run(session::ServerSession, pluto_router) +""" + run(session::ServerSession) + +Specifiy the [`Pluto.ServerSession`](@ref) to run the web server on, which includes the configuration. Passing a session as argument allows you to start the web server with some notebooks already running. See [`SessionActions`](@ref) to learn more about manipulating a `ServerSession`. +""" +function run(session::ServerSession) if is_first_run[] is_first_run[] = false @info "Loading..." end - + if VERSION < v"1.6.2" @warn("\nPluto is running on an old version of Julia ($(VERSION)) that is no longer supported. Visit https://julialang.org/downloads/ for more information about upgrading Julia.") end + pluto_router = http_router_for(session) + notebook_at_startup = session.options.server.notebook open_notebook!(session, notebook_at_startup) @@ -147,11 +144,23 @@ function run(session::ServerSession, pluto_router) local port, serversocket = port_serversocket(hostIP, favourite_port, port_hint) - shutdown_server = Ref{Function}(() -> ()) + on_shutdown() = @sync begin + # Triggered by HTTP.jl + @info("\n\nClosing Pluto... Restart Julia for a fresh session. \n\nHave a nice day! 🎈\n\n") + # TODO: put do_work tokens back + @async swallow_exception(() -> close(serversocket), Base.IOError) + for client in values(session.connected_clients) + @async swallow_exception(() -> close(client.stream), Base.IOError) + end + empty!(session.connected_clients) + for nb in values(session.notebooks) + @asynclog SessionActions.shutdown(session, nb; keep_in_session = false, async = false, verbose = false) + end + end - servertask = @async HTTP.serve(hostIP, port; stream=true, server=serversocket) do http::HTTP.Stream + server = HTTP.listen!(hostIP, port; stream = true, server = serversocket, on_shutdown) do http::HTTP.Stream # messy messy code so that we can use the websocket on the same port as the HTTP server - if HTTP.WebSockets.is_upgrade(http.message) + if HTTP.WebSockets.isupgrade(http.message) secret_required = let s = session.options.security s.require_secret_for_access || s.require_secret_for_open_links @@ -160,39 +169,34 @@ function run(session::ServerSession, pluto_router) try HTTP.WebSockets.upgrade(http) do clientstream - if !isopen(clientstream) + if HTTP.WebSockets.isclosed(clientstream) return end try - while !eof(clientstream) - # This stream contains data received over the WebSocket. - # It is formatted and MsgPack-encoded by send(...) in PlutoConnection.js - local parentbody = nothing - try - message = collect(WebsocketFix.readmessage(clientstream)) - parentbody = unpack(message) - - let - lag = session.options.server.simulated_lag - (lag > 0) && sleep(lag * (0.5 + rand())) # sleep(0) would yield to the process manager which we dont want - end - - process_ws_message(session, parentbody, clientstream) - catch ex - if ex isa InterruptException - shutdown_server[]() - elseif ex isa HTTP.WebSockets.WebSocketError || ex isa EOFError - # that's fine! - else - bt = stacktrace(catch_backtrace()) - @warn "Reading WebSocket client stream failed for unknown reason:" parentbody exception = (ex, bt) + for message in clientstream + # This stream contains data received over the WebSocket. + # It is formatted and MsgPack-encoded by send(...) in PlutoConnection.js + local parentbody = nothing + try + parentbody = unpack(message) + + let + lag = session.options.server.simulated_lag + (lag > 0) && sleep(lag * (0.5 + rand())) # sleep(0) would yield to the process manager which we dont want + end + + process_ws_message(session, parentbody, clientstream) + catch ex + if ex isa InterruptException || ex isa HTTP.WebSockets.WebSocketError || ex isa EOFError + # that's fine! + else + bt = stacktrace(catch_backtrace()) + @warn "Reading WebSocket client stream failed for unknown reason:" parentbody exception = (ex, bt) + end end end - end catch ex - if ex isa InterruptException - shutdown_server[]() - elseif ex isa HTTP.WebSockets.WebSocketError || ex isa EOFError || (ex isa Base.IOError && occursin("connection reset", ex.msg)) + if ex isa InterruptException || ex isa HTTP.WebSockets.WebSocketError || ex isa EOFError || (ex isa Base.IOError && occursin("connection reset", ex.msg)) # that's fine! else bt = stacktrace(catch_backtrace()) @@ -202,7 +206,7 @@ function run(session::ServerSession, pluto_router) end catch ex if ex isa InterruptException - shutdown_server[]() + # that's fine! elseif ex isa Base.IOError # that's fine! elseif ex isa ArgumentError && occursin("stream is closed", ex.msg) @@ -225,10 +229,10 @@ function run(session::ServerSession, pluto_router) end else # then it's a regular HTTP request, not a WS upgrade - + request::HTTP.Request = http.message request.body = read(http) - HTTP.closeread(http) + # HTTP.closeread(http) # If a "token" url parameter is passed in from binder, then we store it to add to every URL (so that you can share the URL to collaborate). params = HTTP.queryparams(HTTP.URI(request.target)) @@ -236,9 +240,8 @@ function run(session::ServerSession, pluto_router) session.binder_token = params["token"] end - request_body = IOBuffer(HTTP.payload(request)) - response_body = HTTP.handle(pluto_router, request) - + response_body = pluto_router(request) + request.response::HTTP.Response = response_body request.response.request = request try @@ -249,7 +252,6 @@ function run(session::ServerSession, pluto_router) HTTP.setheader(http, "Server" => "Pluto.jl/$(PLUTO_VERSION_STR[2:end]) Julia/$(JULIA_VERSION_STR[2:end])") HTTP.startwrite(http) write(http, request.response.body) - HTTP.closewrite(http) catch e if isa(e, Base.IOError) || isa(e, ArgumentError) # @warn "Attempted to write to a closed stream at $(request.target)" @@ -259,15 +261,16 @@ function run(session::ServerSession, pluto_router) end end end - - server_running() = try - HTTP.get("http://$(hostIP):$(port)/ping"; status_exception=false, retry=false, connect_timeout=10, readtimeout=10).status == 200 - catch - false - end + + server_running() = + try + HTTP.get("http://$(hostIP):$(port)/ping"; status_exception = false, retry = false, connect_timeout = 10, readtimeout = 10).status == 200 + catch + false + end # Wait for the server to start up before opening the browser. We have a 5 second grace period for allowing the connection, and then 10 seconds for the server to write data. WorkspaceManager.poll(server_running, 5.0, 1.0) - + address = pretty_address(session, hostIP, port) if session.options.server.launch_browser && open_in_default_browser(address) @info("\nOpening $address in your default browser... ~ have fun!") @@ -275,7 +278,7 @@ function run(session::ServerSession, pluto_router) @info("\nGo to $address in your browser to start writing ~ have fun!") end @info("\nPress Ctrl+C in this terminal to stop Pluto\n\n") - + # Trigger ServerStartEvent with server details try_event_call(session, ServerStartEvent(address, port)) @@ -286,31 +289,18 @@ function run(session::ServerSession, pluto_router) # Start this in the background, so that the first notebook launch (which will trigger registry update) will be faster @asynclog withtoken(pkg_token) do will_update = !PkgCompat.check_registry_age() - PkgCompat.update_registries(; force=false) + PkgCompat.update_registries(; force = false) will_update && println(" Updating registry done ✓") end - shutdown_server[] = () -> @sync begin - @info("\n\nClosing Pluto... Restart Julia for a fresh session. \n\nHave a nice day! 🎈\n\n") - @async swallow_exception(() -> close(serversocket), Base.IOError) - # TODO: HTTP has a kill signal? - # TODO: put do_work tokens back - for client in values(session.connected_clients) - @async swallow_exception(() -> close(client.stream), Base.IOError) - end - empty!(session.connected_clients) - for nb in values(session.notebooks) - @asynclog SessionActions.shutdown(session, nb; keep_in_session=false, async=false, verbose=false) - end - end - try # create blocking call and switch the scheduler back to the server task, so that interrupts land there - wait(servertask) + wait(server) catch e if e isa InterruptException - shutdown_server[]() + close(server) elseif e isa TaskFailedException + @debug "Error is " exception = e stacktrace = catch_backtrace() # nice! else rethrow(e) @@ -319,9 +309,9 @@ function run(session::ServerSession, pluto_router) end precompile(run, (ServerSession, HTTP.Handlers.Router{Symbol("##001")})) -get_favorite_notebook(notebook:: Nothing) = nothing -get_favorite_notebook(notebook:: String) = notebook -get_favorite_notebook(notebook:: AbstractVector) = first(notebook) +get_favorite_notebook(notebook::Nothing) = nothing +get_favorite_notebook(notebook::String) = notebook +get_favorite_notebook(notebook::AbstractVector) = first(notebook) function pretty_address(session::ServerSession, hostIP, port) root = if session.options.server.root_url !== nothing @@ -359,15 +349,17 @@ function pretty_address(session::ServerSession, hostIP, port) else root end - string(HTTP.URI(HTTP.URI(new_root); query=url_params)) + string(HTTP.URI(HTTP.URI(new_root); query = url_params)) end "All messages sent over the WebSocket get decoded+deserialized and end up here." -function process_ws_message(session::ServerSession, parentbody::Dict, clientstream::IO) +function process_ws_message(session::ServerSession, parentbody::Dict, clientstream) client_id = Symbol(parentbody["client_id"]) - client = get!(session.connected_clients, client_id, ClientSession(client_id, clientstream)) + client = get!(session.connected_clients, client_id ) do + ClientSession(client_id, clientstream) + end client.stream = clientstream # it might change when the same client reconnects - + messagetype = Symbol(parentbody["type"]) request_id = Symbol(parentbody["request_id"]) @@ -384,7 +376,7 @@ function process_ws_message(session::ServerSession, parentbody::Dict, clientstre client.connected_notebook = notebook end end - + notebook else nothing diff --git a/src/webserver/WebSocketFix.jl b/src/webserver/WebSocketFix.jl deleted file mode 100644 index a7ad726938..0000000000 --- a/src/webserver/WebSocketFix.jl +++ /dev/null @@ -1,80 +0,0 @@ -"Things that will hopefully go into HTTP.jl someday." -module WebsocketFix - -import HTTP.WebSockets - -function readframe(ws::WebSockets.WebSocket) - header = WebSockets.readheader(ws.io) - # @debug 1 "WebSocket ➡️ $header" - - if header.length > 0 - if length(ws.rxpayload) < header.length - resize!(ws.rxpayload, header.length) - end - unsafe_read(ws.io, pointer(ws.rxpayload), header.length) - # @debug 2 " ➡️ \"$(String(ws.rxpayload[1:header.length]))\"" - end - l = Int(header.length) - if header.hasmask - WebSockets.mask!(ws.rxpayload, ws.rxpayload, l, reinterpret(UInt8, [header.mask])) - end - - return header, view(ws.rxpayload, 1:l) -end - -""" - readmessage(ws::WebSocket) - -HTTP.jl's default `readframe` (or `readavailable`) doesn't look at the FINAL field of frames. -This means that it will return a frame no matter what, even though most people expect to get a full message. -This method fixes that and gives you what you expect. -""" -function readmessage(ws::WebSockets.WebSocket) - # this code is based on HTTP.jl source code: https://github.com/JuliaWeb/HTTP.jl/blob/master/src/WebSockets.jl - - header, data = readframe(ws) - l = Int(header.length) - - if header.opcode == WebSockets.WS_CLOSE - ws.rxclosed = true - if l >= 2 - status = UInt16(ws.rxpayload[1]) << 8 | ws.rxpayload[2] - if status != 1000 - message = String(ws.rxpayload[3:l]) - status_descr = get(WebSockets.STATUS_CODE_DESCRIPTION, Int(status), "") - msg = "Status: $(status_descr), Internal Code: $(message)" - throw(WebSockets.WebSocketError(status, msg)) - end - end - return UInt8[] - elseif header.opcode == WebSockets.WS_PING - WebSockets.wswrite(ws, WebSockets.WS_FINAL | WebSockets.WS_PONG, ws.rxpayload[1:l]) - header2, data2 = readframe(ws) - return readmessage(ws) - elseif header.opcode == WebSockets.WS_CONTINUATION - error("WS continuation gone wrong") - else - if header.final == true - return view(ws.rxpayload, 1:l) - else - multi_message_data = UInt8[] - append!(multi_message_data, data) - while true - header2, data2 = readframe(ws) - if header2.opcode != WebSockets.WS_CONTINUATION - println("header2.opcode:", header2.opcode) - println("header2:", header2) - throw("Should be a continuation") - end - append!(multi_message_data, data2) - if header2.final - break - end - end - - multi_message_data - end - end -end - -end \ No newline at end of file diff --git a/test/compiletimes.jl b/test/compiletimes.jl index 123f01ca3c..c1eef7d165 100644 --- a/test/compiletimes.jl +++ b/test/compiletimes.jl @@ -40,7 +40,7 @@ wait_for_ready(nb) Pluto.SessionActions.shutdown(🍭, nb; async=false) -# Compile HTTP get. +# Compile HTTP get. Use no encoding since there seem to be an issue with Accept-Encoding: gzip HTTP.get("http://github.com") @timeit TOUT "Pluto.run" server_task = @eval let From 67a1e9fdfa1ee2cd612eca6fc438c501386e59ae Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 13 Jul 2022 08:20:18 +0200 Subject: [PATCH 397/821] fix broadcast bug with Vector + AbstractMatrix (#2212) --- src/runner/PlutoRunner.jl | 5 ++++- test/React.jl | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index 4ac9d070ab..c5558d64e1 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -399,7 +399,10 @@ struct OutputNotDefined end function compute(m::Module, computer::Computer) # 1. get the referenced global variables # this might error if the global does not exist, which is exactly what we want - input_global_values = getfield.([m], computer.input_globals) + input_global_values = Vector{Any}(undef, length(computer.input_globals)) + for (i, s) in enumerate(computer.input_globals) + input_global_values[i] = getfield(m, s) + end # 2. run the function out = Base.invokelatest(computer.f, input_global_values...) diff --git a/test/React.jl b/test/React.jl index bc1599f295..a4b112a02c 100644 --- a/test/React.jl +++ b/test/React.jl @@ -1706,4 +1706,20 @@ import Distributed WorkspaceManager.unmake_workspace((🍭, notebook); verbose=false) end + + @testset "Broadcast bug - Issue #2211" begin + notebook = Notebook(Cell.([ + "abstract type AbstractFoo{T} <: AbstractMatrix{T} end", + "struct X{T} <: AbstractFoo{T} end", + "convert(::Type{AbstractArray{T}}, S::AbstractFoo) where {T<:Number} = convert(AbstractFoo{T}, S)", + "Base.convert(::Type{AbstractArray{T}}, ::AbstractFoo) where {T} = nothing", + "Base.size(::AbstractFoo) = (2,2)", + "Base.getindex(::AbstractFoo{T}, args...) where {T} = one(T)", + "x = X{Float64}()", + "y = zeros(2,)", + "x, y", + ])) + update_run!(🍭, notebook, notebook.cells) + @test all(noerror, notebook.cells) + end end From 9674bf8da85410a415038f0f1af1eb83c5f25c3c Mon Sep 17 00:00:00 2001 From: Pablo Zubieta <8410335+pabloferz@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:22:10 -0500 Subject: [PATCH 398/821] Fix WSL paths (#2219) --- src/notebook/path helpers.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/notebook/path helpers.jl b/src/notebook/path helpers.jl index 4ada9473c4..8f486a57ea 100644 --- a/src/notebook/path helpers.jl +++ b/src/notebook/path helpers.jl @@ -28,7 +28,8 @@ function maybe_convert_path_to_wsl(path) try if detectwsl() # wslpath utility prints path to stderr if it fails to convert - # (it fails for WSL-valid paths) + # (it used to fail for WSL-valid paths) + !isnothing(match(r"^/mnt/\w+/", path)) && return path return readchomp(pipeline(`wslpath -u $(path)`; stderr=devnull)) end catch e From 9b4ca83da24b49d15e8192a70a4d8a985c554e37 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 23 Jul 2022 10:08:21 +0200 Subject: [PATCH 399/821] fix progress logging perf (#2222) --- src/runner/PlutoRunner.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index c5558d64e1..d2d7bfc2c6 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -2135,7 +2135,7 @@ function Logging.handle_message(pl::PlutoLogger, level, msg, _module, group, id, put!(pl.log_channel, Dict{String,Any}( "level" => string(level), - "msg" => format_output_default(msg isa String ? Text(msg) : msg), + "msg" => format_output_default(msg isa AbstractString ? Text(msg) : msg), "group" => string(group), "id" => string(id), "file" => string(file), From f568105117e1c53229062ac7767152f4b39c9b5c Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 24 Jul 2022 17:03:55 +0200 Subject: [PATCH 400/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index efcdff0eb7..a950282e57 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.19.9" +version = "0.19.10" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 80a9b07c818efbd0e171499a1fb64d70a84d5151 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 24 Jul 2022 17:15:08 +0200 Subject: [PATCH 401/821] Update sample notebooks & reimport from JuliaPluto/featured --- frontend/featured_sources.js | 4 +- sample/JavaScript.jl | 29 +++++- sample/Plots.jl.jl | 168 +++++++++++++++++++++++------------ sample/PlutoUI.jl.jl | 30 ++++++- 4 files changed, 166 insertions(+), 65 deletions(-) diff --git a/frontend/featured_sources.js b/frontend/featured_sources.js index e9573ba776..4af889fd10 100644 --- a/frontend/featured_sources.js +++ b/frontend/featured_sources.js @@ -2,8 +2,8 @@ export default { // check out https://github.com/JuliaPluto/pluto-developer-instructions/blob/main/How%20to%20update%20the%20featured%20notebooks.md to learn more sources: [ { - url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@a0158dceac9ec602aaf4d8e0d53ddd10b8b56854/pluto_export.json", - integrity: "sha256-6LMXdEg2AvJcgkO1ne6tf75UFC8Vob6YqRAx6ytrfk8=", + url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@b31a1701ebe14b3cc9cc8a06849153161e6f6972/pluto_export.json", + integrity: "sha256-JHf0MqoVjk8ISe2GJ9qBC3gj/3I86SiZbgp07eJYZD4=", }, ], } diff --git a/sample/JavaScript.jl b/sample/JavaScript.jl index d26481309b..cc59d97a7e 100644 --- a/sample/JavaScript.jl +++ b/sample/JavaScript.jl @@ -1,6 +1,14 @@ ### A Pluto.jl notebook ### # v0.19.9 +#> [frontmatter] +#> author_url = "https://github.com/JuliaPluto" +#> image = "https://upload.wikimedia.org/wikipedia/commons/9/99/Unofficial_JavaScript_logo_2.svg" +#> tags = ["javascript", "web", "classic"] +#> author_name = "Pluto.jl" +#> description = "Use HTML, CSS and JavaScript to make your own interactive visualizations!" +#> license = "Unlicense" + using Markdown using InteractiveUtils @@ -1120,14 +1128,21 @@ git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" version = "0.11.4" +[[CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" [[Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +[[FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + [[FixedPointNumbers]] deps = ["Statistics"] git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" @@ -1182,7 +1197,7 @@ uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" [[LinearAlgebra]] -deps = ["Libdl"] +deps = ["Libdl", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[Logging]] @@ -1205,6 +1220,10 @@ uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +[[OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" + [[Parsers]] deps = ["Dates"] git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" @@ -1230,7 +1249,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[Reexport]] @@ -1283,6 +1302,10 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +[[libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" + [[nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" diff --git a/sample/Plots.jl.jl b/sample/Plots.jl.jl index bc41af069b..833cbfdd95 100644 --- a/sample/Plots.jl.jl +++ b/sample/Plots.jl.jl @@ -1,6 +1,14 @@ ### A Pluto.jl notebook ### # v0.19.9 +#> [frontmatter] +#> author_url = "https://github.com/JuliaPluto" +#> image = "https://user-images.githubusercontent.com/6933510/174067386-c0a1296f-dba8-4dbf-b936-1c7f81933f94.png" +#> tags = ["basic", "classic", "plotting"] +#> author_name = "Pluto.jl" +#> description = "An introduction to Plots.jl" +#> license = "Unlicense" + using Markdown using InteractiveUtils @@ -22,6 +30,7 @@ Let's start by importing the `Plots` package. _(Pluto's package manager automati md"We need to choose a _backend_ for Plots.jl. We choose `plotly` because it works with no additional dependencies. You can [read more about backends](http://docs.juliaplots.org/latest/backends/#backends) in Plots.jl - it's one of its coolest features!" # ╔═╡ 9414a092-f105-11ea-10cd-23f84e47d876 +# ╠═╡ show_logs = false plotly() # ╔═╡ 5ae65950-9ad9-11ea-2e14-35119d369acd @@ -215,7 +224,7 @@ PLUTO_PROJECT_TOML_CONTENTS = """ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] -Plots = "~1.30.0" +Plots = "~1.31.4" """ # ╔═╡ 00000000-0000-0000-0000-000000000002 @@ -251,21 +260,27 @@ version = "1.16.1+1" [[ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "9489214b993cd42d17f44c36e359bf6a7c919abf" +git-tree-sha1 = "80ca332f6dcb2508adba68f22f551adb2d00a624" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.15.0" +version = "1.15.3" [[ChangesOfVariables]] deps = ["ChainRulesCore", "LinearAlgebra", "Test"] -git-tree-sha1 = "1e315e3f4b0b7ce40feded39c73049692126cf53" +git-tree-sha1 = "38f7a08f19d8810338d4f5085211c7dfa5d5bdd8" uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" -version = "0.1.3" +version = "0.1.4" + +[[CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.0" [[ColorSchemes]] deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Random"] -git-tree-sha1 = "7297381ccb5df764549818d9a7d57e45f1057d30" +git-tree-sha1 = "1fd869cc3875b57347f7027521f561cf46d1fcd8" uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.18.0" +version = "3.19.0" [[ColorTypes]] deps = ["FixedPointNumbers", "Random"] @@ -296,10 +311,9 @@ deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" [[Contour]] -deps = ["StaticArrays"] -git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7" +git-tree-sha1 = "d05d9e7b7aedff4e5b51a029dced05cfb6125781" uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" -version = "0.5.7" +version = "0.6.2" [[DataAPI]] git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40" @@ -327,12 +341,12 @@ uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" [[DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" +git-tree-sha1 = "c5544d8abb854e306b7b2f799ab31cdba527ccae" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" +version = "0.9.0" [[Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" [[EarCut_jll]] @@ -354,10 +368,13 @@ uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" version = "0.4.1" [[FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_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 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f" +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "ccd479984c7838684b3ac204b716c89955c76623" uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "4.4.0+0" +version = "4.4.2+0" + +[[FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" [[FixedPointNumbers]] deps = ["Statistics"] @@ -397,15 +414,15 @@ version = "3.3.6+0" [[GR]] deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "RelocatableFolders", "Serialization", "Sockets", "Test", "UUIDs"] -git-tree-sha1 = "c98aea696662d09e215ef7cda5296024a9646c75" +git-tree-sha1 = "037a1ca47e8a5989cc07d19729567bb71bfabd0c" uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.64.4" +version = "0.66.0" [[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 = "3a233eeeb2ca45842fe100e0413936834215abf5" +git-tree-sha1 = "c8ab731c9127cd931c93221f65d6a1008dad7256" uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.64.4+0" +version = "0.66.0+0" [[GeometryBasics]] deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"] @@ -437,10 +454,10 @@ uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" version = "1.0.2" [[HTTP]] -deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] -git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a" +deps = ["Base64", "CodecZlib", "Dates", "IniFile", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "ed47af35905b7cc8f1a522ca684b35a212269bd8" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "0.9.17" +version = "1.2.0" [[HarfBuzz_jll]] deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] @@ -521,9 +538,9 @@ version = "1.3.0" [[Latexify]] deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"] -git-tree-sha1 = "46a39b9c58749eefb5f2dc1178cb8fab5332b1ab" +git-tree-sha1 = "1a43be956d433b5d0321197150c2f94e16c0aaa0" uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.15.15" +version = "0.15.16" [[LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] @@ -593,18 +610,24 @@ uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" version = "2.36.0+0" [[LinearAlgebra]] -deps = ["Libdl"] +deps = ["Libdl", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[LogExpFunctions]] deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "09e4b894ce6a976c354a69041a04748180d43637" +git-tree-sha1 = "7c88f63f9f0eb5929f15695af9a4d7d3ed278a91" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.15" +version = "0.3.16" [[Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +[[LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "5d4d2d9904227b8bd66386c1138cf4d5ffa826bf" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "0.4.9" + [[MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" @@ -616,10 +639,10 @@ deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] -git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe" +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "Random", "Sockets"] +git-tree-sha1 = "9f4f5a42de3300439cb8300236925670f844a555" uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.0.3" +version = "1.1.1" [[MbedTLS_jll]] deps = ["Artifacts", "Libdl"] @@ -643,9 +666,10 @@ uuid = "a63ad114-7e13-5084-954f-fe012c677804" uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[NaNMath]] -git-tree-sha1 = "737a5957f387b17e74d4ad2f440eb330b39a62c5" +deps = ["OpenLibm_jll"] +git-tree-sha1 = "a7c3d1da1189a1c2fe843a3bfa04d18d20eb3211" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.0.0" +version = "1.0.1" [[NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -656,15 +680,19 @@ git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f" uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" version = "1.3.5+1" +[[OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" + [[OpenLibm_jll]] deps = ["Artifacts", "Libdl"] uuid = "05823500-19ac-5b8b-9628-191a04bc5112" [[OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "ab05aa4cc89736e95915b01e7279e61b1bfe33b8" +git-tree-sha1 = "e60321e3f2616584ff98f0a4f18d98ae6f89bbb3" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "1.1.14+0" +version = "1.1.17+0" [[OpenSpecFun_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] @@ -713,15 +741,15 @@ version = "3.0.0" [[PlotUtils]] deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"] -git-tree-sha1 = "bb16469fd5224100e422f0b027d26c5a25de1200" +git-tree-sha1 = "9888e59493658e476d3073f1ce24348bdc086660" uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.2.0" +version = "1.3.0" [[Plots]] deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "0b727ac13565a2b665cc78db579e0093b869034e" +git-tree-sha1 = "0a0da27969e8b6b2ee67c112dcf7001a659049a0" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.30.0" +version = "1.31.4" [[Preferences]] deps = ["TOML"] @@ -744,7 +772,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[RecipesBase]] @@ -754,9 +782,9 @@ version = "1.2.1" [[RecipesPipeline]] deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"] -git-tree-sha1 = "dc1e451e15d90347a7decc4221842a022b011714" +git-tree-sha1 = "2690681814016887462cf5ac37102b51cd9ec781" uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.5.2" +version = "0.6.2" [[Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" @@ -765,9 +793,9 @@ version = "1.2.2" [[RelocatableFolders]] deps = ["SHA", "Scratch"] -git-tree-sha1 = "cdbd3b1338c72ce29d9584fdbe9e9b70eeb5adca" +git-tree-sha1 = "22c5201127d7b243b9ee1de3b43c408879dff60f" uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "0.1.3" +version = "0.3.0" [[Requires]] deps = ["UUIDs"] @@ -780,9 +808,9 @@ uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" [[Scratch]] deps = ["Dates"] -git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda" +git-tree-sha1 = "f94f779c94e58bf9ea243e77a37e16d9de9126bd" uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.1.0" +version = "1.1.1" [[Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -793,6 +821,11 @@ git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" version = "1.0.3" +[[SimpleBufferStream]] +git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.1.0" + [[Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" @@ -808,15 +841,20 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [[SpecialFunctions]] deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "a9e798cae4867e3a41cae2dd9eb60c047f1212db" +git-tree-sha1 = "d75bda01f8c31ebb72df80a46c88b25d1c79c56d" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.1.6" +version = "2.1.7" [[StaticArrays]] -deps = ["LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "2bbd9f2e40afd197a1379aef05e0d85dba649951" +deps = ["LinearAlgebra", "Random", "StaticArraysCore", "Statistics"] +git-tree-sha1 = "23368a3313d12a2326ad0035f0db0c0966f438ef" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.4.7" +version = "1.5.2" + +[[StaticArraysCore]] +git-tree-sha1 = "66fe9eb253f910fe8cf161953880cfdaef01cdf0" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.0.1" [[Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -830,15 +868,15 @@ version = "1.4.0" [[StatsBase]] deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "8977b17906b0a1cc74ab2e3a05faa16cf08a8291" +git-tree-sha1 = "472d044a1c8df2b062b23f222573ad6837a615ba" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.33.16" +version = "0.33.19" [[StructArrays]] deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"] -git-tree-sha1 = "9abba8f8fb8458e9adf07c8a2377a070674a24f1" +git-tree-sha1 = "ec47fb6069c57f1cee2f67541bf8f23415146de7" uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" -version = "0.6.8" +version = "0.6.11" [[TOML]] deps = ["Dates"] @@ -870,10 +908,16 @@ version = "0.1.1" deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[[TranscodingStreams]] +deps = ["Random", "Test"] +git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.9.6" + [[URIs]] -git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355" +git-tree-sha1 = "e59ecc5a41b000fa94423a578d29290c7266fc10" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.3.0" +version = "1.4.0" [[UUIDs]] deps = ["Random", "SHA"] @@ -1053,12 +1097,22 @@ git-tree-sha1 = "e45044cd873ded54b6a5bac0eb5c971392cf1927" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" version = "1.5.2+0" +[[libaom_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "3a2ea60308f0996d26f1e5354e10c24e9ef905d4" +uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" +version = "3.4.0+0" + [[libass_jll]] deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47" uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" version = "0.15.1+0" +[[libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" + [[libfdk_aac_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55" diff --git a/sample/PlutoUI.jl.jl b/sample/PlutoUI.jl.jl index 1c38006113..c3365c47f3 100644 --- a/sample/PlutoUI.jl.jl +++ b/sample/PlutoUI.jl.jl @@ -1,6 +1,15 @@ ### A Pluto.jl notebook ### # v0.19.9 +#> [frontmatter] +#> author_url = "https://github.com/JuliaPluto" +#> image = "https://user-images.githubusercontent.com/6933510/174067690-50c8128d-748b-4f50-8a76-2ce18166642b.png" +#> order = "3" +#> tags = ["basic", "interactivity", "classic"] +#> author_name = "Pluto.jl" +#> description = "Slider, buttons, dropdowns and more from PlutoUI.jl!" +#> license = "Unlicense" + using Markdown using InteractiveUtils @@ -648,14 +657,21 @@ git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" version = "0.11.4" +[[CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" [[Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +[[FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + [[FixedPointNumbers]] deps = ["Statistics"] git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" @@ -710,7 +726,7 @@ uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" [[LinearAlgebra]] -deps = ["Libdl"] +deps = ["Libdl", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[Logging]] @@ -733,6 +749,10 @@ uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +[[OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" + [[Parsers]] deps = ["Dates"] git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" @@ -758,7 +778,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[Reexport]] @@ -811,6 +831,10 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +[[libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" + [[nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" From 61e5548b1962d5b9508ea0fd23e608ce21152cbe Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 27 Jul 2022 08:13:43 +0200 Subject: [PATCH 402/821] Add base_url option (#2227) * Add base_url option * add tests & improve docs * Fix polling * update test --- src/Configuration.jl | 7 ++++- src/webserver/Static.jl | 38 ++++++++++++++++++++++++--- src/webserver/WebServer.jl | 5 ++-- test/webserver.jl | 54 +++++++++++++++++++++++++++++++++++--- 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/Configuration.jl b/src/Configuration.jl index 5ae65b5143..8177c012a1 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -28,6 +28,7 @@ function __init__() end const ROOT_URL_DEFAULT = nothing +const BASE_URL_DEFAULT = "/" const HOST_DEFAULT = "127.0.0.1" const PORT_DEFAULT = nothing const PORT_HINT_DEFAULT = 1234 @@ -70,10 +71,12 @@ The HTTP server options. See [`SecurityOptions`](@ref) for additional settings. - `simulated_pkg_lag::Real=$SIMULATED_PKG_LAG_DEFAULT` (internal) Extra lag to add to operations done by Pluto's package manager. Will be multiplied by `0.5 + rand()`. - `injected_javascript_data_url::String = "$INJECTED_JAVASCRIPT_DATA_URL_DEFAULT"` (internal) Optional javascript injectables to the front-end. Can be used to customize the editor, but this API is not meant for general use yet. - `on_event::Function = $ON_EVENT_DEFAULT` -- `root_url::Union{Nothing,String} = $ROOT_URL_DEFAULT` This setting is used to specify the root URL of the Pluto server, but this setting is *only* used to customize the launch message (*"Go to http://localhost:1234/ in your browser"*). You can probably ignore this. +- `root_url::Union{Nothing,String} = $ROOT_URL_DEFAULT` This setting is used to specify the root URL of the Pluto server, but this setting is *only* used to customize the launch message (*"Go to http://localhost:1234/ in your browser"*). You can probably ignore this and use `base_url` instead. +- `base_url::String = "$BASE_URL_DEFAULT"` This setting is used to specify the base URL at which the Pluto server will receive requests, it should start be a valid path starting and ending with a '/'. """ @option mutable struct ServerOptions root_url::Union{Nothing,String} = ROOT_URL_DEFAULT + base_url::String = BASE_URL_DEFAULT host::String = HOST_DEFAULT port::Union{Nothing,Integer} = PORT_DEFAULT port_hint::Integer = PORT_HINT_DEFAULT @@ -218,6 +221,7 @@ end function from_flat_kwargs(; root_url::Union{Nothing,String} = ROOT_URL_DEFAULT, + base_url::String = BASE_URL_DEFAULT, host::String = HOST_DEFAULT, port::Union{Nothing,Integer} = PORT_DEFAULT, port_hint::Integer = PORT_HINT_DEFAULT, @@ -252,6 +256,7 @@ function from_flat_kwargs(; ) server = ServerOptions(; root_url, + base_url, host, port, port_hint, diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index e4aecb908e..7fa00c51c2 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -42,6 +42,10 @@ const day = let day = 24hour end +function default_404(req = nothing) + HTTP.Response(404, "Not found!") +end + function asset_response(path; cacheable::Bool=false) if !isfile(path) && !endswith(path, ".html") return asset_response(path * ".html"; cacheable) @@ -55,7 +59,7 @@ function asset_response(path; cacheable::Bool=false) cacheable && push!(response.headers, "Cache-Control" => "public, max-age=$(30day), immutable") response else - HTTP.Response(404, "Not found!") + default_404() end end @@ -115,8 +119,31 @@ function is_authenticated(session::ServerSession, request::HTTP.Request) # that ) || ( kind of looks like Krabs from spongebob end +""" + scoped_router(base_url::String, base_router::HTTP.Router)::HTTP.Router + +Returns a new `HTTP.Router` which delegates all requests to `base_router` but with requests trimmed +so that they seem like they arrived at `/**` instead of `/\$base_url/**`. +""" +function scoped_router(base_url, base_router) + @assert startswith(base_url, '/') "base_url \"$base_url\" should start with a '/'" + @assert endswith(base_url, '/') "base_url \"$base_url\" should end with a '/'" + @assert !occursin('*', base_url) "'*' not allowed in base_url \"$base_url\" " + + function handler(request) + request.target = request.target[length(base_url):end] + return base_router(request) + end + + router = HTTP.Router(base_router._404, base_router._405) + HTTP.register!(router, base_url * "**", handler) + HTTP.register!(router, base_url, handler) + + return router +end + function http_router_for(session::ServerSession) - router = HTTP.Router() + router = HTTP.Router(default_404) security = session.options.security function add_set_secret_cookie!(response::HTTP.Response) @@ -263,7 +290,7 @@ function http_router_for(session::ServerSession) ) end HTTP.register!(router, "GET", "/sample/*", serve_sample) - HTTP.register!(router, "POST", "/sample/*", serve_sample) + HTTP.register!(router, "POST","/sample/*", serve_sample) notebook_from_uri(request) = let uri = HTTP.URI(request.target) @@ -348,5 +375,10 @@ function http_router_for(session::ServerSession) HTTP.register!(router, "GET", "/**", serve_asset) HTTP.register!(router, "GET", "/favicon.ico", create_serve_onefile(project_relative_path(frontend_directory(allow_bundled=false), "img", "favicon.ico"))) + base_url = session.options.server.base_url + if base_url != "/" + return scoped_router(base_url, router) + end + return router end diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 4fef488f25..3a4540a860 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -264,7 +264,7 @@ function run(session::ServerSession) server_running() = try - HTTP.get("http://$(hostIP):$(port)/ping"; status_exception = false, retry = false, connect_timeout = 10, readtimeout = 10).status == 200 + HTTP.get("http://$(hostIP):$(port)$(session.options.server.base_url)ping"; status_exception = false, retry = false, connect_timeout = 10, readtimeout = 10).status == 200 catch false end @@ -333,7 +333,8 @@ function pretty_address(session::ServerSession, hostIP, port) host_str end port_pretty = Int(port) - "http://$(host_pretty):$(port_pretty)/" + base_url = session.options.server.base_url + "http://$(host_pretty):$(port_pretty)$(base_url)" end url_params = Dict{String,String}() diff --git a/test/webserver.jl b/test/webserver.jl index bbf68fd9ef..9afbd3bdf1 100644 --- a/test/webserver.jl +++ b/test/webserver.jl @@ -8,11 +8,55 @@ using Pluto.WorkspaceManager: WorkspaceManager, poll @testset "Web server" begin +@testset "base_url" begin + port = 13433 + host = "localhost" + + n_components = rand(2:6) + base_url = "/" + for _ in 1:n_components + base_url *= String(rand(collect('a':'z') ∪ collect('0':'9'), rand(5:10))) * "/" + end + local_url(suffix) = "http://$host:$port$base_url$suffix" + + @show local_url("favicon.ico") + server_running() = HTTP.get(local_url("favicon.ico")).status == 200 && HTTP.get(local_url("edit")).status == 200 + + # without notebook at startup + options = Pluto.Configuration.from_flat_kwargs(; + port, + launch_browser=false, + workspace_use_distributed=true, + require_secret_for_access=false, + require_secret_for_open_links=false, + base_url, + ) + 🍭 = Pluto.ServerSession(; options) + server_task = @async Pluto.run(🍭) + + # FYI, you should normally use a PlutoEvent for things we do in this test instead of polling! Don't use this as an example. + @test poll(10) do + server_running() + end + + sleep(20) + @test HTTP.get("http://$host:$port/edit"; status_exception=false).status == 404 + + for notebook in values(🍭.notebooks) + SessionActions.shutdown(🍭, notebook; keep_in_session=false) + end + + schedule(server_task, InterruptException(); error=true) + + # wait for the server task to finish + # normally this `wait` would rethrow the above InterruptException, but Pluto.run should catch for InterruptExceptions and not bubble them up. + wait(server_task) +end @testset "Exports" begin port, socket = @inferred Pluto.port_serversocket(Sockets.ip"0.0.0.0", nothing, 5543) - + close(socket) @test 5543 <= port < 5600 @@ -20,9 +64,9 @@ using Pluto.WorkspaceManager: WorkspaceManager, poll host = "localhost" local_url(suffix) = "http://$host:$port/$suffix" - + server_running() = HTTP.get(local_url("favicon.ico")).status == 200 && HTTP.get(local_url("edit")).status == 200 - + # without notebook at startup options = Pluto.Configuration.from_flat_kwargs(; port, launch_browser=false, workspace_use_distributed=true, require_secret_for_access=false, require_secret_for_open_links=false) @@ -64,7 +108,9 @@ using Pluto.WorkspaceManager: WorkspaceManager, poll @test occursin(string(Pluto.PLUTO_VERSION), export_contents) @test occursin("", export_contents) - SessionActions.shutdown(🍭, notebook; keep_in_session=false) + for notebook in values(🍭.notebooks) + SessionActions.shutdown(🍭, notebook; keep_in_session=false) + end schedule(server_task, InterruptException(); error=true) # wait for the server task to finish From ad07c522cd0fecefe38eaad03aa876e56476fc84 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 27 Jul 2022 23:17:14 +0200 Subject: [PATCH 403/821] fix 404 on welcome menu --- frontend/components/welcome/Welcome.js | 5 ++++- frontend/index.html | 4 +++- src/webserver/Static.jl | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/components/welcome/Welcome.js b/frontend/components/welcome/Welcome.js index 8733e92b46..2b58bc3177 100644 --- a/frontend/components/welcome/Welcome.js +++ b/frontend/components/welcome/Welcome.js @@ -21,6 +21,9 @@ import { Featured } from "./Featured.js" * }} */ +// We use a link from the head instead of directing linking "img/logo.svg" because parcel does not bundle preact files +const url_logo_big = document.head.querySelector("link[rel='pluto-logo-big']")?.getAttribute("href") ?? "" + export const Welcome = () => { const [remote_notebooks, set_remote_notebooks] = useState(/** @type {Array} */ ([])) @@ -74,7 +77,7 @@ export const Welcome = () => { return html`
    -

    welcome to

    +

    welcome to

    diff --git a/frontend/index.html b/frontend/index.html index e7fda8c032..9ada585d32 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -20,6 +20,8 @@ + + @@ -70,4 +72,4 @@

    Loading...

    - \ No newline at end of file + diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index 7fa00c51c2..acb767e494 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -369,7 +369,7 @@ function http_router_for(session::ServerSession) function serve_asset(request::HTTP.Request) uri = HTTP.URI(request.target) - filepath = project_relative_path(frontend_directory(), relpath(HTTP.unescapeuri(uri.path), "/")) + filepath = project_relative_path(frontend_directory(), relpath(uri.path, "/")) asset_response(filepath; cacheable=should_cache(filepath)) end HTTP.register!(router, "GET", "/**", serve_asset) From 2158cab91132ae337e68fb0b708dd6cc46305b88 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 27 Jul 2022 23:18:26 +0200 Subject: [PATCH 404/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a950282e57..1a1e46b45f 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.19.10" +version = "0.19.11" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 72555eda2ba121a023243344ffa1dd75d3a17d35 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Wed, 17 Aug 2022 18:00:01 +0200 Subject: [PATCH 405/821] Add macrocall cell_id to LineNumberNodes inside expanded expr (#2241) * Add function to update disabled_cells_dependency * update disabled/skipped dependency inside load_notebook * add a test * append macrocall cell_id inside macro expr * Revert "Add function to update disabled_cells_dependency" This reverts commit f659cf912773ce11d12a6185f75af9c16a827334. * Revert "update disabled/skipped dependency inside load_notebook" This reverts commit 897604e112594412c1ed50189ae4067ec6e3dea8. * Revert "add a test" This reverts commit 5b590621125eb1b58a3464f566dc3a3093b1e111. * Correctly handle jump to link in stacktraces * move LineNumberNode processing to replace_pluto_properties_in_expr * add tests for methods deletion from macros --- Project.toml | 3 +- frontend/components/ErrorMessage.js | 2 +- src/runner/PlutoRunner.jl | 12 +++++++- test/MacroAnalysis.jl | 48 +++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 1a1e46b45f..47ea4bcaf7 100644 --- a/Project.toml +++ b/Project.toml @@ -48,6 +48,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" +Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" [targets] -test = ["DataFrames", "OffsetArrays", "Random", "Sockets", "Test", "TimerOutputs"] +test = ["DataFrames", "OffsetArrays", "Random", "Sockets", "Test", "TimerOutputs", "Memoize"] 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` { diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index d2d7bfc2c6..dc8883fef3 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -181,6 +181,17 @@ else m end replace_pluto_properties_in_expr(other; kwargs...) = other +function replace_pluto_properties_in_expr(ln::LineNumberNode; cell_id, kwargs...) # See https://github.com/fonsp/Pluto.jl/pull/2241 + file = string(ln.file) + out = if endswith(file, string(cell_id)) + # We already have the correct cell_id in this LineNumberNode + ln + else + # We append to the LineNumberNode file #@#==# + cell_id + LineNumberNode(ln.line, Symbol(file * "#@#==#$(cell_id)")) + end + return out +end "Similar to [`replace_pluto_properties_in_expr`](@ref), but just checks for existance and doesn't check for [`GiveMeCellID`](@ref)" has_hook_style_pluto_properties_in_expr(::GiveMeRerunCellFunction) = true @@ -253,7 +264,6 @@ module CantReturnInPluto replace_returns_with_error_in_interpolation(ex) = ex end - function try_macroexpand(mod, cell_uuid, expr) # Remove the precvious cached expansion, so when we error somewhere before we update, # the old one won't linger around and get run accidentally. diff --git a/test/MacroAnalysis.jl b/test/MacroAnalysis.jl index 96c925dc69..396c732b22 100644 --- a/test/MacroAnalysis.jl +++ b/test/MacroAnalysis.jl @@ -1,6 +1,7 @@ using Test import UUIDs import Pluto: PlutoRunner, Notebook, WorkspaceManager, Cell, ServerSession, ClientSession, update_run! +import Memoize: @memoize @testset "Macro analysis" begin 🍭 = ServerSession() @@ -872,4 +873,51 @@ import Pluto: PlutoRunner, Notebook, WorkspaceManager, Cell, ServerSession, Clie @test !occursin("An empty conjugate", bool.output.body) @test occursin("complex conjugate", bool.output.body) end + + @testset "Delete methods from macros" begin + 🍭 = ServerSession() + 🍭.options.evaluation.workspace_use_distributed = false + + fakeclient = ClientSession(:fake, nothing) + 🍭.connected_clients[fakeclient.id] = fakeclient + + notebook = Notebook([ + Cell("using Memoize"), + Cell(""" + macro user_defined() + quote + struct ASD end + custom_func(::ASD) = "ASD" + end |> esc + end + """), + Cell("@user_defined"), + Cell("methods(custom_func)"), + Cell(""" + @memoize function memoized_func(a) + println("Running") + 2a + end + """), + Cell("methods(memoized_func)"), + ]) + cell(idx) = notebook.cells[idx] + + update_run!(🍭, notebook, notebook.cells) + + @test :custom_func ∈ notebook.topology.nodes[cell(3)].funcdefs_without_signatures + @test cell(4) |> noerror + @test :memoized_func ∈ notebook.topology.nodes[cell(5)].funcdefs_without_signatures + @test cell(6) |> noerror + + cell(3).code = "#=$(cell(3).code)=#" + cell(5).code = "#=$(cell(5).code)=#" + + update_run!(🍭, notebook, notebook.cells) + + @test :custom_func ∉ notebook.topology.nodes[cell(3)].funcdefs_without_signatures + @test occursinerror("UndefVarError: custom_func", cell(4)) + @test :memoized_func ∉ notebook.topology.nodes[cell(5)].funcdefs_without_signatures + @test occursinerror("UndefVarError: memoized_func", cell(6)) + end end From 70a80786fe9f5c3fe1c6c1368eb937a7e0288305 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 4 Sep 2022 22:29:00 +0200 Subject: [PATCH 406/821] :broom: Code cleanup (#2260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cleanup Run.jl and move macro analysis to its own file * 🧹 Cleanup code --- src/Pluto.jl | 1 + src/evaluation/MacroAnalysis.jl | 203 +++++++++++++++++++++++++ src/evaluation/Run.jl | 256 ++++---------------------------- src/webserver/Dynamic.jl | 20 +-- src/webserver/Firebasey.jl | 11 +- 5 files changed, 240 insertions(+), 251 deletions(-) create mode 100644 src/evaluation/MacroAnalysis.jl diff --git a/src/Pluto.jl b/src/Pluto.jl index 835fea5eb8..88e0cafce5 100644 --- a/src/Pluto.jl +++ b/src/Pluto.jl @@ -71,6 +71,7 @@ include("./analysis/DependencyCache.jl") include("./analysis/MoreAnalysis.jl") include("./evaluation/WorkspaceManager.jl") +include("./evaluation/MacroAnalysis.jl") include("./packages/Packages.jl") include("./packages/PkgUtils.jl") include("./evaluation/Run.jl") diff --git a/src/evaluation/MacroAnalysis.jl b/src/evaluation/MacroAnalysis.jl new file mode 100644 index 0000000000..4ef8760978 --- /dev/null +++ b/src/evaluation/MacroAnalysis.jl @@ -0,0 +1,203 @@ +# Macro Analysis & Topology Resolution (see https://github.com/fonsp/Pluto.jl/pull/1032) + +import .WorkspaceManager: macroexpand_in_workspace + +const lazymap = Base.Generator + +function defined_variables(topology::NotebookTopology, cells) + lazymap(cells) do cell + topology.nodes[cell].definitions + end +end + +function defined_functions(topology::NotebookTopology, cells) + lazymap(cells) do cell + ((cell.cell_id, namesig.name) for namesig in topology.nodes[cell].funcdefs_with_signatures) + end +end + +is_macro_identifier(symbol::Symbol) = startswith(string(symbol), "@") + +function with_new_soft_definitions(topology::NotebookTopology, cell::Cell, soft_definitions) + old_node = topology.nodes[cell] + new_node = union!(ReactiveNode(), old_node, ReactiveNode(soft_definitions=soft_definitions)) + NotebookTopology( + codes=topology.codes, + nodes=merge(topology.nodes, Dict(cell => new_node)), + unresolved_cells=topology.unresolved_cells, + cell_order=topology.cell_order, + ) +end + +collect_implicit_usings(topology::NotebookTopology, cell::Cell) = ExpressionExplorer.collect_implicit_usings(topology.codes[cell].module_usings_imports) + +function cells_with_deleted_macros(old_topology::NotebookTopology, new_topology::NotebookTopology) + old_macros = mapreduce(c -> defined_macros(old_topology, c), union!, all_cells(old_topology); init=Set{Symbol}()) + new_macros = mapreduce(c -> defined_macros(new_topology, c), union!, all_cells(new_topology); init=Set{Symbol}()) + removed_macros = setdiff(old_macros, new_macros) + + where_referenced(old_topology, removed_macros) +end + +"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) = union!(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) + cell_code = topology.codes[cell] + cell_node = topology.nodes[cell] + macros = defined_macros(cell_node) + + !isempty(cell_code.module_usings_imports.usings) || + (!isempty(macros) && any(calls -> !disjoint(calls, macros), topology.nodes[c].macrocalls for c in topology.unresolved_cells)) +end + +# Sorry couldn't help myself - DRAL +abstract type Result end +struct Success <: Result + result +end +struct Failure <: Result + error +end +struct Skipped <: Result end + +"""We still have 'unresolved' macrocalls, use the current and maybe previous workspace to do macro-expansions. + +You can optionally specify the roots for the current reactive run. If a cell macro contains only macros that will +be re-defined during this reactive run, we don't expand yet and expect the `can_help_resolve_cells` function above +to be true for the cell defining the macro, triggering a new topology resolution without needing to fallback to the +previous workspace. +""" +function resolve_topology( + session::ServerSession, + notebook::Notebook, + unresolved_topology::NotebookTopology, + old_workspace_name::Symbol; + current_roots::Vector{Cell}=Cell[], +)::NotebookTopology + + + sn = (session, notebook) + + function macroexpand_cell(cell) + try_macroexpand(module_name::Union{Nothing,Symbol}=nothing) = begin + success, result = macroexpand_in_workspace(sn, unresolved_topology.codes[cell].parsedcode, cell.cell_id, module_name) + if success + Success(result) + else + Failure(result) + end + end + + result = try_macroexpand() + if result isa Success + result + else + if (result.error isa LoadError && result.error.error isa UndefVarError) || result.error isa UndefVarError + try_macroexpand(old_workspace_name) + else + result + end + end + end + + function analyze_macrocell(cell::Cell) + if unresolved_topology.nodes[cell].macrocalls ⊆ ExpressionExplorer.can_macroexpand + return Skipped() + end + + result = macroexpand_cell(cell) + if result isa Success + (expr, computer_id) = result.result + expanded_node = ExpressionExplorer.try_compute_symbolreferences(expr) |> ReactiveNode + function_wrapped = ExpressionExplorer.can_be_function_wrapped(expr) + Success((expanded_node, function_wrapped, computer_id)) + else + result + end + end + + run_defined_macros = mapreduce(c -> defined_macros(unresolved_topology, c), union!, current_roots; init=Set{Symbol}()) + + # create new node & new codes for macrocalled cells + new_nodes = Dict{Cell,ReactiveNode}() + new_codes = Dict{Cell,ExprAnalysisCache}() + still_unresolved_nodes = Set{Cell}() + + for cell in unresolved_topology.unresolved_cells + if unresolved_topology.nodes[cell].macrocalls ⊆ run_defined_macros + # Do not try to expand if a newer version of the macro is also scheduled to run in the + # current run. The recursive reactive runs will take care of it. + push!(still_unresolved_nodes, cell) + continue + end + + result = try + if will_run_code(notebook) + analyze_macrocell(cell) + else + Failure(ErrorException("shutdown")) + end + catch error + @error "Macro call expansion failed with a non-macroexpand error" error + Failure(error) + end + if result isa Success + (new_node, function_wrapped, forced_expr_id) = result.result + union!(new_node.macrocalls, unresolved_topology.nodes[cell].macrocalls) + union!(new_node.references, new_node.macrocalls) + new_nodes[cell] = new_node + + # set function_wrapped to the function wrapped analysis of the expanded expression. + new_codes[cell] = ExprAnalysisCache(unresolved_topology.codes[cell]; forced_expr_id, function_wrapped) + elseif result isa Skipped + # Skipped because it has already been resolved during ExpressionExplorer. + else + @debug "Could not resolve" result cell.code + push!(still_unresolved_nodes, cell) + end + end + + all_nodes = merge(unresolved_topology.nodes, new_nodes) + all_codes = merge(unresolved_topology.codes, new_codes) + + new_unresolved_cells = if length(still_unresolved_nodes) == length(unresolved_topology.unresolved_cells) + # then they must equal, and we can skip creating a new one to preserve identity: + unresolved_topology.unresolved_cells + else + ImmutableSet(still_unresolved_nodes; skip_copy=true) + end + + NotebookTopology( + nodes=all_nodes, + codes=all_codes, + unresolved_cells=new_unresolved_cells, + cell_order=unresolved_topology.cell_order, + ) +end + +"""Tries to add information about macro calls without running any code, using knowledge about common macros. +So, the resulting reactive nodes may not be absolutely accurate. If you can run code in a session, use `resolve_topology` instead. +""" +function static_macroexpand(topology::NotebookTopology, cell::Cell) + new_node = ExpressionExplorer.maybe_macroexpand(topology.codes[cell].parsedcode; recursive=true) |> + ExpressionExplorer.try_compute_symbolreferences |> ReactiveNode + union!(new_node.macrocalls, topology.nodes[cell].macrocalls) + + new_node +end + +"The same as `resolve_topology` but does not require custom code execution, only works with a few `Base` & `PlutoRunner` macros" +function static_resolve_topology(topology::NotebookTopology) + new_nodes = Dict{Cell,ReactiveNode}(cell => static_macroexpand(topology, cell) for cell in topology.unresolved_cells) + all_nodes = merge(topology.nodes, new_nodes) + + NotebookTopology( + nodes=all_nodes, + codes=topology.codes, + unresolved_cells=topology.unresolved_cells, + cell_order=topology.cell_order, + ) +end diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 6349ebf813..76e33f7e4c 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -1,28 +1,9 @@ -import REPL:ends_with_semicolon +import REPL: ends_with_semicolon import .Configuration -import .ExpressionExplorer: FunctionNameSignaturePair, is_joined_funcname, UsingsImports, external_package_names -import .WorkspaceManager: macroexpand_in_workspace -import .MoreAnalysis: find_bound_variables +import .ExpressionExplorer: is_joined_funcname Base.push!(x::Set{Cell}) = x -""" -```julia -set_bond_value_pairs!(session::ServerSession, notebook::Notebook, bond_value_pairs::Vector{Tuple{Symbol, Any}}) -``` - -Given a list of tuples of the form `(bound variable name, (untransformed) value)`, assign each (transformed) value to the corresponding global bound variable in the notebook workspace. - -`bond_value_pairs` can also be an iterator. -""" -function set_bond_value_pairs!(session::ServerSession, notebook::Notebook, bond_value_pairs) - for (bound_sym, new_value) in bond_value_pairs - WorkspaceManager.eval_in_workspace((session, notebook), :($(bound_sym) = Main.PlutoRunner.transform_bond_value($(QuoteNode(bound_sym)), $(new_value)))) - end -end - -const _empty_bond_value_pairs = zip(Symbol[],Any[]) - """ Run given cells and all the cells that depend on them, based on the topology information before and after the changes. """ @@ -34,7 +15,7 @@ function run_reactive!( roots::Vector{Cell}; deletion_hook::Function = WorkspaceManager.move_vars, user_requested_run::Bool = true, - bond_value_pairs=_empty_bond_value_pairs, + bond_value_pairs=zip(Symbol[],Any[]), )::TopologicalOrder withtoken(notebook.executetoken) do run_reactive_core!( @@ -45,7 +26,7 @@ function run_reactive!( roots; deletion_hook, user_requested_run, - bond_value_pairs + bond_value_pairs, ) end end @@ -65,7 +46,7 @@ function run_reactive_core!( deletion_hook::Function = WorkspaceManager.move_vars, user_requested_run::Bool = true, already_run::Vector{Cell} = Cell[], - bond_value_pairs = _empty_bond_value_pairs, + bond_value_pairs=zip(Symbol[],Any[]), )::TopologicalOrder @assert !isready(notebook.executetoken) "run_reactive_core!() was called with a free notebook.executetoken." @assert will_run_code(notebook) @@ -181,16 +162,16 @@ function run_reactive_core!( (session, notebook), cell, new_topology.nodes[cell], new_topology.codes[cell]; user_requested_run = (user_requested_run && cell ∈ roots), - capture_stdout = session.options.evaluation.capture_stdout, + capture_stdout = session.options.evaluation.capture_stdout, ) any_interrupted |= run.interrupted - # Support one bond defining another when setting both simultaneously in PlutoSliderServer - # https://github.com/fonsp/Pluto.jl/issues/1695 + # Support one bond defining another when setting both simultaneously in PlutoSliderServer + # https://github.com/fonsp/Pluto.jl/issues/1695 - # set the redefined bound variables to their original value from the request - defs = notebook.topology.nodes[cell].definitions - set_bond_value_pairs!(session, notebook, Iterators.filter(((sym,val),) -> sym ∈ defs, bond_value_pairs)) + # set the redefined bound variables to their original value from the request + defs = notebook.topology.nodes[cell].definitions + set_bond_value_pairs!(session, notebook, Iterators.filter(((sym,val),) -> sym ∈ defs, bond_value_pairs)) end cell.running = false @@ -233,6 +214,19 @@ function run_reactive_core!( return new_order end +""" +```julia +set_bond_value_pairs!(session::ServerSession, notebook::Notebook, bond_value_pairs::Vector{Tuple{Symbol, Any}}) +``` +Given a list of tuples of the form `(bound variable name, (untransformed) value)`, assign each (transformed) value to the corresponding global bound variable in the notebook workspace. +`bond_value_pairs` can also be an iterator. +""" +function set_bond_value_pairs!(session::ServerSession, notebook::Notebook, bond_value_pairs) + for (bound_sym, new_value) in bond_value_pairs + WorkspaceManager.eval_in_workspace((session, notebook), :($(bound_sym) = Main.PlutoRunner.transform_bond_value($(QuoteNode(bound_sym)), $(new_value)))) + end +end + run_reactive_async!(session::ServerSession, notebook::Notebook, to_run::Vector{Cell}; kwargs...) = run_reactive_async!(session, notebook, notebook.topology, notebook.topology, to_run; kwargs...) function run_reactive_async!(session::ServerSession, notebook::Notebook, old::NotebookTopology, new::NotebookTopology, to_run::Vector{Cell}; run_async::Bool=true, kwargs...)::Union{Task,TopologicalOrder} @@ -250,20 +244,6 @@ function maybe_async(f::Function, async::Bool) end end -const lazymap = Base.Generator - -function defined_variables(topology::NotebookTopology, cells) - lazymap(cells) do cell - topology.nodes[cell].definitions - end -end - -function defined_functions(topology::NotebookTopology, cells) - lazymap(cells) do cell - ((cell.cell_id, namesig.name) for namesig in topology.nodes[cell].funcdefs_with_signatures) - end -end - "Run a single cell non-reactively, set its output, return run information." function run_single!( session_notebook::Union{Tuple{ServerSession,Notebook},WorkspaceManager.Workspace}, @@ -332,192 +312,6 @@ end will_run_code(notebook::Notebook) = notebook.process_status != ProcessStatus.no_process && notebook.process_status != ProcessStatus.waiting_to_restart -is_macro_identifier(symbol::Symbol) = startswith(string(symbol), "@") - -function with_new_soft_definitions(topology::NotebookTopology, cell::Cell, soft_definitions) - old_node = topology.nodes[cell] - new_node = union!(ReactiveNode(), old_node, ReactiveNode(soft_definitions=soft_definitions)) - NotebookTopology( - codes=topology.codes, - nodes=merge(topology.nodes, Dict(cell => new_node)), - unresolved_cells=topology.unresolved_cells, - cell_order=topology.cell_order, - ) -end - -collect_implicit_usings(topology::NotebookTopology, cell::Cell) = ExpressionExplorer.collect_implicit_usings(topology.codes[cell].module_usings_imports) - -function cells_with_deleted_macros(old_topology::NotebookTopology, new_topology::NotebookTopology) - old_macros = mapreduce(c -> defined_macros(old_topology, c), union!, all_cells(old_topology); init=Set{Symbol}()) - new_macros = mapreduce(c -> defined_macros(new_topology, c), union!, all_cells(new_topology); init=Set{Symbol}()) - removed_macros = setdiff(old_macros, new_macros) - - where_referenced(old_topology, removed_macros) -end - -"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) = union!(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) - cell_code = topology.codes[cell] - cell_node = topology.nodes[cell] - macros = defined_macros(cell_node) - - !isempty(cell_code.module_usings_imports.usings) || - (!isempty(macros) && any(calls -> !disjoint(calls, macros), topology.nodes[c].macrocalls for c in topology.unresolved_cells)) -end - -# Sorry couldn't help myself - DRAL -abstract type Result end -struct Success <: Result - result -end -struct Failure <: Result - error -end -struct Skipped <: Result end - -"""We still have 'unresolved' macrocalls, use the current and maybe previous workspace to do macro-expansions. - -You can optionally specify the roots for the current reactive run. If a cell macro contains only macros that will -be re-defined during this reactive run, we don't expand yet and expect the `can_help_resolve_cells` function above -to be true for the cell defining the macro, triggering a new topology resolution without needing to fallback to the -previous workspace. -""" -function resolve_topology( - session::ServerSession, - notebook::Notebook, - unresolved_topology::NotebookTopology, - old_workspace_name::Symbol; - current_roots::Vector{Cell}=Cell[], -)::NotebookTopology - - - sn = (session, notebook) - - function macroexpand_cell(cell) - try_macroexpand(module_name::Union{Nothing,Symbol}=nothing) = begin - success, result = macroexpand_in_workspace(sn, unresolved_topology.codes[cell].parsedcode, cell.cell_id, module_name) - if success - Success(result) - else - Failure(result) - end - end - - result = try_macroexpand() - if result isa Success - result - else - if (result.error isa LoadError && result.error.error isa UndefVarError) || result.error isa UndefVarError - try_macroexpand(old_workspace_name) - else - result - end - end - end - - function analyze_macrocell(cell::Cell) - if unresolved_topology.nodes[cell].macrocalls ⊆ ExpressionExplorer.can_macroexpand - return Skipped() - end - - result = macroexpand_cell(cell) - if result isa Success - (expr, computer_id) = result.result - expanded_node = ExpressionExplorer.try_compute_symbolreferences(expr) |> ReactiveNode - function_wrapped = ExpressionExplorer.can_be_function_wrapped(expr) - Success((expanded_node, function_wrapped, computer_id)) - else - result - end - end - - run_defined_macros = mapreduce(c -> defined_macros(unresolved_topology, c), union!, current_roots; init=Set{Symbol}()) - - # create new node & new codes for macrocalled cells - new_nodes = Dict{Cell,ReactiveNode}() - new_codes = Dict{Cell,ExprAnalysisCache}() - still_unresolved_nodes = Set{Cell}() - - for cell in unresolved_topology.unresolved_cells - if unresolved_topology.nodes[cell].macrocalls ⊆ run_defined_macros - # Do not try to expand if a newer version of the macro is also scheduled to run in the - # current run. The recursive reactive runs will take care of it. - push!(still_unresolved_nodes, cell) - continue - end - - result = try - if will_run_code(notebook) - analyze_macrocell(cell) - else - Failure(ErrorException("shutdown")) - end - catch error - @error "Macro call expansion failed with a non-macroexpand error" error - Failure(error) - end - if result isa Success - (new_node, function_wrapped, forced_expr_id) = result.result - union!(new_node.macrocalls, unresolved_topology.nodes[cell].macrocalls) - union!(new_node.references, new_node.macrocalls) - new_nodes[cell] = new_node - - # set function_wrapped to the function wrapped analysis of the expanded expression. - new_codes[cell] = ExprAnalysisCache(unresolved_topology.codes[cell]; forced_expr_id, function_wrapped) - elseif result isa Skipped - # Skipped because it has already been resolved during ExpressionExplorer. - else - @debug "Could not resolve" result cell.code - push!(still_unresolved_nodes, cell) - end - end - - all_nodes = merge(unresolved_topology.nodes, new_nodes) - all_codes = merge(unresolved_topology.codes, new_codes) - - new_unresolved_cells = if length(still_unresolved_nodes) == length(unresolved_topology.unresolved_cells) - # then they must equal, and we can skip creating a new one to preserve identity: - unresolved_topology.unresolved_cells - else - ImmutableSet(still_unresolved_nodes; skip_copy=true) - end - - NotebookTopology( - nodes=all_nodes, - codes=all_codes, - unresolved_cells=new_unresolved_cells, - cell_order=unresolved_topology.cell_order, - ) -end - -"""Tries to add information about macro calls without running any code, using knowledge about common macros. -So, the resulting reactive nodes may not be absolutely accurate. If you can run code in a session, use `resolve_topology` instead. -""" -function static_macroexpand(topology::NotebookTopology, cell::Cell) - new_node = ExpressionExplorer.maybe_macroexpand(topology.codes[cell].parsedcode; recursive=true) |> - ExpressionExplorer.try_compute_symbolreferences |> ReactiveNode - union!(new_node.macrocalls, topology.nodes[cell].macrocalls) - - new_node -end - -"The same as `resolve_topology` but does not require custom code execution, only works with a few `Base` & `PlutoRunner` macros" -function static_resolve_topology(topology::NotebookTopology) - new_nodes = Dict{Cell,ReactiveNode}(cell => static_macroexpand(topology, cell) for cell in topology.unresolved_cells) - all_nodes = merge(topology.nodes, new_nodes) - - NotebookTopology( - nodes=all_nodes, - codes=topology.codes, - unresolved_cells=topology.unresolved_cells, - cell_order=topology.cell_order, - ) -end - ### # CONVENIENCE FUNCTIONS ### @@ -711,4 +505,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 \ No newline at end of file +end diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index e8bf3dbfcd..61f775e633 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -310,7 +310,7 @@ responses[:update_notebook] = function response_update_notebook(🙋::ClientRequ mutator(matches..., rest...; request=🙋, patch) end - push!(changes, current_changes...) + union!(changes, current_changes) end # We put a flag to check whether any patch changes the skip_as_script metadata. This is to eventually trigger a notebook updated if no reactive_run is part of this update @@ -368,19 +368,11 @@ function trigger_resolver(resolvers::Dict, path, values=[]) throw(BoundsError("resolver path ends at Dict with keys $(keys(resolver))")) end - segment = first(path) - rest = path[firstindex(path)+1:end] - for (key, resolver) in resolvers - if key isa Wildcard - continue - end - if key == segment - return trigger_resolver(resolver, rest, values) - end - end - - if haskey(resolvers, Wildcard()) - return trigger_resolver(resolvers[Wildcard()], rest, (values..., segment)) + segment, rest... = path + if haskey(resolvers, segment) + trigger_resolver(resolvers[segment], rest, values) + elseif haskey(resolvers, Wildcard()) + trigger_resolver(resolvers[Wildcard()], rest, (values..., segment)) else throw(BoundsError("failed to match path $(path), possible keys $(keys(resolver))")) end diff --git a/src/webserver/Firebasey.jl b/src/webserver/Firebasey.jl index 863ef08118..ab59d3ffd6 100644 --- a/src/webserver/Firebasey.jl +++ b/src/webserver/Firebasey.jl @@ -95,7 +95,7 @@ md""" abstract type JSONPatch end # ╔═╡ bd0d46bb-3e58-4522-bae0-83eb799196c4 -PatchPath = Vector +const PatchPath = Vector # ╔═╡ db2d8a3e-2de1-11eb-02b8-9ffbfaeff61c struct AddPatch <: JSONPatch @@ -679,8 +679,7 @@ function getpath(value, path) return value end - current = path[firstindex(path)] - rest = path[firstindex(path) + 1:end] + current, rest... = path if value isa AbstractDict key = force_convert_key(value, current) getpath(getindex(value, key), rest) @@ -709,7 +708,7 @@ function applypatch!(value, patch::AddPatch) throw("Impossible") else last = patch.path[end] - rest = patch.path[firstindex(patch.path):end - 1] + rest = patch.path[begin:end - 1] subvalue = getpath(value, rest) if subvalue isa AbstractDict key = force_convert_key(subvalue, last) @@ -746,7 +745,7 @@ function applypatch!(value, patch::ReplacePatch) throw("Impossible") else last = patch.path[end] - rest = patch.path[firstindex(patch.path):end - 1] + rest = patch.path[begin:end - 1] subvalue = getpath(value, rest) if subvalue isa AbstractDict key = force_convert_key(subvalue, last) @@ -783,7 +782,7 @@ function applypatch!(value, patch::RemovePatch) throw("Impossible") else last = patch.path[end] - rest = patch.path[firstindex(patch.path):end - 1] + rest = patch.path[begin:end - 1] subvalue = getpath(value, rest) if subvalue isa AbstractDict key = force_convert_key(subvalue, last) From 80c36a222d5722ac2b54cf3571c63cd3abb6d45f Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 5 Sep 2022 16:41:45 +0200 Subject: [PATCH 407/821] Fix #2215 (#2261) --- frontend/components/Editor.js | 46 +++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 3cd6ed7780..30ac6accb6 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -322,7 +322,7 @@ export class Editor extends Component { this.setStatePromise = (fn) => new Promise((r) => this.setState(fn, r)) // these are things that can be done to the local notebook - this.actions = { + this.real_actions = { get_notebook: () => this?.state?.notebook || {}, send: (message_type, ...args) => this.client.send(message_type, ...args), get_published_object: (objectid) => this.state.notebook.published_objects[objectid], @@ -622,6 +622,7 @@ export class Editor extends Component { return message.versions }, } + this.actions = { ...this.real_actions } const apply_notebook_patches = (patches, /** @type {NotebookData?} */ old_state = null, get_reverse_patches = false) => new Promise((resolve) => { @@ -822,26 +823,35 @@ patch: ${JSON.stringify( connect_metadata: { notebook_id: this.state.notebook.notebook_id }, }).then(on_establish_connection) - this.real_actions = this.actions - this.fake_actions = - launch_params.slider_server_url != null - ? slider_server_actions({ - setStatePromise: this.setStatePromise, - actions: this.actions, - launch_params: launch_params, - apply_notebook_patches, - get_original_state: () => this.props.initial_notebook_state, - get_current_state: () => this.state.notebook, - }) - : nothing_actions({ - actions: this.actions, - }) - this.on_disable_ui = () => { set_disable_ui_css(this.state.disable_ui) - //@ts-ignore - this.actions = this.state.disable_ui || (launch_params.slider_server_url != null && !this.state.connected) ? this.fake_actions : this.real_actions //heyo + // Pluto has three modes of operation: + // 1. (normal) Connected to a Pluto notebook. + // 2. Static HTML with PlutoSliderServer. All edits are ignored, but bond changes are processes by the PlutoSliderServer. + // 3. Static HTML without PlutoSliderServer. All interactions are ignored. + // + // To easily support all three with minimal changes to the source code, we sneakily swap out the `this.actions` object (`pluto_actions` in other source files) with a different one: + Object.assign( + this.actions, + // if we have no pluto server... + this.state.disable_ui || (launch_params.slider_server_url != null && !this.state.connected) + ? // then use a modified set of actions + launch_params.slider_server_url != null + ? slider_server_actions({ + setStatePromise: this.setStatePromise, + actions: this.actions, + launch_params: launch_params, + apply_notebook_patches, + get_original_state: () => this.props.initial_notebook_state, + get_current_state: () => this.state.notebook, + }) + : nothing_actions({ + actions: this.actions, + }) + : // otherwise, use the real actions + this.real_actions + ) } this.on_disable_ui() From 8b58ce52431c47c02dbec2e64a024754b450354a Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Mon, 5 Sep 2022 20:20:01 +0200 Subject: [PATCH 408/821] logging tweaks --- src/notebook/Export.jl | 2 +- src/webserver/WebServer.jl | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/notebook/Export.jl b/src/notebook/Export.jl index 445179bee7..3c0f56aac6 100644 --- a/src/notebook/Export.jl +++ b/src/notebook/Export.jl @@ -30,7 +30,7 @@ function cdnified_editor_html(; URIs.resolvereference("https://cdn.jsdelivr.net/gh/fonsp/Pluto.jl@$(string(PLUTO_VERSION))/frontend-dist/", url) |> string end catch e - @warn "Could not use bundled CDN version of editor.html. You should only see this message if you are using a fork of Pluto." exception=(e,catch_backtrace()) + @warn "Could not use bundled CDN version of editor.html. You should only see this message if you are using a fork of Pluto." exception=(e,catch_backtrace()) maxlog=1 nothing end end, diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 3a4540a860..7f2a04640e 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -177,21 +177,27 @@ function run(session::ServerSession) # This stream contains data received over the WebSocket. # It is formatted and MsgPack-encoded by send(...) in PlutoConnection.js local parentbody = nothing + local did_read = false try parentbody = unpack(message) - + let lag = session.options.server.simulated_lag (lag > 0) && sleep(lag * (0.5 + rand())) # sleep(0) would yield to the process manager which we dont want end - + + did_read = true process_ws_message(session, parentbody, clientstream) catch ex if ex isa InterruptException || ex isa HTTP.WebSockets.WebSocketError || ex isa EOFError # that's fine! else - bt = stacktrace(catch_backtrace()) - @warn "Reading WebSocket client stream failed for unknown reason:" parentbody exception = (ex, bt) + bt = catch_backtrace() + if did_read + @warn "Processing message failed for unknown reason:" parentbody exception = (ex, bt) + else + @warn "Reading WebSocket client stream failed for unknown reason:" parentbody exception = (ex, bt) + end end end end From 183dc3ad1004ab76272e630b6d6e3e549d763794 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 6 Sep 2022 11:46:04 +0200 Subject: [PATCH 409/821] Improve ARM support for `roughly_the_number_of_physical_cpu_cores` (#2263) --- src/Configuration.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Configuration.jl b/src/Configuration.jl index 8177c012a1..5e12d5b06f 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -159,12 +159,20 @@ const HISTORY_FILE_DEFAULT = "no" function roughly_the_number_of_physical_cpu_cores() # https://gist.github.com/fonsp/738fe244719cae820245aa479e7b4a8d - if Sys.CPU_THREADS == 1 + threads = Sys.CPU_THREADS + num_threads_is_maybe_doubled_for_marketing = Sys.ARCH === :x86_64 + + if threads == 1 1 - elseif Sys.CPU_THREADS == 2 || Sys.CPU_THREADS == 3 + elseif threads == 2 || threads == 3 2 + elseif num_threads_is_maybe_doubled_for_marketing + # This includes: + # - intel hyperthreading + # - Apple ARM efficiency cores included in the count (when running the x86 executable) + threads ÷ 2 else - Sys.CPU_THREADS ÷ 2 + threads end end From c92a8e1df919fa01dfc0a5e00f9bbbe60ac99af8 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 6 Sep 2022 19:34:45 +0200 Subject: [PATCH 410/821] Keep track of Pkg install times (#2265) --- frontend/components/Editor.js | 7 +++--- src/evaluation/Run.jl | 1 + src/notebook/Notebook.jl | 1 + src/packages/Packages.jl | 45 ++++++++++++++++++++++------------- src/packages/PkgUtils.jl | 2 +- src/webserver/Dynamic.jl | 1 + test/packages/Basic.jl | 8 +++++++ 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 30ac6accb6..606023e4bd 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -142,10 +142,10 @@ const first_true_key = (obj) => { * queued: boolean, * running: boolean, * errored: boolean, - * runtime: ?number, + * runtime: number?, * downstream_cells_map: { string: [string]}, * upstream_cells_map: { string: [string]}, - * precedence_heuristic: ?number, + * precedence_heuristic: number?, * depends_on_disabled_cells: boolean, * depends_on_skipped_cells: boolean, * output: { @@ -153,7 +153,7 @@ const first_true_key = (obj) => { * persist_js_state: boolean, * last_run_timestamp: number, * mime: string, - * rootassignee: ?string, + * rootassignee: string?, * has_pluto_hook_features: boolean, * }, * logs: Array, @@ -177,6 +177,7 @@ const first_true_key = (obj) => { * restart_required_msg: string?, * installed_versions: { [pkg_name: string]: string }, * terminal_outputs: { [pkg_name: string]: string }, + * install_time_ns: number?, * busy_packages: string[], * instantiated: boolean, * }} diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 76e33f7e4c..d1b56d5fac 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -479,6 +479,7 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...) if (notebook.nbpkg_ctx isa Nothing) != (just_loaded.nbpkg_ctx isa Nothing) @info "nbpkg status changed, overriding..." notebook.nbpkg_ctx = just_loaded.nbpkg_ctx + notebook.nbpkg_install_time_ns = just_loaded.nbpkg_install_time_ns else @info "Old new project" PkgCompat.read_project_file(notebook) PkgCompat.read_project_file(just_loaded) @info "Old new manifest" PkgCompat.read_manifest_file(notebook) PkgCompat.read_manifest_file(just_loaded) diff --git a/src/notebook/Notebook.jl b/src/notebook/Notebook.jl index 473d480773..3b7c4e6806 100644 --- a/src/notebook/Notebook.jl +++ b/src/notebook/Notebook.jl @@ -50,6 +50,7 @@ Base.@kwdef mutable struct Notebook nbpkg_restart_recommended_msg::Union{Nothing,String}=nothing nbpkg_restart_required_msg::Union{Nothing,String}=nothing nbpkg_terminal_outputs::Dict{String,String}=Dict{String,String}() + nbpkg_install_time_ns::Union{Nothing,UInt64}=zero(UInt64) nbpkg_busy_packages::Vector{String}=String[] nbpkg_installed_versions_cache::Dict{String,String}=Dict{String,String}() diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index 53e8194555..bbd91b4762 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -57,13 +57,14 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new use_plutopkg_new = use_plutopkg(new_topology) if !use_plutopkg_old && use_plutopkg_new - @debug "Started using PlutoPkg!! HELLO reproducibility!" + @debug "PlutoPkg: Started using PlutoPkg!! HELLO reproducibility!" notebook.path 👺 = true notebook.nbpkg_ctx = PkgCompat.create_empty_ctx() + notebook.nbpkg_install_time_ns = 0 end if use_plutopkg_old && !use_plutopkg_new - @debug "Stopped using PlutoPkg 💔😟😢" + @debug "PlutoPkg: Stopped using PlutoPkg 💔😟😢" notebook.path no_packages_loaded_yet = ( notebook.nbpkg_restart_required_msg === nothing && @@ -72,8 +73,10 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new ) 👺 = !no_packages_loaded_yet notebook.nbpkg_ctx = nothing + notebook.nbpkg_install_time_ns = nothing end if !use_plutopkg_new + notebook.nbpkg_install_time_ns = nothing notebook.nbpkg_ctx_instantiated = true end @@ -123,12 +126,14 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new end end end - + + to_add = filter(PkgCompat.package_exists, added) to_remove = filter(removed) do p haskey(PkgCompat.project(notebook.nbpkg_ctx).dependencies, p) end + @debug "PlutoPkg:" notebook.path to_add to_remove + 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() @@ -138,6 +143,7 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new for p in to_remove ]) + notebook.nbpkg_install_time_ns = nothing # we lose our estimate of install time # 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() @@ -147,13 +153,10 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new # 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 + start_time = time_ns() 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. @@ -190,15 +193,16 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new println(iolistener.buffer, "\e[32m\e[1mLoading\e[22m\e[39m packages...") end end - - @debug "PlutoPkg done" + notebook.nbpkg_install_time_ns = notebook.nbpkg_install_time_ns === nothing ? nothing : (notebook.nbpkg_install_time_ns + (time_ns() - start_time)) + @debug "PlutoPkg: done" notebook.path end should_instantiate = !notebook.nbpkg_ctx_instantiated || !isempty(to_add) || !isempty(to_remove) if should_instantiate + start_time = time_ns() startlistening(iolistener) PkgCompat.withio(notebook.nbpkg_ctx, IOContext(iolistener.buffer, :color => true)) do - @debug "Instantiating" + @debug "PlutoPkg: Instantiating" notebook.path # 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. @@ -213,6 +217,7 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new @assert LOAD_PATH[1] == env_dir popfirst!(LOAD_PATH) end + notebook.nbpkg_install_time_ns = notebook.nbpkg_install_time_ns === nothing ? nothing : (notebook.nbpkg_install_time_ns + (time_ns() - start_time)) notebook.nbpkg_ctx_instantiated = true end @@ -269,15 +274,15 @@ function sync_nbpkg(session, notebook, old_topology::NotebookTopology, new_topol end if pkg_result.did_something - @debug "PlutoPkg: success!" pkg_result + @debug "PlutoPkg: success!" notebook.path pkg_result if pkg_result.restart_recommended notebook.nbpkg_restart_recommended_msg = "Yes, something changed during regular sync." - @debug "PlutoPkg: Notebook restart recommended" notebook.nbpkg_restart_recommended_msg + @debug "PlutoPkg: Notebook restart recommended" notebook.path notebook.nbpkg_restart_recommended_msg end if pkg_result.restart_required notebook.nbpkg_restart_required_msg = "Yes, something changed during regular sync." - @debug "PlutoPkg: Notebook restart REQUIRED" notebook.nbpkg_restart_required_msg + @debug "PlutoPkg: Notebook restart REQUIRED" notebook.path notebook.nbpkg_restart_required_msg end notebook.nbpkg_busy_packages = String[] @@ -306,6 +311,7 @@ function sync_nbpkg(session, notebook, old_topology::NotebookTopology, new_topol # Clear the embedded Project and Manifest and require a restart from the user. reset_nbpkg(notebook, new_topology; keep_project=false, save=save) notebook.nbpkg_restart_required_msg = "Yes, because sync_nbpkg_core failed. \n\n$(error_text)" + notebook.nbpkg_install_time_ns = nothing notebook.nbpkg_ctx_instantiated = false update_nbpkg_cache!(notebook) send_notebook_changes!(ClientRequest(session=session, notebook=notebook)) @@ -323,6 +329,13 @@ function writebackup(notebook::Notebook) backup_path end +""" +Reset the package environment of a notebook. This will remove the `Project.toml` and `Manifest.toml` files from the notebook's secret package environment folder, and if `save` is `true`, it will then save the notebook without embedded Project and Manifest. + +If `keep_project` is `true` (default `false`), the `Project.toml` file will be kept, but the `Manifest.toml` file will be removed. + +This function is useful when we are not able to resolve/activate/instantiate a notebook's environment after loading, which happens when e.g. the environment was created on a different OS or Julia version. +""" function reset_nbpkg(notebook::Notebook, topology::Union{NotebookTopology,Nothing}=nothing; keep_project::Bool=false, backup::Bool=true, save::Bool=true) backup && save && writebackup(notebook) @@ -434,11 +447,11 @@ function update_nbpkg(session, notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.U if pkg_result.did_something if pkg_result.restart_recommended notebook.nbpkg_restart_recommended_msg = "Yes, something changed during regular update_nbpkg." - @debug "PlutoPkg: Notebook restart recommended" notebook.nbpkg_restart_recommended_msg + @debug "PlutoPkg: Notebook restart recommended" notebook.path notebook.nbpkg_restart_recommended_msg end if pkg_result.restart_required notebook.nbpkg_restart_required_msg = "Yes, something changed during regular update_nbpkg." - @debug "PlutoPkg: Notebook restart REQUIRED" notebook.nbpkg_restart_required_msg + @debug "PlutoPkg: Notebook restart REQUIRED" notebook.path notebook.nbpkg_restart_required_msg end else isfile(bp) && rm(bp) diff --git a/src/packages/PkgUtils.jl b/src/packages/PkgUtils.jl index 52f3d91662..e9916a5fb0 100644 --- a/src/packages/PkgUtils.jl +++ b/src/packages/PkgUtils.jl @@ -81,7 +81,7 @@ nb_and_dir_environments_equal(notebook_path::String, dir::String) = nb_and_dir_e reset_notebook_environment(notebook_path::String; keep_project::Bool=false, backup::Bool=true) ``` -Remove the embedded `Project.toml` and `Manifest.toml` from a notebook file, modifying the file. If `keep_project` is true, only `Manifest.toml` will be deleted. A backup file is created by default. +Remove the embedded `Project.toml` and `Manifest.toml` from a notebook file, modifying the file. If `keep_project` is true, only `Manifest.toml` will be deleted. A backup of the notebook file is created by default. """ function reset_notebook_environment(path::String; kwargs...) Pluto.reset_nbpkg( diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index 61f775e633..835d56f5f8 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -158,6 +158,7 @@ function notebook_to_js(notebook::Notebook) # TODO: cache this "installed_versions" => ctx === nothing ? Dict{String,String}() : notebook.nbpkg_installed_versions_cache, "terminal_outputs" => notebook.nbpkg_terminal_outputs, + "install_time_ns" => notebook.nbpkg_install_time_ns, "busy_packages" => notebook.nbpkg_busy_packages, "instantiated" => notebook.nbpkg_ctx_instantiated, ) diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index eeee6abcc7..4cbdb566d0 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -58,7 +58,9 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test notebook.nbpkg_restart_recommended_msg === nothing @test notebook.nbpkg_restart_required_msg === nothing @test notebook.nbpkg_ctx_instantiated + @test notebook.nbpkg_install_time_ns > 0 @test notebook.nbpkg_busy_packages |> isempty + last_install_time = notebook.nbpkg_install_time_ns terminals = notebook.nbpkg_terminal_outputs @@ -86,6 +88,10 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test notebook.nbpkg_ctx !== nothing @test notebook.nbpkg_restart_recommended_msg === nothing @test notebook.nbpkg_restart_required_msg === nothing + @test notebook.nbpkg_ctx_instantiated + @test notebook.nbpkg_install_time_ns > last_install_time + @test notebook.nbpkg_busy_packages |> isempty + last_install_time = notebook.nbpkg_install_time_ns @test haskey(terminals, "PlutoPkgTestB") @test terminals["PlutoPkgTestA"] == terminals["PlutoPkgTestD"] == old_A_terminal @@ -106,6 +112,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; notebook.nbpkg_restart_recommended_msg !== nothing || notebook.nbpkg_restart_required_msg !== nothing ) @test notebook.nbpkg_restart_required_msg !== nothing + @test notebook.nbpkg_install_time_ns > last_install_time # running cells again should persist the restart message @@ -205,6 +212,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test notebook.nbpkg_ctx !== nothing @test notebook.nbpkg_restart_recommended_msg !== nothing # recommend restart @test notebook.nbpkg_restart_required_msg === nothing + @test notebook.nbpkg_install_time_ns === nothing # removing a package means that we lose our estimate @test count("PlutoPkgTestD", ptoml_contents()) == 0 From 1b9baa8750445c47bc5683f72aef779dedc71158 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 6 Sep 2022 19:34:55 +0200 Subject: [PATCH 411/821] Plots.jl GR respect fmt setting for auto choosing between SVG PNG (#2266) --- src/runner/PlutoRunner.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index dc8883fef3..d597748fe0 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -1546,7 +1546,17 @@ const integrations = Integration[ 0 end const max_plot_size = 8000 - pluto_showable(::MIME"image/svg+xml", p::Plots.Plot{Plots.GRBackend}) = approx_size(p) <= max_plot_size + function pluto_showable(::MIME"image/svg+xml", p::Plots.Plot{Plots.GRBackend}) + format = try + p.attr[:html_output_format] + catch + :auto + end + + format === :svg || ( + format === :auto && approx_size(p) <= max_plot_size + ) + end pluto_showable(::MIME"text/html", p::Plots.Plot{Plots.GRBackend}) = false end, ), From 75261d3296f9392e6fa97a9607f2c3a247500e2c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 6 Sep 2022 19:45:07 +0200 Subject: [PATCH 412/821] Show "Estimate runtime" in static HTML (#2267) --- frontend/binder.css | 17 ++++++++++- frontend/common/Serialization.js | 2 +- frontend/components/EditOrRunButton.js | 39 ++++++++++++++++++++++++-- frontend/components/Editor.js | 5 ++-- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/frontend/binder.css b/frontend/binder.css index 5fedf6591b..56d400a161 100644 --- a/frontend/binder.css +++ b/frontend/binder.css @@ -143,7 +143,7 @@ body.wiggle_binder .edit_or_run > button { color: black; color-scheme: light; box-shadow: 0px 0px 0px 100vmax #0000004a; - font-family: var(--lato-ui-font-stack); + font-family: var(--sans-serif-font-stack); } .binder_help_text a { color: black; @@ -285,3 +285,18 @@ body.wiggle_binder .edit_or_run > button { border-radius: 5px; width: 100%; } + +.expected_runtime_box { + padding: 0.6em 1em; + border-radius: 0.6em; + font-style: italic; + display: block; + background: linear-gradient(45deg, hsl(222deg 52% 87%), #e5f7ff); + margin: 2em 0em -2em 0em; + color: #323232; +} + +.expected_runtime_box span { + font-style: initial; + font-weight: bold; +} diff --git a/frontend/common/Serialization.js b/frontend/common/Serialization.js index c45c1cc67d..e1829da54e 100644 --- a/frontend/common/Serialization.js +++ b/frontend/common/Serialization.js @@ -51,7 +51,7 @@ export function deserialize_repl(repl_session) { .filter((s) => s !== "") } -export const detect_deserializer = (topaste) => +export const detect_deserializer = (/** @type {string} */ topaste) => topaste.trim().startsWith(JULIA_REPL_PROMPT) ? deserialize_repl : topaste.match(/# ╔═╡ ........-....-....-....-............/g)?.length diff --git a/frontend/components/EditOrRunButton.js b/frontend/components/EditOrRunButton.js index 181008b02e..aad48acaa5 100644 --- a/frontend/components/EditOrRunButton.js +++ b/frontend/components/EditOrRunButton.js @@ -1,3 +1,4 @@ +import _ from "../imports/lodash.js" import { BackendLaunchPhase } from "../common/Binder.js" import { html, useEffect, useState, useRef } from "../imports/Preact.js" @@ -20,11 +21,19 @@ export const RunLocalButton = ({ show, start_local }) => { ` } -export const BinderButton = ({ offer_binder, start_binder, notebookfile }) => { +/** + * @param {{ + * notebook: import("./Editor.js").NotebookData, + * notebookfile: string?, + * start_binder: () => Promise, + * offer_binder: boolean, + * }} props + * */ +export const BinderButton = ({ offer_binder, start_binder, notebookfile, notebook }) => { const [popupOpen, setPopupOpen] = useState(false) const [showCopyPopup, setShowCopyPopup] = useState(false) const notebookfile_ref = useRef("") - notebookfile_ref.current = notebookfile + notebookfile_ref.current = notebookfile ?? "" //@ts-ignore window.open_edit_or_run_popup = () => { @@ -63,6 +72,7 @@ export const BinderButton = ({ offer_binder, start_binder, notebookfile }) => { }, [start_binder, offer_binder]) const recommend_download = notebookfile_ref.current.startsWith("data:") + const runtime_str = expected_runtime_str(notebook) return html`
    `} ` } + +const expected_runtime = (/** @type {import("./Editor.js").NotebookData} */ notebook) => { + return ((notebook.nbpkg?.install_time_ns ?? NaN) + _.sum(Object.values(notebook.cell_results).map((c) => c.runtime ?? 0))) / 1e9 +} + +const runtime_overhead = 15 // seconds +const runtime_multiplier = 1.5 + +const expected_runtime_str = (/** @type {import("./Editor.js").NotebookData} */ notebook) => { + const ex = expected_runtime(notebook) + if (isNaN(ex)) { + return null + } + + const sec = _.round(runtime_overhead + ex * runtime_multiplier, -1) + if (sec < 60) { + return `${Math.ceil(sec)} second${sec > 1 ? "s" : ""}` + } else { + const min = sec / 60 + return `${Math.ceil(min)} minute${min > 1 ? "s" : ""}` + } +} diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 606023e4bd..961e0ea1a2 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -1452,6 +1452,7 @@ patch: ${JSON.stringify( launch_params: launch_params, })} notebookfile=${launch_params.notebookfile == null ? null : new URL(launch_params.notebookfile, window.location.href).href} + notebook=${notebook} />` : null } @@ -1467,11 +1468,11 @@ patch: ${JSON.stringify( <${Preamble} last_update_time=${this.state.last_update_time} any_code_differs=${status.code_differs} - last_hot_reload_time=${this.state.notebook.last_hot_reload_time} + last_hot_reload_time=${notebook.last_hot_reload_time} connected=${this.state.connected} /> <${Notebook} - notebook=${this.state.notebook} + notebook=${notebook} cell_inputs_local=${this.state.cell_inputs_local} disable_input=${this.state.disable_ui || !this.state.connected /* && this.state.backend_launch_phase == null*/} last_created_cell=${this.state.last_created_cell} From a59045b3170a3674c18fb07761e691d47937dc67 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 7 Sep 2022 16:51:33 +0200 Subject: [PATCH 413/821] Update Test.yml --- .github/workflows/Test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 03cd2cc9d4..8b7b207f7e 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: # We test quite a lot of versions because we do some OS and version specific things unfortunately - julia-version: ["1.6", "1.7", "~1.8.0-0"] #, "nightly"] + julia-version: ["1.6", "1.7", "1.8"] #, "~1.9.0-0"] #, "nightly"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: From 19ac9b1764a3d131021a4e0c85abe61d3d2971a9 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 7 Sep 2022 21:10:56 +0200 Subject: [PATCH 414/821] `updated_topology`: maintain identity when there are no changes (#2269) --- src/analysis/TopologyUpdate.jl | 41 ++++--- test/Analysis.jl | 200 +++++++++++++++++++++------------ test/cell_disabling.jl | 2 +- 3 files changed, 158 insertions(+), 85 deletions(-) diff --git a/src/analysis/TopologyUpdate.jl b/src/analysis/TopologyUpdate.jl index 47251d4dd7..d9363124d0 100644 --- a/src/analysis/TopologyUpdate.jl +++ b/src/analysis/TopologyUpdate.jl @@ -6,7 +6,7 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce updated_codes = Dict{Cell,ExprAnalysisCache}() updated_nodes = Dict{Cell,ReactiveNode}() - unresolved_cells = copy(old_topology.unresolved_cells.c) + for cell in cells old_code = old_topology.codes[cell] if old_code.code !== cell.code @@ -20,17 +20,9 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce # reset computer code updated_codes[cell] = ExprAnalysisCache(old_code; forced_expr_id=nothing, function_wrapped=false) end - - new_reactive_node = get(updated_nodes, cell, old_topology.nodes[cell]) - if !isempty(new_reactive_node.macrocalls) - # The unresolved cells are the cells for wich we cannot create - # a ReactiveNode yet, because they contains macrocalls. - push!(unresolved_cells, cell) - else - pop!(unresolved_cells, cell, nothing) - end end - + + old_cells = all_cells(old_topology) removed_cells = setdiff(old_cells, notebook.cells) if isempty(removed_cells) @@ -38,21 +30,42 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce new_codes = merge(old_topology.codes, updated_codes) new_nodes = merge(old_topology.nodes, updated_nodes) else - setdiff!(unresolved_cells, removed_cells) new_codes = merge(setdiffkeys(old_topology.codes, removed_cells), updated_codes) new_nodes = merge(setdiffkeys(old_topology.nodes, removed_cells), updated_nodes) end + new_unresolved_set = setdiff!( + union!( + Set{Cell}(), + # all cells that were unresolved before, and did not change code... + Iterators.filter(old_topology.unresolved_cells) do c + !haskey(updated_nodes, c) + end, + # ...plus all cells that changed, and now use a macrocall... + Iterators.filter(cells) do c + !isempty(new_nodes[c].macrocalls) + end, + ), + # ...minus cells that were removed + removed_cells, + ) + + unresolved_cells = if new_unresolved_set == old_topology.unresolved_cells + old_topology.unresolved_cells + else + ImmutableSet(new_unresolved_set; skip_copy=true) + end + cell_order = if old_cells == notebook.cells old_topology.cell_order else ImmutableVector(notebook.cells) end - + NotebookTopology(; nodes=new_nodes, codes=new_codes, - unresolved_cells=ImmutableSet(unresolved_cells; skip_copy=true), + unresolved_cells, cell_order, ) end diff --git a/test/Analysis.jl b/test/Analysis.jl index 38436f468c..e45bf13023 100644 --- a/test/Analysis.jl +++ b/test/Analysis.jl @@ -1,78 +1,138 @@ using Test -import Pluto: Notebook, ServerSession, ClientSession, Cell, updated_topology, static_resolve_topology, is_just_text +import Pluto: Notebook, Cell, updated_topology, static_resolve_topology, is_just_text, NotebookTopology @testset "Analysis" begin - notebook = Notebook([ - Cell(""), - Cell("md\"a\""), - Cell("html\"a\""), - Cell("md\"a \$b\$\""), - Cell("md\"a ``b``\""), - Cell(""" - let - x = md"a" - md"r \$x" - end - """), - Cell("html\"a 7 \$b\""), + @testset "is_just_text" begin + notebook = Notebook([ + Cell(""), + Cell("md\"a\""), + Cell("html\"a\""), + Cell("md\"a \$b\$\""), + Cell("md\"a ``b``\""), + Cell(""" + let + x = md"a" + md"r \$x" + end + """), + Cell("html\"a 7 \$b\""), - Cell("md\"a 8 \$b\""), - Cell("@a md\"asdf 9\""), - Cell("x()"), - Cell("x() = y()"), - Cell("12 + 12"), - Cell("import Dates"), - Cell("import Dates"), - Cell("while false end"), - Cell("for i in [16]; end"), - Cell("[i for i in [17]]"), - Cell("module x18 end"), - Cell(""" - module x19 - exit() - end - """), - Cell("""quote end"""), - Cell("""quote x = 21 end"""), - Cell("""quote \$(x = 22) end"""), - Cell("""asdf"23" """), - Cell("""@asdf("24") """), - Cell("""@x"""), - Cell("""@y z 26"""), - Cell("""f(g"27")"""), - ]) + Cell("md\"a 8 \$b\""), + Cell("@a md\"asdf 9\""), + Cell("x()"), + Cell("x() = y()"), + Cell("12 + 12"), + Cell("import Dates"), + Cell("import Dates"), + Cell("while false end"), + Cell("for i in [16]; end"), + Cell("[i for i in [17]]"), + Cell("module x18 end"), + Cell(""" + module x19 + exit() + end + """), + Cell("""quote end"""), + Cell("""quote x = 21 end"""), + Cell("""quote \$(x = 22) end"""), + Cell("""asdf"23" """), + Cell("""@asdf("24") """), + Cell("""@x"""), + Cell("""@y z 26"""), + Cell("""f(g"27")"""), + ]) + + old = notebook.topology + new = notebook.topology = updated_topology(old, notebook, notebook.cells) |> static_resolve_topology - old = notebook.topology - new = notebook.topology = updated_topology(old, notebook, notebook.cells) |> static_resolve_topology + @testset "Only-text detection" begin + @test is_just_text(new, notebook.cells[1]) + @test is_just_text(new, notebook.cells[2]) + @test is_just_text(new, notebook.cells[3]) + @test is_just_text(new, notebook.cells[4]) + @test is_just_text(new, notebook.cells[5]) + @test is_just_text(new, notebook.cells[6]) + @test is_just_text(new, notebook.cells[7]) - @testset "Only-text detection" begin - @test is_just_text(new, notebook.cells[1]) - @test is_just_text(new, notebook.cells[2]) - @test is_just_text(new, notebook.cells[3]) - @test is_just_text(new, notebook.cells[4]) - @test is_just_text(new, notebook.cells[5]) - @test is_just_text(new, notebook.cells[6]) - @test is_just_text(new, notebook.cells[7]) + @test !is_just_text(new, notebook.cells[8]) + @test !is_just_text(new, notebook.cells[9]) + @test !is_just_text(new, notebook.cells[10]) + @test !is_just_text(new, notebook.cells[11]) + @test !is_just_text(new, notebook.cells[12]) + @test !is_just_text(new, notebook.cells[13]) + @test !is_just_text(new, notebook.cells[14]) + @test !is_just_text(new, notebook.cells[15]) + @test !is_just_text(new, notebook.cells[16]) + @test !is_just_text(new, notebook.cells[17]) + @test !is_just_text(new, notebook.cells[18]) + @test !is_just_text(new, notebook.cells[19]) + @test !is_just_text(new, notebook.cells[20]) + @test !is_just_text(new, notebook.cells[21]) + @test !is_just_text(new, notebook.cells[22]) + @test !is_just_text(new, notebook.cells[23]) + @test !is_just_text(new, notebook.cells[24]) + @test !is_just_text(new, notebook.cells[25]) + @test !is_just_text(new, notebook.cells[26]) + @test !is_just_text(new, notebook.cells[27]) + end + end - @test !is_just_text(new, notebook.cells[8]) - @test !is_just_text(new, notebook.cells[9]) - @test !is_just_text(new, notebook.cells[10]) - @test !is_just_text(new, notebook.cells[11]) - @test !is_just_text(new, notebook.cells[12]) - @test !is_just_text(new, notebook.cells[13]) - @test !is_just_text(new, notebook.cells[14]) - @test !is_just_text(new, notebook.cells[15]) - @test !is_just_text(new, notebook.cells[16]) - @test !is_just_text(new, notebook.cells[17]) - @test !is_just_text(new, notebook.cells[18]) - @test !is_just_text(new, notebook.cells[19]) - @test !is_just_text(new, notebook.cells[20]) - @test !is_just_text(new, notebook.cells[21]) - @test !is_just_text(new, notebook.cells[22]) - @test !is_just_text(new, notebook.cells[23]) - @test !is_just_text(new, notebook.cells[24]) - @test !is_just_text(new, notebook.cells[25]) - @test !is_just_text(new, notebook.cells[26]) - @test !is_just_text(new, notebook.cells[27]) + @testset "updated_topology identity" begin + notebook = Notebook([ + Cell("x = 1") + Cell("function f(x) + x + 1 + end") + Cell("a = x - 123") + Cell("") + Cell("") + Cell("") + ]) + + empty_top = notebook.topology + topo = updated_topology(empty_top, notebook, notebook.cells) + # updated_topology should preserve the identity of the topology if nothing changed. This means that we can cache the result of other functions in our code! + @test topo === updated_topology(topo, notebook, notebook.cells) + @test topo === updated_topology(topo, notebook, Cell[]) + @test topo === static_resolve_topology(topo) + + # for n in fieldnames(NotebookTopology) + # @test getfield(topo, n) === getfield(top2a, n) + # end + + setcode!(notebook.cells[1], "x = 999") + topo_2 = updated_topology(topo, notebook, notebook.cells[1:1]) + @test topo_2 !== topo + + + setcode!(notebook.cells[4], "@asdf 1 + 2") + topo_3 = updated_topology(topo_2, notebook, notebook.cells[4:4]) + @test topo_3 !== topo_2 + @test topo_3 !== topo + + @test topo_3.unresolved_cells |> only === notebook.cells[4] + + @test topo_3 === updated_topology(topo_3, notebook, notebook.cells[1:3]) + @test topo_3 === updated_topology(topo_3, notebook, Cell[]) + # rerunning the cell with the macro does not change the topology because it was already unresolved + @test topo_3 === updated_topology(topo_3, notebook, notebook.cells[1:4]) + + # let's pretend that we resolved the macro in the 4th cell + topo_3_resolved = NotebookTopology(; + nodes=topo_3.nodes, + codes=topo_3.codes, + unresolved_cells=setdiff(topo_3.unresolved_cells, notebook.cells[4:4]), + cell_order=topo_3.cell_order, + ) + + @test topo_3_resolved === updated_topology(topo_3_resolved, notebook, notebook.cells[1:3]) + @test topo_3_resolved === updated_topology(topo_3_resolved, notebook, Cell[]) + # rerunning the cell with the macro makes it unresolved again + @test topo_3_resolved !== updated_topology(topo_3_resolved, notebook, notebook.cells[1:4]) + + notebook.cells[4] ∈ updated_topology(topo_3_resolved, notebook, notebook.cells[1:4]).unresolved_cells + + # @test topo_3 === static_resolve_topology(topo_3) end -end +end \ No newline at end of file diff --git a/test/cell_disabling.jl b/test/cell_disabling.jl index 1f9602f0d5..19bc64aa83 100644 --- a/test/cell_disabling.jl +++ b/test/cell_disabling.jl @@ -31,7 +31,7 @@ using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook # disable first cell notebook.cells[1].metadata["disabled"] = true - update_run!(🍭, notebook, notebook.cells) + update_run!(🍭, notebook, notebook.cells[1]) should_be_disabled = [1, 3, 5] @test get_disabled_cells(notebook) == should_be_disabled @test notebook.cells[1].metadata["disabled"] == true From faa136303f24c66069b9a07f600ac96069541f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A0=CE=B1=CE=BD=CE=B1=CE=B3=CE=B9=CF=8E=CF=84=CE=B7?= =?UTF-8?q?=CF=82=20=CE=93=CE=B5=CF=89=CF=81=CE=B3=CE=B1=CE=BA=CF=8C=CF=80?= =?UTF-8?q?=CE=BF=CF=85=CE=BB=CE=BF=CF=82?= Date: Wed, 7 Sep 2022 23:48:11 +0300 Subject: [PATCH 415/821] Environments/cells can render Non-Cell outputs in the notebook (#2195) --- frontend/components/Editor.js | 7 ++++- frontend/components/NonCellOutput.js | 41 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 frontend/components/NonCellOutput.js diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 961e0ea1a2..be9cd28a97 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -28,6 +28,7 @@ import { setup_mathjax } from "../common/SetupMathJax.js" import { BinderButton, RunLocalButton } from "./EditOrRunButton.js" import { slider_server_actions, nothing_actions } from "../common/SliderServerClient.js" import { ProgressBar } from "./ProgressBar.js" +import { NonCellOutput } from "./NonCellOutput.js" import { IsolatedCell } from "./Cell.js" import { RawHTMLContainer } from "./CellOutput.js" import { RecordingPlaybackUI, RecordingUI } from "./RecordingUI.js" @@ -766,11 +767,12 @@ patch: ${JSON.stringify( Object.assign(this.client, client) try { const { default: environment } = await import(this.client.session_options.server.injected_javascript_data_url) - const { custom_editor_header_component } = environment({ client, editor: this, imports: { preact } }) + const { custom_editor_header_component, custom_non_cell_output } = environment({ client, editor: this, imports: { preact } }) this.setState({ extended_components: { ...this.state.extended_components, CustomHeader: custom_editor_header_component, + NonCellOutputComponents: custom_non_cell_output, }, }) } catch (e) {} @@ -1508,6 +1510,9 @@ patch: ${JSON.stringify( }} />` } + <${NonCellOutput} + notebook_id=${this.state.notebook.notebook_id} + environment_component=${this.state.extended_components.NonCellOutputComponents} /> <${LiveDocs} desired_doc_query=${this.state.desired_doc_query} diff --git a/frontend/components/NonCellOutput.js b/frontend/components/NonCellOutput.js new file mode 100644 index 0000000000..099491132a --- /dev/null +++ b/frontend/components/NonCellOutput.js @@ -0,0 +1,41 @@ +import { html, useEffect, useRef, useState } from "../imports/Preact.js" +/** + * Sometimes, we want to render HTML outside of the Cell Output, + * for to add toolboxes like the Table of Contents or something similar. + * + * Additionally, the environment may want to inject some non cell/non editor + * specific HTML to be rendered in the page. This component acts as a sink for + * rendering these usecases. + * + * That way, the Cell Output can change with a different lifecycle than + * the Non-Cell output and environments can inject UI. + * + * This component listens to events like the one below and updates + * document.dispatchEvent( + * new CustomEvent("experimental_add_node_non_cell_output", { + * detail: { order: 1, node: html`
    ...
    `, name: "Name of toolbox" } + * })) + + */ +export const NonCellOutput = ({ environment_component, notebook_id }) => { + const surely_the_latest_updated_set = useRef() + const [component_set, update_component_set] = useState({}) + useEffect(() => { + const hn = (e) => { + try { + const { name, node, order } = e.detail + surely_the_latest_updated_set.current = { ...surely_the_latest_updated_set.current, [name]: { node, order } } + update_component_set(surely_the_latest_updated_set.current) + } catch (e) {} + } + document.addEventListener("experimental_add_node_non_cell_output", hn) + return () => document.removeEventListener("experimental_add_node_non_cell_output", hn) + }, [surely_the_latest_updated_set]) + + let components = Object.values(component_set) + components.sort(({ order: o1 }, { order: o2 }) => o1 - o2) + components = components.map(({ node }) => node) + return html`
    + ${environment_component ? html`<${environment_component} notebook_id=${notebook_id} />` : null} ${components} +
    ` +} From 3007b48f3fa224992ea421bacb85f370dec016f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A0=CE=B1=CE=BD=CE=B1=CE=B3=CE=B9=CF=8E=CF=84=CE=B7?= =?UTF-8?q?=CF=82=20=CE=93=CE=B5=CF=89=CF=81=CE=B3=CE=B1=CE=BA=CF=8C=CF=80?= =?UTF-8?q?=CE=BF=CF=85=CE=BB=CE=BF=CF=82?= Date: Wed, 7 Sep 2022 23:48:43 +0300 Subject: [PATCH 416/821] Reload CM when things go south (#1917) Co-authored-by: Fons van der Plas --- frontend/components/Cell.js | 31 ++++++++++++------- frontend/components/CellInput.js | 25 ++++++++++++--- .../CellInput/pluto_autocomplete.js | 4 +++ frontend/imports/Preact.d.ts | 1 + frontend/imports/Preact.js | 2 ++ 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 17a5aa806b..039e8d5178 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -1,5 +1,5 @@ import _ from "../imports/lodash.js" -import { html, useState, useEffect, useMemo, useRef, useContext, useLayoutEffect, useCallback } from "../imports/Preact.js" +import { html, useState, useEffect, useMemo, useRef, useContext, useLayoutEffect, useErrorBoundary, useCallback } from "../imports/Preact.js" import { CellOutput } from "./CellOutput.js" import { CellInput } from "./CellInput.js" @@ -119,6 +119,19 @@ export const Cell = ({ const on_focus_neighbor = pluto_actions.focus_on_neighbor const on_change = useCallback((val) => pluto_actions.set_local_cell(cell_id, val), [cell_id, pluto_actions]) const variables = useMemo(() => Object.keys(cell_dependencies?.downstream_cells_map ?? {}), [cell_dependencies]) + + // We need to unmount & remount when a destructive error occurs. + // For that reason, we will use a simple react key and increment it on error + const [key, setKey] = useState(0) + const cell_key = useMemo(() => cell_id + key, [cell_id, key]) + + const [, resetError] = useErrorBoundary((error) => { + console.log(`An error occured in the CodeMirror code, resetting CellInput component. See error below:\n\n${error}\n\n -------------- `) + setKey(key + 1) + resetError() + }) + + const remount = useMemo(() => () => setKey(key + 1)) // cm_forced_focus is null, except when a line needs to be highlighted because it is part of a stack trace const [cm_forced_focus, set_cm_forced_focus] = useState(/** @type{any} */ (null)) const [cm_highlighted_line, set_cm_highlighted_line] = useState(null) @@ -213,17 +226,12 @@ export const Cell = ({ set_waiting_to_run_smart(true) }, [pluto_actions, cell_id, selected, set_waiting_to_run_smart]) - const skip_as_script_jump = useCallback( - on_jump(hasTargetBarrier("skip_as_script"), pluto_actions, cell_id), - [pluto_actions, cell_id], - ) - const disabled_jump = useCallback( - on_jump(hasTargetBarrier("disabled"), pluto_actions, cell_id), - [pluto_actions, cell_id], - ) + const skip_as_script_jump = useCallback(on_jump(hasTargetBarrier("skip_as_script"), pluto_actions, cell_id), [pluto_actions, cell_id]) + const disabled_jump = useCallback(on_jump(hasTargetBarrier("disabled"), pluto_actions, cell_id), [pluto_actions, cell_id]) return html` ${show_logs ? html`<${Logs} logs=${Object.values(logs)} line_heights=${line_heights} set_cm_highlighted_line=${set_cm_highlighted_line} />` : null} <${RunArea} @@ -336,8 +345,8 @@ export const Cell = ({ body: html`This cell is currently stored in the notebook file as a Julia comment, instead of code.
    This way, it will not run when the notebook runs as a script outside of Pluto.
    An upstream cell is indirectly disabling in file this one; enable - the upstream one to - affect this cell.`, + the upstream one to affect + this cell.`, }) }} >` diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index cea6a51964..c3f46f3a5f 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -63,6 +63,7 @@ import { HighlightLineFacet, highlightLinePlugin } from "./CellInput/highlight_l import { commentKeymap } from "./CellInput/comment_mixed_parsers.js" import { debug_syntax_plugin } from "./CellInput/debug_syntax_plugin.js" import { ScopeStateField } from "./CellInput/scopestate_statefield.js" +import { is_mac_keyboard } from "../common/KeyboardShortcuts.js" export const ENABLE_CM_MIXED_PARSER = window.localStorage.getItem("ENABLE_CM_MIXED_PARSER") === "true" @@ -375,10 +376,17 @@ export const CellInput = ({ }) => { let pluto_actions = useContext(PlutoActionsContext) const { disabled: running_disabled, skip_as_script } = metadata - - const newcm_ref = useRef(/** @type {EditorView?} */ (null)) - const dom_node_ref = useRef(/** @type {HTMLElement?} */ (null)) - const remote_code_ref = useRef(/** @type {string?} */ (null)) + let [error, set_error] = useState(null) + if (error) { + const to_throw = error + set_error(null) + throw to_throw + } + const newcm_ref = useRef(/** @type {EditorView} */ (null)) + const dom_node_ref = useRef(/** @type {HTMLElement} */ (null)) + const remote_code_ref = useRef(null) + const on_change_ref = useRef(null) + on_change_ref.current = on_change let nbpkg_compartment = useCompartment(newcm_ref, NotebookpackagesFacet.of(nbpkg)) let global_definitions_compartment = useCompartment(newcm_ref, GlobalDefinitionsFacet.of(global_definition_locations)) @@ -571,7 +579,6 @@ export const CellInput = ({ const newcm = (newcm_ref.current = new EditorView({ state: EditorState.create({ doc: local_code, - extensions: [ EditorView.theme({}, { dark: usesDarkTheme }), // Compartments coming from react state/props @@ -693,6 +700,14 @@ export const CellInput = ({ // Enable this plugin if you want to see the lezer tree, // and possible lezer errors and maybe more debug info in the console: // debug_syntax_plugin, + // Handle errors hopefully? + EditorView.exceptionSink.of((exception) => { + set_error(exception) + console.error("EditorView exception!", exception) + // alert( + // `We ran into an issue! We have lost your cursor 😞😓😿\n If this appears again, please press F12, then click the "Console" tab, eport an issue at https://github.com/fonsp/Pluto.jl/issues` + // ) + }), ], }), parent: dom_node_ref.current, diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js index 3596a473e0..dbfb382def 100644 --- a/frontend/components/CellInput/pluto_autocomplete.js +++ b/frontend/components/CellInput/pluto_autocomplete.js @@ -64,6 +64,10 @@ const tabCompletionState = StateField.define({ /** @param {EditorView} cm */ const tab_completion_command = (cm) => { // This will return true if the autocomplete select popup is open + // To test the exception sink, uncomment these lines: + // if (Math.random() > 0.7) { + // throw "LETS CRASH THIS" + // } if (acceptCompletion(cm)) { return true } diff --git a/frontend/imports/Preact.d.ts b/frontend/imports/Preact.d.ts index f22f8bb5eb..e491d9168b 100644 --- a/frontend/imports/Preact.d.ts +++ b/frontend/imports/Preact.d.ts @@ -58,6 +58,7 @@ export declare function useRef(initialValue?: T): Ref export declare function useMemo(calculate: () => T, deps?: Array): T export declare function useCallback(callback: T, deps?: Array): T +export declare function useErrorBoundary(callback?: (error: any) => Promise | void): [any, () => void]; type UnsubscribeFn = () => void type EffectFn = () => void | UnsubscribeFn diff --git a/frontend/imports/Preact.js b/frontend/imports/Preact.js index b938009bb2..29d5f06b3d 100644 --- a/frontend/imports/Preact.js +++ b/frontend/imports/Preact.js @@ -18,6 +18,7 @@ import { useMemo, useCallback, useContext, + useErrorBoundary, } from "https://esm.sh/v66/preact@10.6.6/hooks?target=es2020" import htm from "https://esm.sh/v66/htm@3.1.0?target=es2020" @@ -35,6 +36,7 @@ export { useRef, useMemo, useCallback, + useErrorBoundary, createContext, createRef, useContext, From 19c48bbb5b89a2e2b24d2105e63b6a3928803c88 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 7 Sep 2022 23:06:53 +0200 Subject: [PATCH 417/821] Include set of disabled cells in `NotebookTopology` (#2270) --- src/analysis/Topology.jl | 4 +++- src/analysis/TopologyUpdate.jl | 25 ++++++++++++++++++++++--- src/evaluation/MacroAnalysis.jl | 5 ++++- src/evaluation/Run.jl | 5 +++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/analysis/Topology.jl b/src/analysis/Topology.jl index 655421706c..d1c22477bc 100644 --- a/src/analysis/Topology.jl +++ b/src/analysis/Topology.jl @@ -32,6 +32,7 @@ Base.@kwdef struct NotebookTopology cell_order::ImmutableVector{Cell}=ImmutableVector{Cell}() unresolved_cells::ImmutableSet{Cell} = ImmutableSet{Cell}() + disabled_cells::ImmutableSet{Cell} = ImmutableSet{Cell}() end # BIG TODO HERE: CELL ORDER @@ -48,6 +49,7 @@ function set_unresolved(topology::NotebookTopology, unresolved_cells::Vector{Cel nodes=topology.nodes, codes=merge(topology.codes, codes), unresolved_cells=union(topology.unresolved_cells, unresolved_cells), - cell_order = topology.cell_order, + cell_order=topology.cell_order, + disabled_cells=topology.disabled_cells, ) end diff --git a/src/analysis/TopologyUpdate.jl b/src/analysis/TopologyUpdate.jl index d9363124d0..0f81070725 100644 --- a/src/analysis/TopologyUpdate.jl +++ b/src/analysis/TopologyUpdate.jl @@ -46,26 +46,45 @@ function updated_topology(old_topology::NotebookTopology, notebook::Notebook, ce !isempty(new_nodes[c].macrocalls) end, ), - # ...minus cells that were removed + # ...minus cells that were removed. removed_cells, ) - + + new_disabled_set = setdiff!( + union!( + Set{Cell}(), + # all cells that were disabled before... + old_topology.disabled_cells, + # ...plus all cells that changed... + cells, + ), + # ...minus cells that changed and are not disabled. + Iterators.filter(!is_disabled, cells), + ) + unresolved_cells = if new_unresolved_set == old_topology.unresolved_cells old_topology.unresolved_cells else ImmutableSet(new_unresolved_set; skip_copy=true) end + disabled_cells = if new_disabled_set == old_topology.disabled_cells + old_topology.disabled_cells + else + ImmutableSet(new_disabled_set; skip_copy=true) + end + cell_order = if old_cells == notebook.cells old_topology.cell_order else - ImmutableVector(notebook.cells) + ImmutableVector(notebook.cells) # makes a copy end NotebookTopology(; nodes=new_nodes, codes=new_codes, unresolved_cells, + disabled_cells, cell_order, ) end diff --git a/src/evaluation/MacroAnalysis.jl b/src/evaluation/MacroAnalysis.jl index 4ef8760978..a9e6d55c97 100644 --- a/src/evaluation/MacroAnalysis.jl +++ b/src/evaluation/MacroAnalysis.jl @@ -26,6 +26,7 @@ function with_new_soft_definitions(topology::NotebookTopology, cell::Cell, soft_ nodes=merge(topology.nodes, Dict(cell => new_node)), unresolved_cells=topology.unresolved_cells, cell_order=topology.cell_order, + disabled_cells=topology.disabled_cells, ) end @@ -170,11 +171,12 @@ function resolve_topology( ImmutableSet(still_unresolved_nodes; skip_copy=true) end - NotebookTopology( + NotebookTopology(; nodes=all_nodes, codes=all_codes, unresolved_cells=new_unresolved_cells, cell_order=unresolved_topology.cell_order, + disabled_cells=unresolved_topology.disabled_cells, ) end @@ -199,5 +201,6 @@ function static_resolve_topology(topology::NotebookTopology) codes=topology.codes, unresolved_cells=topology.unresolved_cells, cell_order=topology.cell_order, + disabled_cells=topology.disabled_cells, ) end diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index d1b56d5fac..b3b878c06b 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -76,6 +76,7 @@ function run_reactive_core!( ), unresolved_cells = new_topology.unresolved_cells, cell_order = new_topology.cell_order, + disabled_cells=new_topology.disabled_cells, ) # save the old topological order - we'll delete variables assigned from its @@ -92,8 +93,7 @@ function run_reactive_core!( to_run_raw = setdiff(union(new_runnable, old_runnable), keys(new_order.errable))::Vector{Cell} # TODO: think if old error cell order matters # find (indirectly) deactivated cells and update their status - disabled_cells = filter(is_disabled, notebook.cells) - indirectly_deactivated = collect(topological_order(new_topology, disabled_cells)) + indirectly_deactivated = collect(topological_order(new_topology, collect(new_topology.disabled_cells))) for cell in indirectly_deactivated cell.running = false cell.queued = false @@ -375,6 +375,7 @@ function update_save_run!( codes=setdiffkeys(old.codes, to_remove), unresolved_cells=setdiff(old.unresolved_cells, to_remove), cell_order=old.cell_order, + disabled_cells=setdiff(old.disabled_cells, to_remove), ) # and don't run them From ab85efca962d009c741d4ec66508d687806e9579 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 8 Sep 2022 00:36:06 +0200 Subject: [PATCH 418/821] Offer popup to enable cell when you double click a disabled cell's run button (#2272) --- frontend/components/Cell.js | 27 ++++++++++++++++++----- frontend/components/CellInput.js | 18 ++++++--------- frontend/components/RunArea.js | 38 ++++++++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index 039e8d5178..c43b2b4f54 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -136,12 +136,6 @@ export const Cell = ({ const [cm_forced_focus, set_cm_forced_focus] = useState(/** @type{any} */ (null)) const [cm_highlighted_line, set_cm_highlighted_line] = useState(null) - const any_logs = useMemo(() => !_.isEmpty(logs), [logs]) - const set_show_logs = (show_logs) => - pluto_actions.update_notebook((notebook) => { - notebook.cell_inputs[cell_id].metadata.show_logs = show_logs - }) - useEffect(() => { const focusListener = (e) => { if (e.detail.cell_id === cell_id) { @@ -225,6 +219,25 @@ export const Cell = ({ pluto_actions.set_and_run_multiple(pluto_actions.get_selected_cells(cell_id, selected)) set_waiting_to_run_smart(true) }, [pluto_actions, cell_id, selected, set_waiting_to_run_smart]) + const set_show_logs = useCallback( + (show_logs) => + pluto_actions.update_notebook((notebook) => { + notebook.cell_inputs[cell_id].metadata.show_logs = show_logs + }), + [pluto_actions, cell_id] + ) + const set_cell_disabled = useCallback( + async (new_val) => { + await pluto_actions.update_notebook((notebook) => { + notebook.cell_inputs[cell_id].metadata["disabled"] = new_val + }) + // we also 'run' the cell if it is disabled, this will make the backend propage the disabled state to dependent cells + await on_submit() + }, + [pluto_actions, cell_id, on_submit] + ) + + const any_logs = useMemo(() => !_.isEmpty(logs), [logs]) const skip_as_script_jump = useCallback(on_jump(hasTargetBarrier("skip_as_script"), pluto_actions, cell_id), [pluto_actions, cell_id]) const disabled_jump = useCallback(on_jump(hasTargetBarrier("disabled"), pluto_actions, cell_id), [pluto_actions, cell_id]) @@ -292,6 +305,7 @@ export const Cell = ({ any_logs=${any_logs} show_logs=${show_logs} set_show_logs=${set_show_logs} + set_cell_disabled=${set_cell_disabled} cm_highlighted_line=${cm_highlighted_line} set_cm_highlighted_line=${set_cm_highlighted_line} onerror=${remount} @@ -305,6 +319,7 @@ export const Cell = ({ on_interrupt=${() => { pluto_actions.interrupt_remote(cell_id) }} + set_cell_disabled=${set_cell_disabled} runtime=${runtime} running=${running} code_differs=${class_code_differs} diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index c3f46f3a5f..92582c4349 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -370,6 +370,7 @@ export const CellInput = ({ any_logs, show_logs, set_show_logs, + set_cell_disabled, cm_highlighted_line, metadata, global_definition_locations, @@ -382,11 +383,9 @@ export const CellInput = ({ set_error(null) throw to_throw } - const newcm_ref = useRef(/** @type {EditorView} */ (null)) - const dom_node_ref = useRef(/** @type {HTMLElement} */ (null)) - const remote_code_ref = useRef(null) - const on_change_ref = useRef(null) - on_change_ref.current = on_change + const newcm_ref = useRef(/** @type {EditorView?} */ (null)) + const dom_node_ref = useRef(/** @type {HTMLElement?} */ (null)) + const remote_code_ref = useRef(/** @type {string?} */ (null)) let nbpkg_compartment = useCompartment(newcm_ref, NotebookpackagesFacet.of(nbpkg)) let global_definitions_compartment = useCompartment(newcm_ref, GlobalDefinitionsFacet.of(global_definition_locations)) @@ -833,12 +832,13 @@ export const CellInput = ({ any_logs=${any_logs} show_logs=${show_logs} set_show_logs=${set_show_logs} + set_cell_disabled=${set_cell_disabled} /> ` } -const InputContextMenu = ({ on_delete, cell_id, run_cell, skip_as_script, running_disabled, any_logs, show_logs, set_show_logs }) => { +const InputContextMenu = ({ on_delete, cell_id, run_cell, skip_as_script, running_disabled, any_logs, show_logs, set_show_logs, set_cell_disabled }) => { const timeout = useRef(null) let pluto_actions = useContext(PlutoActionsContext) const [open, setOpen] = useState(false) @@ -857,11 +857,7 @@ const InputContextMenu = ({ on_delete, cell_id, run_cell, skip_as_script, runnin const new_val = !running_disabled e.preventDefault() e.stopPropagation() - await pluto_actions.update_notebook((notebook) => { - notebook.cell_inputs[cell_id].metadata["disabled"] = new_val - }) - // we also 'run' the cell if it is disabled, this will make the backend propage the disabled state to dependent cells - await run_cell() + await set_cell_disabled(new_val) } const toggle_logs = () => set_show_logs(!show_logs) diff --git a/frontend/components/RunArea.js b/frontend/components/RunArea.js index e17303a8c4..2c3bca724e 100644 --- a/frontend/components/RunArea.js +++ b/frontend/components/RunArea.js @@ -3,8 +3,20 @@ import { html, useContext, useEffect, useMemo, useState } from "../imports/Preac import { in_textarea_or_input } from "../common/KeyboardShortcuts.js" import { PlutoActionsContext } from "../common/PlutoContext.js" +import { open_pluto_popup } from "./Popup.js" -export const RunArea = ({ runtime, running, queued, code_differs, on_run, on_interrupt, depends_on_disabled_cells, running_disabled, on_jump }) => { +export const RunArea = ({ + runtime, + running, + queued, + code_differs, + on_run, + on_interrupt, + set_cell_disabled, + depends_on_disabled_cells, + running_disabled, + on_jump, +}) => { const on_save = on_run /* because disabled cells save without running */ const local_time_running_ms = useMillisSinceTruthy(running) @@ -27,9 +39,31 @@ export const RunArea = ({ runtime, running, queued, code_differs, on_run, on_int run: "Run cell", } + const on_double_click = (/** @type {MouseEvent} */ e) => { + console.log(running_disabled) + if (running_disabled) + open_pluto_popup({ + type: "info", + source_element: /** @type {HTMLElement?} */ (e.target), + body: html`${`This cell is disabled. `} +
    { + //@ts-ignore + set_cell_disabled(false) + + e.preventDefault() + window.dispatchEvent(new CustomEvent("close pluto popup")) + }} + >Enable this cell + ${` to run the code.`}`, + }) + } + return html` - ${prettytime(running ? local_time_running_ns ?? runtime : runtime)} From 7adc68991d7b9d2da35a9b9d14c74c936d5fe474 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 19 Sep 2022 19:01:04 +0200 Subject: [PATCH 419/821] Add query suggestions in live docs (#2282) * Handle 1.8 invalid module display Introduced in https://github.com/JuliaLang/julia/pull/43932 * Show similar query suggestions in live docs * Add test --- frontend/components/CellOutput.js | 3 +- frontend/components/LiveDocs.js | 2 ++ src/runner/PlutoRunner.jl | 53 +++++++++++++++++++++++++++++-- src/webserver/REPLTools.jl | 4 +-- test/MacroAnalysis.jl | 9 ++++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js index 6f10ccf613..2923c3b8df 100644 --- a/frontend/components/CellOutput.js +++ b/frontend/components/CellOutput.js @@ -514,7 +514,7 @@ export let RawHTMLContainer = ({ body, className = "", persist_js_state = false, try { container.querySelectorAll("code").forEach((code_element) => { code_element.classList.forEach((className) => { - if (className.startsWith("language-")) { + if (className.startsWith("language-") && !className.endsWith("undefined")) { // Remove "language-" let language = className.substring(9) highlight(code_element, language) @@ -561,6 +561,7 @@ export let highlight = (code_element, language) => { // its shows up as a comment and can be confusing doc: code_element.innerText .trim() + .replace(/Main.var\"workspace#\d+\"\./, "") .replace(/Main.workspace#\d+\./, "") .replace(/Main.workspace#(\d+)/, 'Main.var"workspace#$1"'), diff --git a/frontend/components/LiveDocs.js b/frontend/components/LiveDocs.js index ba75d01b4c..8de2d639a9 100644 --- a/frontend/components/LiveDocs.js +++ b/frontend/components/LiveDocs.js @@ -8,7 +8,9 @@ import { PlutoActionsContext } from "../common/PlutoContext.js" const without_workspace_stuff = (str) => str + .replace(/Main\.var"workspace\#\d+"\./g, "") // remove workspace modules from variable names .replace(/Main\.workspace\#\d+\./g, "") // remove workspace modules from variable names + .replace(/ in Main\.var"workspace\#\d+"/g, "") // remove workspace modules from method lists .replace(/ in Main\.workspace\#\d+/g, "") // remove workspace modules from method lists .replace(/#==#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\:\d+/g, "") // remove UUIDs from filenames diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index d597748fe0..c5df1cb1ab 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -28,6 +28,7 @@ import Base: show, istextmime import UUIDs: UUID, uuid4 import Dates: DateTime import Logging +import REPL export @bind @@ -1737,10 +1738,33 @@ binding_from(s::Symbol, workspace::Module) = Docs.Binding(workspace, s) binding_from(r::GlobalRef, workspace::Module) = Docs.Binding(r.mod, r.name) binding_from(other, workspace::Module) = error("Invalid @var syntax `$other`.") +const DOC_SUGGESTION_LIMIT = 10 + +struct Suggestion + match::String + query::String +end + +# inspired from REPL.printmatch() +function Base.show(io::IO, ::MIME"text/html", suggestion::Suggestion) + print(io, "") + is, _ = REPL.bestmatch(suggestion.query, suggestion.match) + for (i, char) in enumerate(suggestion.match) + esc_c = get(Markdown._htmlescape_chars, char, char) + if i in is + print(io, "", esc_c, "") + else + print(io, esc_c) + end + end + print(io, "") +end + "You say doc_fetcher, I say You say doc_fetcher, I say You say doc_fetcher, I say You say doc_fetcher, I say ...!!!!" function doc_fetcher(query, workspace::Module) try - value = binding_from(Meta.parse(query), workspace) + parsed_query = Meta.parse(query) + value = binding_from(parsed_query, workspace) doc_md = Docs.doc(value) if !showable(MIME("text/html"), doc_md) @@ -1748,7 +1772,32 @@ function doc_fetcher(query, workspace::Module) # which is a bit silly, but turns out it actuall is markdown if you look hard enough. doc_md = Markdown.parse(repr(doc_md)) end - + + # Add suggestions results if no docstring was found + if parsed_query isa Symbol && + !Docs.defined(value) && + doc_md isa Markdown.MD && + haskey(doc_md.meta, :results) && + isempty(doc_md.meta[:results]) + + suggestions = REPL.accessible(workspace) + suggestions_scores = map(s -> REPL.fuzzyscore(query, s), suggestions) + removed_indices = [i for (i, s) in enumerate(suggestions_scores) if s < 0] + deleteat!(suggestions_scores, removed_indices) + deleteat!(suggestions, removed_indices) + + perm = sortperm(suggestions_scores; lt=Base.:>) + permute!(suggestions, perm) + links = map(s -> Suggestion(s, query), @view(suggestions[begin:min(end,DOC_SUGGESTION_LIMIT)])) + + if length(links) > 0 + push!(doc_md.content, + Markdown.HorizontalRule(), + Markdown.Paragraph(["Similar result$(length(links) > 1 ? "s" : ""):"]), + Markdown.List(links)) + end + end + (repr(MIME("text/html"), doc_md), :👍) catch ex (nothing, :👎) diff --git a/src/webserver/REPLTools.jl b/src/webserver/REPLTools.jl index d1147bb8de..d6a09b7c93 100644 --- a/src/webserver/REPLTools.jl +++ b/src/webserver/REPLTools.jl @@ -117,9 +117,9 @@ responses[:docs] = function response_docs(🙋::ClientRequest) query = "@$(query[begin:end-1])_str" end - doc_html, status = if REPL.lookup_doc(Symbol(query)) isa Markdown.MD + doc_html, status = if (doc_md = Docs.doc(Docs.Binding(Base, Symbol(query)))) isa Markdown.MD && + haskey(doc_md.meta, :results) && !isempty(doc_md.meta[:results]) # available in Base, no need to ask worker - doc_md = REPL.lookup_doc(Symbol(query)) (repr(MIME("text/html"), doc_md), :👍) else workspace = WorkspaceManager.get_workspace((🙋.session, 🙋.notebook); allow_creation=false) diff --git a/test/MacroAnalysis.jl b/test/MacroAnalysis.jl index 396c732b22..d96d15cd82 100644 --- a/test/MacroAnalysis.jl +++ b/test/MacroAnalysis.jl @@ -826,6 +826,15 @@ import Memoize: @memoize trigger, bool, int = notebook.cells + workspace = WorkspaceManager.get_workspace((🍭, notebook)) + workspace_module = getproperty(Main, workspace.module_name) + + # Propose suggestions when no binding is found + doc_content, status = PlutoRunner.doc_fetcher("filer", workspace_module) + @test status == :👍 + @test occursin("Similar results:", doc_content) + @test occursin("filter", doc_content) + update_run!(🍭, notebook, notebook.cells) @test all(noerror, notebook.cells) @test occursin("::Bool", bool.output.body) From 73bb1abc5980e4defc7a41bf1c40547f5fa54d4f Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 19 Sep 2022 22:57:45 +0200 Subject: [PATCH 420/821] Replace search param with hash in filename (#2284) --- .../parcel-resolver-like-a-browser/https-resolver.js | 11 +++++++++-- src/webserver/Static.jl | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend-bundler/parcel-resolver-like-a-browser/https-resolver.js b/frontend-bundler/parcel-resolver-like-a-browser/https-resolver.js index 864687547d..d17540df00 100644 --- a/frontend-bundler/parcel-resolver-like-a-browser/https-resolver.js +++ b/frontend-bundler/parcel-resolver-like-a-browser/https-resolver.js @@ -4,6 +4,7 @@ let fetch = require("node-fetch") let fs = require("fs/promises") let mkdirp = require("mkdirp") let { URL } = require("url") +let crypto = require("crypto") let DONT_INCLUDE = { isExcluded: true } @@ -61,12 +62,18 @@ module.exports = new Resolver({ let found_extension = /\.[a-zA-Z][a-zA-Z0-9]+$/.exec(url.pathname)?.[0] let extension_to_add = found_extension ?? (dependency.specifierType === "esm" ? ".mjs" : "") + + let search_component = "" + if (url.search !== "") { + search_component = "." + crypto.createHmac("sha256", "42").update(url.search).digest("hex").slice(0, 10) + } + // If a search is given in the URL, this will search be appended to the path, so we need to repeat the extension. - let should_add_extension = url.search !== "" || found_extension == null + let should_add_extension = search_component !== "" || found_extension == null let suffix = should_add_extension ? extension_to_add : "" // Create a folder structure and file for the import. This folder structure will match the URL structure, to make sure that relative imports still work. - let filename_parts = (url.pathname.slice(1) + encodeURIComponent(url.search) + suffix).split("/") + let filename_parts = (url.pathname.slice(1) + search_component + suffix).split("/") let url_to_path = path.join(url.protocol.slice(0, -1), url.hostname, ...filename_parts) let fullpath = path.join(my_temp_cave, url_to_path) let folder = path.dirname(fullpath) diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index acb767e494..7fa00c51c2 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -369,7 +369,7 @@ function http_router_for(session::ServerSession) function serve_asset(request::HTTP.Request) uri = HTTP.URI(request.target) - filepath = project_relative_path(frontend_directory(), relpath(uri.path, "/")) + filepath = project_relative_path(frontend_directory(), relpath(HTTP.unescapeuri(uri.path), "/")) asset_response(filepath; cacheable=should_cache(filepath)) end HTTP.register!(router, "GET", "/**", serve_asset) From 41f9cfbb4d75c16ce662d14c1bac61192b0f71f4 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 20 Sep 2022 14:21:00 +0200 Subject: [PATCH 421/821] tweak test i forgot why i did this yesterday --- test/Analysis.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Analysis.jl b/test/Analysis.jl index e45bf13023..1c1efa9d68 100644 --- a/test/Analysis.jl +++ b/test/Analysis.jl @@ -124,6 +124,7 @@ import Pluto: Notebook, Cell, updated_topology, static_resolve_topology, is_just codes=topo_3.codes, unresolved_cells=setdiff(topo_3.unresolved_cells, notebook.cells[4:4]), cell_order=topo_3.cell_order, + disabled_cells=topo_3.disabled_cells, ) @test topo_3_resolved === updated_topology(topo_3_resolved, notebook, notebook.cells[1:3]) From 10ad307bba97c4ead8cfb17f6b5089fb098690b4 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 20 Sep 2022 15:47:57 +0200 Subject: [PATCH 422/821] Use HTTP.setheader instead of push!(response.headers --- src/webserver/Static.jl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/webserver/Static.jl b/src/webserver/Static.jl index 7fa00c51c2..0a3e00863e 100644 --- a/src/webserver/Static.jl +++ b/src/webserver/Static.jl @@ -53,10 +53,10 @@ function asset_response(path; cacheable::Bool=false) if isfile(path) data = read(path) response = HTTP.Response(200, data) - push!(response.headers, "Content-Type" => MIMEs.contenttype_from_mime(MIMEs.mime_from_path(path, MIME"application/octet-stream"()))) - push!(response.headers, "Content-Length" => string(length(data))) - push!(response.headers, "Access-Control-Allow-Origin" => "*") - cacheable && push!(response.headers, "Cache-Control" => "public, max-age=$(30day), immutable") + HTTP.setheader(response, "Content-Type" => MIMEs.contenttype_from_mime(MIMEs.mime_from_path(path, MIME"application/octet-stream"()))) + HTTP.setheader(response, "Content-Length" => string(length(data))) + HTTP.setheader(response, "Access-Control-Allow-Origin" => "*") + cacheable && HTTP.setheader(response, "Cache-Control" => "public, max-age=$(30day), immutable") response else default_404() @@ -76,14 +76,14 @@ function error_response( "\$BODY" => htmlesc(body)) response = HTTP.Response(status_code, filled_in) - push!(response.headers, "Content-Type" => MIMEs.contenttype_from_mime(MIME"text/html"())) + HTTP.setheader(response, "Content-Type" => MIMEs.contenttype_from_mime(MIME"text/html"())) response end function notebook_response(notebook; home_url="./", as_redirect=true) if as_redirect response = HTTP.Response(302, "") - push!(response.headers, "Location" => home_url * "edit?id=" * string(notebook.notebook_id)) + HTTP.setheader(response, "Location" => home_url * "edit?id=" * string(notebook.notebook_id)) return response else HTTP.Response(200, string(notebook.notebook_id)) @@ -147,7 +147,7 @@ function http_router_for(session::ServerSession) security = session.options.security function add_set_secret_cookie!(response::HTTP.Response) - push!(response.headers, "Set-Cookie" => "secret=$(session.secret); SameSite=Strict; HttpOnly") + HTTP.setheader(response, "Set-Cookie" => "secret=$(session.secret); SameSite=Strict; HttpOnly") response end @@ -166,7 +166,7 @@ function http_router_for(session::ServerSession) add_set_secret_cookie!(response) if !required filter!(p -> p[1] != "Access-Control-Allow-Origin", response.headers) - push!(response.headers, "Access-Control-Allow-Origin" => "*") + HTTP.setheader(response, "Access-Control-Allow-Origin" => "*") end response else @@ -305,8 +305,8 @@ function http_router_for(session::ServerSession) try notebook = notebook_from_uri(request) response = HTTP.Response(200, sprint(save_notebook, notebook)) - push!(response.headers, "Content-Type" => "text/julia; charset=utf-8") - push!(response.headers, "Content-Disposition" => "inline; filename=\"$(basename(notebook.path))\"") + HTTP.setheader(response, "Content-Type" => "text/julia; charset=utf-8") + HTTP.setheader(response, "Content-Disposition" => "inline; filename=\"$(basename(notebook.path))\"") response catch e return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) @@ -321,8 +321,8 @@ function http_router_for(session::ServerSession) try notebook = notebook_from_uri(request) response = HTTP.Response(200, Pluto.pack(Pluto.notebook_to_js(notebook))) - push!(response.headers, "Content-Type" => "application/octet-stream") - push!(response.headers, "Content-Disposition" => "inline; filename=\"$(without_pluto_file_extension(basename(notebook.path))).plutostate\"") + HTTP.setheader(response, "Content-Type" => "application/octet-stream") + HTTP.setheader(response, "Content-Disposition" => "inline; filename=\"$(without_pluto_file_extension(basename(notebook.path))).plutostate\"") response catch e return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) @@ -337,8 +337,8 @@ function http_router_for(session::ServerSession) try notebook = notebook_from_uri(request) response = HTTP.Response(200, generate_html(notebook)) - push!(response.headers, "Content-Type" => "text/html; charset=utf-8") - push!(response.headers, "Content-Disposition" => "inline; filename=\"$(basename(notebook.path)).html\"") + HTTP.setheader(response, "Content-Type" => "text/html; charset=utf-8") + HTTP.setheader(response, "Content-Disposition" => "inline; filename=\"$(basename(notebook.path)).html\"") response catch e return error_response(400, "Bad query", "Please report this error!", sprint(showerror, e, stacktrace(catch_backtrace()))) From bea076825a60a862e9b183d5f1bf624796eee1f0 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 20 Sep 2022 16:39:55 +0200 Subject: [PATCH 423/821] Show loading screen on main menu when navigating away (#2292) --- frontend/components/welcome/Open.js | 7 +-- frontend/components/welcome/Recent.js | 11 +++-- frontend/components/welcome/Welcome.js | 64 +++++++++++++++++--------- frontend/index.css | 14 ++++++ frontend/index.js | 1 + 5 files changed, 69 insertions(+), 28 deletions(-) diff --git a/frontend/components/welcome/Open.js b/frontend/components/welcome/Open.js index 3c513fd90a..018defd6fd 100644 --- a/frontend/components/welcome/Open.js +++ b/frontend/components/welcome/Open.js @@ -11,17 +11,18 @@ import { guess_notebook_location } from "../../common/NotebookLocationFromURL.js * connected: Boolean, * show_samples: Boolean, * CustomPicker: {text: String, placeholder: String}?, + * on_start_navigation: (string) => void, * }} props */ -export const Open = ({ client, connected, CustomPicker, show_samples }) => { +export const Open = ({ client, connected, CustomPicker, show_samples, on_start_navigation }) => { const on_open_path = async (new_path) => { const processed = await guess_notebook_location(new_path) if (processed.type === "path") { - document.body.classList.add("loading") + on_start_navigation(processed.path_or_url) window.location.href = link_open_path(processed.path_or_url) } else { if (confirm("Are you sure? This will download and run the file at\n\n" + processed.path_or_url)) { - document.body.classList.add("loading") + on_start_navigation(processed.path_or_url) window.location.href = link_open_url(processed.path_or_url) } } diff --git a/frontend/components/welcome/Recent.js b/frontend/components/welcome/Recent.js index 815987456e..ac81a507c6 100644 --- a/frontend/components/welcome/Recent.js +++ b/frontend/components/welcome/Recent.js @@ -48,9 +48,10 @@ const shortest_path = (path, allpaths) => { * connected: Boolean, * remote_notebooks: Array, * CustomRecent: preact.ReactElement?, + * on_start_navigation: (string) => void, * }} props */ -export const Recent = ({ client, connected, remote_notebooks, CustomRecent }) => { +export const Recent = ({ client, connected, remote_notebooks, CustomRecent, on_start_navigation }) => { const [combined_notebooks, set_combined_notebooks] = useState(/** @type {Array?} */ (null)) const combined_notebooks_ref = useRef(combined_notebooks) combined_notebooks_ref.current = combined_notebooks @@ -188,7 +189,7 @@ export const Recent = ({ client, connected, remote_notebooks, CustomRecent }) => title=${nb.path} onClick=${(e) => { if (!running) { - document.body.classList.add("loading") + on_start_navigation(shortest_path(nb.path, all_paths)) set_notebook_state(nb.path, { transitioning: true, }) @@ -204,7 +205,11 @@ export const Recent = ({ client, connected, remote_notebooks, CustomRecent }) =>

    My work

    • - { + on_start_navigation("new notebook") + }} >Create a new notebook
    • diff --git a/frontend/components/welcome/Welcome.js b/frontend/components/welcome/Welcome.js index 2b58bc3177..f8b49a617c 100644 --- a/frontend/components/welcome/Welcome.js +++ b/frontend/components/welcome/Welcome.js @@ -1,5 +1,5 @@ import _ from "../../imports/lodash.js" -import { html, useEffect, useState, useRef } from "../../imports/Preact.js" +import { html, useEffect, useState, useRef, useLayoutEffect } from "../../imports/Preact.js" import * as preact from "../../imports/Preact.js" import { create_pluto_connection } from "../../common/PlutoConnection.js" @@ -75,27 +75,47 @@ export const Welcome = () => { const { show_samples, CustomRecent, CustomPicker } = extended_components - return html` -
      -

      welcome to

      - -
      -
      -
      - <${Recent} client=${client_ref.current} connected=${connected} remote_notebooks=${remote_notebooks} CustomRecent=${CustomRecent} /> -
      -
      -
      -
      - <${Open} client=${client_ref.current} connected=${connected} CustomPicker=${CustomPicker} show_samples=${show_samples} /> -
      -
      - - ` +
    +
    +
    + <${Recent} + client=${client_ref.current} + connected=${connected} + remote_notebooks=${remote_notebooks} + CustomRecent=${CustomRecent} + on_start_navigation=${set_navigation_away} + /> +
    +
    +
    +
    + <${Open} + client=${client_ref.current} + connected=${connected} + CustomPicker=${CustomPicker} + show_samples=${show_samples} + on_start_navigation=${set_navigation_away} + /> +
    +
    + + ` } diff --git a/frontend/index.css b/frontend/index.css index da21f1be91..65cd31760a 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -422,3 +422,17 @@ featured-card.special::before { /* border: 2px solid #ffca62; */ pointer-events: none; } + +.navigating-away-banner { + width: 100vw; + min-height: 70vh; + place-content: center; + display: grid; + padding: 3em; +} + +.navigating-away-banner h2 { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} diff --git a/frontend/index.js b/frontend/index.js index 4ba142026d..bffd178ba6 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -3,4 +3,5 @@ import "./common/NodejsCompatibilityPolyfill.js" import { Welcome } from "./components/welcome/Welcome.js" +// @ts-ignore render(html`<${Welcome} />`, document.querySelector("#app")) From 22045d21f88a90ea3c9ea606b6e6630b5bec81e5 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 14:54:51 +0200 Subject: [PATCH 424/821] Workaround Ctrl+C on Julia 1.8 (#2293) --- src/webserver/SessionActions.jl | 4 ++-- src/webserver/WebServer.jl | 24 +++++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/webserver/SessionActions.jl b/src/webserver/SessionActions.jl index 1904c63017..e1288a75ef 100644 --- a/src/webserver/SessionActions.jl +++ b/src/webserver/SessionActions.jl @@ -103,12 +103,11 @@ function add(session::ServerSession, nb::Notebook; run_async::Bool=true) @info "Updating from file..." - sleep(0.1) ## There seems to be a synchronization issue if your OS is VERYFAST wait_until_file_unchanged(nb.path, .3) # call update_from_file. If it returns false, that means that the notebook file was corrupt, so we try again, a maximum of 10 times. - for i in 1:10 + for _ in 1:10 if update_from_file(session, nb) break end @@ -122,6 +121,7 @@ function add(session::ServerSession, nb::Notebook; run_async::Bool=true) end in_session() = get(session.notebooks, nb.notebook_id, nothing) === nb + session.options.server.auto_reload_from_file && @asynclog try while in_session() if !isfile(nb.path) diff --git a/src/webserver/WebServer.jl b/src/webserver/WebServer.jl index 7f2a04640e..f8ea1b18fd 100644 --- a/src/webserver/WebServer.jl +++ b/src/webserver/WebServer.jl @@ -146,7 +146,7 @@ function run(session::ServerSession) on_shutdown() = @sync begin # Triggered by HTTP.jl - @info("\n\nClosing Pluto... Restart Julia for a fresh session. \n\nHave a nice day! 🎈\n\n") + @info("\nClosing Pluto... Restart Julia for a fresh session. \n\nHave a nice day! 🎈\n\n") # TODO: put do_work tokens back @async swallow_exception(() -> close(serversocket), Base.IOError) for client in values(session.connected_clients) @@ -293,7 +293,7 @@ function run(session::ServerSession) end # Start this in the background, so that the first notebook launch (which will trigger registry update) will be faster - @asynclog withtoken(pkg_token) do + initial_registry_update_task = @asynclog withtoken(pkg_token) do will_update = !PkgCompat.check_registry_age() PkgCompat.update_registries(; force = false) will_update && println(" Updating registry done ✓") @@ -301,17 +301,19 @@ function run(session::ServerSession) try # create blocking call and switch the scheduler back to the server task, so that interrupts land there - wait(server) - catch e - if e isa InterruptException - close(server) - elseif e isa TaskFailedException - @debug "Error is " exception = e stacktrace = catch_backtrace() - # nice! - else - rethrow(e) + while isopen(server) + sleep(.1) end + catch e + println() + println() + close(server) + wait(server) + wait(initial_registry_update_task) + (e isa InterruptException) || rethrow(e) end + + nothing end precompile(run, (ServerSession, HTTP.Handlers.Router{Symbol("##001")})) From 3f2aa4b8a0a36fa8aa2de67a006cf3bb7a06cdad Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 15:03:25 +0200 Subject: [PATCH 425/821] Fix auto scrolling during multi cell selection --- frontend/components/SelectionArea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/SelectionArea.js b/frontend/components/SelectionArea.js index 6a0f514c7c..7f145bb7dd 100644 --- a/frontend/components/SelectionArea.js +++ b/frontend/components/SelectionArea.js @@ -124,7 +124,7 @@ export const SelectionArea = ({ on_selection, set_scroller, cell_order }) => { return A.start_left < B.end_left && A.end_left > B.start_left && A.start_top < B.end_top && A.end_top > B.start_top }) - set_scroller({ up: selection.start.y > new_selection_end.y, down: selection.start.y < new_selection_end.y }) + set_scroller({ up: true, down: true }) on_selection(in_selection.map((x) => x.id)) set_selection({ start: selection.start, end: new_selection_end }) }) From a6279f3ce6041c782c42543ba88cb1b72879a3d7 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 15:12:25 +0200 Subject: [PATCH 426/821] Fix #2271 --- frontend/editor.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/editor.css b/frontend/editor.css index 95fbf24223..ace8f5e3ff 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -1577,7 +1577,8 @@ pluto-input > button.input_context_menu ul { z-index: 31; } pluto-input > button.input_context_menu ul { - left: calc(100% - var(--width) - 36px); + right: 0px; + left: unset; } } .input_context_menu li { From ea7ccff3f28ac79d13e77486eea4aa7f58562a39 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 15:14:18 +0200 Subject: [PATCH 427/821] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 47ea4bcaf7..ca1a7306cc 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Pluto" uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781" license = "MIT" authors = ["Fons van der Plas "] -version = "0.19.11" +version = "0.19.12" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From 61b80d9d4068818c4521f77522eb72d3ddf1003b Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 15:27:15 +0200 Subject: [PATCH 428/821] =?UTF-8?q?=F0=9F=A7=B9=20remove=20fakeclient=20fr?= =?UTF-8?q?om=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/packages/Basic.jl | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index 4cbdb566d0..ca73ee615b 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -24,9 +24,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; # We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. @testset "Basic" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient # See https://github.com/JuliaPluto/PlutoPkgTestRegistry @@ -44,7 +42,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; Cell("eval(:(import DataFrames))"), Cell("import HelloWorldC_jll"), ]) - fakeclient.connected_notebook = notebook @test !notebook.nbpkg_ctx_instantiated @@ -224,16 +221,13 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; simple_import_notebook = read(simple_import_path, String) @testset "Manifest loading" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient dir = mktempdir() path = joinpath(dir, "hello.jl") write(path, simple_import_notebook) notebook = SessionActions.open(🍭, path; run_async=false) - fakeclient.connected_notebook = notebook @test num_backups_in(dir) == 0 @@ -252,10 +246,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @testset "Pkg cell -- dynamically added" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = Notebook([ Cell("1"), @@ -265,7 +256,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; Cell("5"), Cell("6"), ]) - fakeclient.connected_notebook = notebook update_save_run!(🍭, notebook, notebook.cells) @@ -309,11 +299,8 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; end pkg_cell_notebook = read(joinpath(@__DIR__, "pkg_cell.jl"), String) - @testset "Pkg cell -- loaded from file" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient dir = mktempdir() for n in ["Project.toml", "Manifest.toml"] @@ -326,7 +313,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test num_backups_in(dir) == 0 notebook = SessionActions.open(🍭, path; run_async=false) - fakeclient.connected_notebook = notebook nb_contents() = read(notebook.path, String) @test num_backups_in(dir) == 0 @@ -368,17 +354,14 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; end @testset "DrWatson cell" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - 🍭.connected_clients[fakeclient.id] = fakeclient notebook = Notebook([ Cell("using Plots"), Cell("@quickactivate"), Cell("using DrWatson"), ]) - fakeclient.connected_notebook = notebook notebook.topology = Pluto.updated_topology(Pluto.NotebookTopology(cell_order=Pluto.ImmutableVector(notebook.cells)), notebook, notebook.cells) |> Pluto.static_resolve_topology @@ -392,13 +375,9 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; end pre_pkg_notebook = read(joinpath(@__DIR__, "old_import.jl"), String) - local post_pkg_notebook = nothing - @testset "File format -- Backwards compat" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient dir = mktempdir() path = joinpath(dir, "hello.jl") @@ -407,7 +386,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test num_backups_in(dir) == 0 notebook = SessionActions.open(🍭, path; run_async=false) - fakeclient.connected_notebook = notebook nb_contents() = read(notebook.path, String) @test num_backups_in(dir) == 0 @@ -510,9 +488,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; original_path = joinpath(@__DIR__, "$(name).jl") original_contents = read(original_path, String) - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient dir = mktempdir() path = joinpath(dir, "hello.jl") @@ -521,7 +497,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; @test num_backups_in(dir) == 0 notebook = SessionActions.open(🍭, path; run_async=false) - fakeclient.connected_notebook = notebook nb_contents() = read(notebook.path, String) should_restart = ( @@ -606,9 +581,7 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; # end @testset "Race conditions" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() - 🍭.connected_clients[fakeclient.id] = fakeclient lag = 0.2 🍭.options.server.simulated_pkg_lag = lag @@ -626,7 +599,6 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; Cell("import PlutoPkgTestE"), # cell 9 Cell("PlutoPkgTestE.MY_VERSION |> Text"), ]) - fakeclient.connected_notebook = notebook @test !notebook.nbpkg_ctx_instantiated From 2750fa0500d5ea3649bda36e0f7a468730454429 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 16:49:28 +0200 Subject: [PATCH 429/821] Recover gracefully from loading a notebook with non-existing versions (#2294) --- src/packages/Packages.jl | 12 ++- src/packages/PkgCompat.jl | 13 +++ test/packages/Basic.jl | 25 ++++++ test/packages/future_nonexisting_version.jl | 98 +++++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 test/packages/future_nonexisting_version.jl diff --git a/src/packages/Packages.jl b/src/packages/Packages.jl index bbd91b4762..c621924e6f 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -120,8 +120,16 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new Pkg.resolve(notebook.nbpkg_ctx) catch e @warn "Failed to resolve Pkg environment. Removing Manifest and trying again..." exception=e - reset_nbpkg(notebook, new_topology; keep_project=true, save=false, backup=false) - Pkg.resolve(notebook.nbpkg_ctx) + reset_nbpkg(notebook; keep_project=true, save=false, backup=false) + try + Pkg.resolve(notebook.nbpkg_ctx) + catch e + @warn "Failed to resolve Pkg environment. Removing Project compat entires and Manifest and trying again..." exception=e + reset_nbpkg(notebook; keep_project=true, save=false, backup=false) + notebook.nbpkg_ctx = PkgCompat.clear_compat_entries(notebook.nbpkg_ctx) + + Pkg.resolve(notebook.nbpkg_ctx) + end end end end diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 000cb3a8f6..b0766a641b 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -461,6 +461,19 @@ function write_auto_compat_entries(ctx::PkgContext)::PkgContext end +# ✅ Public API +""" +Remove all [`compat`](https://pkgdocs.julialang.org/v1/compatibility/) entries from the `Project.toml`. +""" +function clear_compat_entries(ctx::PkgContext)::PkgContext + if isfile(project_file(ctx)) + _modify_compat(empty!, ctx) + else + ctx + 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`. diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index ca73ee615b..f64466899f 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -243,6 +243,31 @@ const pluto_test_registry_spec = Pkg.RegistrySpec(; WorkspaceManager.unmake_workspace((🍭, notebook)) end + + future_notebook = read(joinpath(@__DIR__, "future_nonexisting_version.jl"), String) + @testset "Recovery from unavailable versions" begin + 🍭 = ServerSession() + + dir = mktempdir() + path = joinpath(dir, "hello.jl") + write(path, future_notebook) + + notebook = SessionActions.open(🍭, path; run_async=false) + + @test num_backups_in(dir) == 0 + + + @test notebook.nbpkg_ctx !== nothing + @test notebook.nbpkg_restart_recommended_msg === nothing + @test notebook.nbpkg_restart_required_msg === nothing + + @test noerror(notebook.cells[1]) + @test noerror(notebook.cells[2]) + + @test notebook.cells[2].output.body == "0.3.1" + + WorkspaceManager.unmake_workspace((🍭, notebook)) + end @testset "Pkg cell -- dynamically added" begin diff --git a/test/packages/future_nonexisting_version.jl b/test/packages/future_nonexisting_version.jl new file mode 100644 index 0000000000..8b2093e210 --- /dev/null +++ b/test/packages/future_nonexisting_version.jl @@ -0,0 +1,98 @@ +### A Pluto.jl notebook ### +# v0.15.0 + +using Markdown +using InteractiveUtils + +# ╔═╡ c581d17a-c965-11eb-1607-bbeb44933d25 +# This file imports a future version of PlutoPkgTestA: 99.99.99, which does not actually exist. + +# It is generated by modifying the simple_import.jl file by hand. + +import PlutoPkgTestA + +# ╔═╡ aef57966-ea36-478f-8724-e71430f10be9 +PlutoPkgTestA.MY_VERSION |> Text + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +PlutoPkgTestA = "419c6f8d-b8cd-4309-abdc-cee491252f94" + +[compat] +PlutoPkgTestA = "~99.99.99" +""" + +# ╔═╡ 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" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[LibGit2]] +deps = ["Printf"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[Pkg]] +deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" + +[[PlutoPkgTestA]] +deps = ["Pkg"] +git-tree-sha1 = "6c9aa67135641123c559d59ba88e8cb938999999" +uuid = "419c6f8d-b8cd-4309-abdc-cee491252f94" +version = "99.99.99" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +""" + +# ╔═╡ Cell order: +# ╠═c581d17a-c965-11eb-1607-bbeb44933d25 +# ╠═aef57966-ea36-478f-8724-e71430f10be9 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From 289c1d61022dd218af8e90d58e8967b44b101aee Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 16:49:40 +0200 Subject: [PATCH 430/821] Compare metadata when reloading from file (#2291) --- src/evaluation/Run.jl | 33 ++++++++++++-------------- src/notebook/Cell.jl | 5 ++++ test/ReloadFromFile.jl | 54 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index b3b878c06b..2d79cc6460 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -398,22 +398,18 @@ update_save_run!(session::ServerSession, notebook::Notebook, cell::Cell; kwargs. update_run!(args...; kwargs...) = update_save_run!(args...; save=false, kwargs...) function notebook_differences(from::Notebook, to::Notebook) - old_codes = Dict( - id => c.code - for (id,c) in from.cells_dict - ) - new_codes = Dict( - id => c.code - for (id,c) in to.cells_dict - ) + from_cells = from.cells_dict + to_cells = to.cells_dict ( # it's like D3 joins: https://observablehq.com/@d3/learn-d3-joins#cell-528 - added = setdiff(keys(new_codes), keys(old_codes)), - removed = setdiff(keys(old_codes), keys(new_codes)), + added = setdiff(keys(to_cells), keys(from_cells)), + removed = setdiff(keys(from_cells), keys(to_cells)), changed = let - remained = keys(old_codes) ∩ keys(new_codes) - filter(id -> old_codes[id] != new_codes[id], remained) + remained = keys(from_cells) ∩ keys(to_cells) + filter(remained) do id + from_cells[id].code != to_cells[id].code || from_cells[id].metadata != to_cells[id].metadata + end end, order_changed = from.cell_order != to.cell_order, @@ -438,11 +434,6 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...) return false end::Notebook - new_codes = Dict( - id => c.code - for (id,c) in just_loaded.cells_dict - ) - d = notebook_differences(notebook, just_loaded) added = d.added @@ -469,10 +460,16 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...) delete!(notebook.cells_dict, c) end for c in changed - notebook.cells_dict[c].code = new_codes[c] + notebook.cells_dict[c].code = just_loaded.cells_dict[c].code + notebook.cells_dict[c].metadata = just_loaded.cells_dict[c].metadata end + for c in keys(notebook.cells_dict) ∩ keys(just_loaded.cells_dict) + notebook.cells_dict[c].code_folded = just_loaded.cells_dict[c].code_folded + end + notebook.cell_order = just_loaded.cell_order + notebook.metadata = just_loaded.metadata if include_nbpg && nbpkg_changed @info "nbpkgs not equal" (notebook.nbpkg_ctx isa Nothing) (just_loaded.nbpkg_ctx isa Nothing) diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index e90b4194d3..ef8313c28b 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -81,6 +81,11 @@ end "Returns whether or not the cell is **explicitely** disabled." is_disabled(c::Cell) = get(c.metadata, METADATA_DISABLED_KEY, DEFAULT_CELL_METADATA[METADATA_DISABLED_KEY]) +set_disabled(c::Cell, value::Bool) = if value == DEFAULT_CELL_METADATA[METADATA_DISABLED_KEY] + delete!(c.metadata, METADATA_DISABLED_KEY) +else + c.metadata[METADATA_DISABLED_KEY] = value +end can_show_logs(c::Cell) = get(c.metadata, METADATA_SHOW_LOGS_KEY, DEFAULT_CELL_METADATA[METADATA_SHOW_LOGS_KEY]) is_skipped_as_script(c::Cell) = get(c.metadata, METADATA_SKIP_AS_SCRIPT_KEY, DEFAULT_CELL_METADATA[METADATA_SKIP_AS_SCRIPT_KEY]) must_be_commented_in_file(c::Cell) = is_disabled(c) || is_skipped_as_script(c) || c.depends_on_disabled_cells || c.depends_on_skipped_cells \ No newline at end of file diff --git a/test/ReloadFromFile.jl b/test/ReloadFromFile.jl index a6a5eadbb6..2ebc96f047 100644 --- a/test/ReloadFromFile.jl +++ b/test/ReloadFromFile.jl @@ -1,5 +1,5 @@ using Test -import Pluto: Configuration, Notebook, ServerSession, ClientSession, update_run!, Cell, WorkspaceManager, SessionActions +import Pluto: Configuration, Notebook, ServerSession, ClientSession, update_run!, Cell, WorkspaceManager, SessionActions, save_notebook import Pluto.Configuration: Options, EvaluationOptions import Distributed using Pluto.WorkspaceManager: poll @@ -85,14 +85,62 @@ import Pkg # notebook order reversed again, but cell should not re-run @test original_rand_output == notebook.cells[3].output.body - + + ### sleep(timeout_between_tests) - file4 = read(joinpath(@__DIR__, "packages", "simple_stdlib_import.jl"), String) + file4 = read(notebook.path, String) + notebook.cells[3].code_folded = true + save_notebook(notebook) + sleep(timeout_between_tests) + + file5 = read(notebook.path, String) + @test file4 != file5 + + @test notebook.cells[3].code_folded write(notebook.path, file4) + @test poll(10) do + notebook.cells[3].code_folded == false + end + + # cell folded, but cell should not re-run + @test original_rand_output == notebook.cells[3].output.body + + + + ### + sleep(timeout_between_tests) + + + file6 = read(notebook.path, String) + Pluto.set_disabled(notebook.cells[3], true) + save_notebook(notebook) + sleep(timeout_between_tests) + + file7 = read(notebook.path, String) + @test file6 != file7 + @test Pluto.is_disabled(notebook.cells[3]) + + write(notebook.path, file6) + @test poll(10) do + !Pluto.is_disabled(notebook.cells[3]) + end + + # cell disabled and re-enabled, so it should re-run + @test original_rand_output != notebook.cells[3].output.body + + + + ### + sleep(timeout_between_tests) + + file8 = read(joinpath(@__DIR__, "packages", "simple_stdlib_import.jl"), String) + write(notebook.path, file8) + + @test poll(10) do notebook.cells[2].output.body == "false" end From c7792dd43a9cd7ea3fd1f204fe83eebf01d4d771 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 17:21:42 +0200 Subject: [PATCH 431/821] hide pkg update button when not connected --- frontend/components/Editor.js | 5 ++++- frontend/components/Popup.js | 14 ++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index be9cd28a97..2394272b7f 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -1519,7 +1519,10 @@ patch: ${JSON.stringify( on_update_doc_query=${this.actions.set_doc_query} notebook=${this.state.notebook} /> - <${Popup} notebook=${this.state.notebook}/> + <${Popup} + notebook=${this.state.notebook} + disable_input=${this.state.disable_ui || !this.state.connected /* && this.state.backend_launch_phase == null*/} + /> <${UndoDelete} recently_deleted=${this.state.recently_deleted} on_click=${() => { diff --git a/frontend/components/Popup.js b/frontend/components/Popup.js index e33e128ffb..8e7a59e533 100644 --- a/frontend/components/Popup.js +++ b/frontend/components/Popup.js @@ -39,7 +39,7 @@ export const open_pluto_popup = (/** @type{PkgPopupDetails | MiscPopupDetails} * ) } -export const Popup = ({ notebook }) => { +export const Popup = ({ notebook, disable_input }) => { const [recent_event, set_recent_event] = useState(/** @type{(PkgPopupDetails | MiscPopupDetails)?} */ (null)) const recent_source_element_ref = useRef(/** @type{HTMLElement?} */ (null)) const pos_ref = useRef("") @@ -94,7 +94,12 @@ export const Popup = ({ notebook }) => { style="${pos_ref.current}" > ${type === "nbpkg" - ? html`<${PkgPopup} notebook=${notebook} recent_event=${recent_event} clear_recent_event=${() => set_recent_event(null)} />` + ? html`<${PkgPopup} + notebook=${notebook} + disable_input=${disable_input} + recent_event=${recent_event} + clear_recent_event=${() => set_recent_event(null)} + />` : type === "info" ? html`
    ${recent_event?.body}
    ` : null} @@ -106,9 +111,10 @@ export const Popup = ({ notebook }) => { * notebook: import("./Editor.js").NotebookData, * recent_event: PkgPopupDetails, * clear_recent_event: () => void, + * disable_input: boolean, * }} props */ -const PkgPopup = ({ notebook, recent_event, clear_recent_event }) => { +const PkgPopup = ({ notebook, recent_event, clear_recent_event, disable_input }) => { let pluto_actions = useContext(PlutoActionsContext) const [pkg_status, set_pkg_status] = useState(/** @type{import("./PkgStatusMark.js").PackageStatus?} */ (null)) @@ -171,7 +177,7 @@ const PkgPopup = ({ notebook, recent_event, clear_recent_event }) => { class="pkg-update" target="_blank" title="Update packages" - style=${(!!showupdate ? "" : "opacity: .4;") + (recent_event?.is_disable_pkg ? "display: none;" : "")} + style=${(!!showupdate ? "" : "opacity: .4;") + (recent_event?.is_disable_pkg || disable_input ? "display: none;" : "")} href="#" onClick=${(e) => { if (busy) { From ae47b70c9cc8d1da60cab5bd682ce7928bfb2e99 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 17:21:46 +0200 Subject: [PATCH 432/821] Update featured_sources.js --- frontend/featured_sources.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/featured_sources.js b/frontend/featured_sources.js index 4af889fd10..15976c33d4 100644 --- a/frontend/featured_sources.js +++ b/frontend/featured_sources.js @@ -2,8 +2,8 @@ export default { // check out https://github.com/JuliaPluto/pluto-developer-instructions/blob/main/How%20to%20update%20the%20featured%20notebooks.md to learn more sources: [ { - url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@b31a1701ebe14b3cc9cc8a06849153161e6f6972/pluto_export.json", - integrity: "sha256-JHf0MqoVjk8ISe2GJ9qBC3gj/3I86SiZbgp07eJYZD4=", + url: "https://cdn.jsdelivr.net/gh/JuliaPluto/featured@6828b12/pluto_export.json", + integrity: "sha256-RKFITj6QfwcuqpQE3nhVyhNO5REaCuo84HQulf32l7A=", }, ], } From b9158e0bd84e3394829ae3391decddd9f0fe5be9 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 21 Sep 2022 22:02:44 +0200 Subject: [PATCH 433/821] whitespace from #2249 Co-Authored-By: Paul Berg <9824244+Pangoraw@users.noreply.github.com> --- src/evaluation/WorkspaceManager.jl | 124 ++++++++++++++--------------- src/runner/PlutoRunner.jl | 16 ++-- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index e972505e54..e34d82df87 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -48,7 +48,7 @@ function make_workspace((session, notebook)::SN; is_offline_renderer::Bool=false pid = if use_distributed @debug "Creating workspace process" notebook.path length(notebook.cells) - create_workspaceprocess(;compiler_options=_merge_notebook_compiler_options(notebook, session.options.compiler)) + create_workspaceprocess(; compiler_options=_merge_notebook_compiler_options(notebook, session.options.compiler)) else pid = Distributed.myid() if !(isdefined(Main, :PlutoRunner) && Main.PlutoRunner isa Module) @@ -61,7 +61,7 @@ function make_workspace((session, notebook)::SN; is_offline_renderer::Bool=false end Distributed.remotecall_eval(Main, [pid], :(PlutoRunner.notebook_id[] = $(notebook.notebook_id))) - + remote_log_channel = Core.eval(Main, quote $(Distributed).RemoteChannel(() -> eval(quote @@ -75,18 +75,18 @@ function make_workspace((session, notebook)::SN; is_offline_renderer::Bool=false channel end), $pid) end) - + run_channel = Core.eval(Main, quote $(Distributed).RemoteChannel(() -> eval(:(Main.PlutoRunner.run_channel)), $pid) end) module_name = create_emptyworkspacemodule(pid) - + original_LOAD_PATH, original_ACTIVE_PROJECT = Distributed.remotecall_eval(Main, pid, :(Base.LOAD_PATH, Base.ACTIVE_PROJECT[])) - + workspace = Workspace(; pid, notebook_id=notebook.notebook_id, - remote_log_channel, + remote_log_channel, module_name, original_LOAD_PATH, original_ACTIVE_PROJECT, @@ -109,17 +109,17 @@ function use_nbpkg_environment((session, notebook)::SN, workspace=nothing) if workspace.nbpkg_was_active == enabled return end - + workspace = workspace !== nothing ? workspace : get_workspace((session, notebook)) if workspace.discarded return end - + workspace.nbpkg_was_active = enabled if workspace.pid != Distributed.myid() new_LP = enabled ? ["@", "@stdlib"] : workspace.original_LOAD_PATH new_AP = enabled ? PkgCompat.env_dir(notebook.nbpkg_ctx) : workspace.original_ACTIVE_PROJECT - + Distributed.remotecall_eval(Main, [workspace.pid], quote copy!(LOAD_PATH, $(new_LP)) Base.ACTIVE_PROJECT[] = $(new_AP) @@ -146,10 +146,10 @@ function start_relaying_self_updates((session, notebook)::SN, run_channel::Distr end function start_relaying_logs((session, notebook)::SN, log_channel::Distributed.RemoteChannel) - update_throttled, flush_throttled = Pluto.throttled(0.1) do + update_throttled, flush_throttled = Pluto.throttled(0.1) do Pluto.send_notebook_changes!(Pluto.ClientRequest(session=session, notebook=notebook)) end - + while true try next_log::Dict{String,Any} = take!(log_channel) @@ -164,32 +164,32 @@ function start_relaying_logs((session, notebook)::SN, log_channel::Distributed.R begin source_cell_id = if match !== nothing # the log originated from within the notebook - + UUID(fn[match[end]+1:end]) else # the log originated from a function call defined outside of the notebook - + # we will show the log at the currently running cell, at "line -1", i.e. without line info. next_log["line"] = -1 running_cell_id end - + if running_cell_id != source_cell_id # the log originated from a function in another cell of the notebook # we will show the log at the currently running cell, at "line -1", i.e. without line info. next_log["line"] = -1 end end - + source_cell = get(notebook.cells_dict, source_cell_id, nothing) running_cell = get(notebook.cells_dict, running_cell_id, nothing) - + display_cell = if running_cell === nothing || (source_cell !== nothing && source_cell.output.has_pluto_hook_features) source_cell else running_cell end - + @assert !isnothing(display_cell) maybe_max_log = findfirst(((key, _),) -> key == "maxlog", next_log["kwargs"]) @@ -257,7 +257,7 @@ const Distributed_expr = :( # NOTE: this function only start a worker process using given # compiler options, it does not resolve paths for notebooks # compiler configurations passed to it should be resolved before this -function create_workspaceprocess(;compiler_options=CompilerOptions())::Integer +function create_workspaceprocess(; compiler_options=CompilerOptions())::Integer # run on proc 1 in case Pluto is being used inside a notebook process # Workaround for "only process 1 can add/remove workers" pid = Distributed.remotecall_eval(Main, 1, quote @@ -288,15 +288,15 @@ function get_workspace(session_notebook::SN; allow_creation::Bool=true)::Union{N @debug "This should not happen" notebook.process_status error("Cannot run code in this notebook: it has already shut down.") end - - task = !allow_creation ? - get(workspaces, notebook.notebook_id, nothing) : - get!(workspaces, notebook.notebook_id) do - Task(() -> make_workspace(session_notebook)) - end - + + task = !allow_creation ? + get(workspaces, notebook.notebook_id, nothing) : + get!(workspaces, notebook.notebook_id) do + Task(() -> make_workspace(session_notebook)) + end + isnothing(task) && return nothing - + istaskstarted(task) || schedule(task) fetch(task) end @@ -349,8 +349,8 @@ function distributed_exception_result(exs::CompositeException, workspace::Worksp ex = exs.exceptions |> first if ex isa Distributed.RemoteException && - ex.pid == workspace.pid && - ex.captured.ex isa InterruptException + ex.pid == workspace.pid && + ex.captured.ex isa InterruptException ( output_formatted=PlutoRunner.format_output(CapturedException(InterruptException(), [])), @@ -390,19 +390,19 @@ end `expr` has to satisfy `ExpressionExplorer.is_toplevel_expr`." function eval_format_fetch_in_workspace( - session_notebook::Union{SN,Workspace}, - expr::Expr, - cell_id::UUID; - ends_with_semicolon::Bool=false, - function_wrapped_info::Union{Nothing,Tuple}=nothing, - forced_expr_id::Union{PlutoRunner.ObjectID,Nothing}=nothing, - known_published_objects::Vector{String}=String[], - user_requested_run::Bool=true, - capture_stdout::Bool=true, - )::PlutoRunner.FormattedCellResult + session_notebook::Union{SN,Workspace}, + expr::Expr, + cell_id::UUID; + ends_with_semicolon::Bool=false, + function_wrapped_info::Union{Nothing,Tuple}=nothing, + forced_expr_id::Union{PlutoRunner.ObjectID,Nothing}=nothing, + known_published_objects::Vector{String}=String[], + user_requested_run::Bool=true, + capture_stdout::Bool=true +)::PlutoRunner.FormattedCellResult workspace = get_workspace(session_notebook) - + is_on_this_process = workspace.pid == Distributed.myid() # if multiple notebooks run on the same process, then we need to `cd` between the different notebook paths @@ -412,16 +412,16 @@ function eval_format_fetch_in_workspace( end use_nbpkg_environment(session_notebook, workspace) end - + # run the code 🏃‍♀️ - + # a try block (on this process) to catch an InterruptException take!(workspace.dowork_token) early_result = try # we use [pid] instead of pid to prevent fetching output Distributed.remotecall_eval(Main, [workspace.pid], :(PlutoRunner.run_expression( - getfield(Main, $(QuoteNode(workspace.module_name))), - $(QuoteNode(expr)), + getfield(Main, $(QuoteNode(workspace.module_name))), + $(QuoteNode(expr)), $(workspace.notebook_id), $cell_id, $function_wrapped_info, @@ -438,25 +438,25 @@ function eval_format_fetch_in_workspace( end early_result === nothing ? - format_fetch_in_workspace(workspace, cell_id, ends_with_semicolon, known_published_objects) : - early_result + format_fetch_in_workspace(workspace, cell_id, ends_with_semicolon, known_published_objects) : + early_result end "Evaluate expression inside the workspace - output is not fetched, errors are rethrown. For internal use." function eval_in_workspace(session_notebook::Union{SN,Workspace}, expr) workspace = get_workspace(session_notebook) - + Distributed.remotecall_eval(Main, [workspace.pid], :(Core.eval($(workspace.module_name), $(expr |> QuoteNode)))) nothing end function format_fetch_in_workspace( - session_notebook::Union{SN,Workspace}, - cell_id, - ends_with_semicolon, - known_published_objects::Vector{String}=String[], - showmore_id::Union{PlutoRunner.ObjectDimPair,Nothing}=nothing, - )::PlutoRunner.FormattedCellResult + session_notebook::Union{SN,Workspace}, + cell_id, + ends_with_semicolon, + known_published_objects::Vector{String}=String[], + showmore_id::Union{PlutoRunner.ObjectDimPair,Nothing}=nothing, +)::PlutoRunner.FormattedCellResult workspace = get_workspace(session_notebook) # instead of fetching the output value (which might not make sense in our context, since the user can define structs, types, functions, etc), we format the cell output on the worker, and fetch the formatted output. @@ -464,12 +464,12 @@ function format_fetch_in_workspace( try Distributed.remotecall_eval(Main, workspace.pid, :(PlutoRunner.formatted_result_of( $(workspace.notebook_id), - $cell_id, - $ends_with_semicolon, + $cell_id, + $ends_with_semicolon, $known_published_objects, $showmore_id, getfield(Main, $(QuoteNode(workspace.module_name))), - ))) + ))) catch ex distributed_exception_result(CompositeException([ex]), workspace) end @@ -488,13 +488,13 @@ function collect_soft_definitions(session_notebook::SN, modules::Set{Expr}) end -function macroexpand_in_workspace(session_notebook::Union{SN,Workspace}, macrocall, cell_uuid, module_name = nothing)::Tuple{Bool, Any} +function macroexpand_in_workspace(session_notebook::Union{SN,Workspace}, macrocall, cell_id, module_name=nothing)::Tuple{Bool,Any} workspace = get_workspace(session_notebook) module_name = module_name === nothing ? workspace.module_name : module_name Distributed.remotecall_eval(Main, workspace.pid, quote try - (true, PlutoRunner.try_macroexpand($(module_name), $(cell_uuid), $(macrocall |> QuoteNode))) + (true, PlutoRunner.try_macroexpand($(module_name), $(workspace.notebook_id), $(cell_id), $(macrocall |> QuoteNode))) catch error # We have to be careful here, for example a thrown `MethodError()` will contain the called method and arguments. # which normally would be very useful for debugging, but we can't serialize it! @@ -511,7 +511,7 @@ end "Evaluate expression inside the workspace - output is returned. For internal use." function eval_fetch_in_workspace(session_notebook::Union{SN,Workspace}, expr) workspace = get_workspace(session_notebook) - + Distributed.remotecall_eval(Main, workspace.pid, :(Core.eval($(workspace.module_name), $(expr |> QuoteNode)))) end @@ -525,12 +525,12 @@ end function move_vars(session_notebook::Union{SN,Workspace}, old_workspace_name::Symbol, new_workspace_name::Union{Nothing,Symbol}, to_delete::Set{Symbol}, methods_to_delete::Set{Tuple{UUID,FunctionName}}, module_imports_to_move::Set{Expr}, invalidated_cell_uuids::Set{UUID}; kwargs...) workspace = get_workspace(session_notebook) new_workspace_name = something(new_workspace_name, workspace.module_name) - + Distributed.remotecall_eval(Main, [workspace.pid], :(PlutoRunner.move_vars($(old_workspace_name |> QuoteNode), $(new_workspace_name |> QuoteNode), $to_delete, $methods_to_delete, $module_imports_to_move, $invalidated_cell_uuids))) end move_vars(session_notebook::Union{SN,Workspace}, to_delete::Set{Symbol}, methods_to_delete::Set{Tuple{UUID,FunctionName}}, module_imports_to_move::Set{Expr}, invalidated_cell_uuids::Set{UUID}; kwargs...) = -move_vars(session_notebook, bump_workspace_module(session_notebook)..., to_delete, methods_to_delete, module_imports_to_move, invalidated_cell_uuids; kwargs...) + move_vars(session_notebook, bump_workspace_module(session_notebook)..., to_delete, methods_to_delete, module_imports_to_move, invalidated_cell_uuids; kwargs...) # TODO: delete me @deprecate( @@ -586,7 +586,7 @@ end "Force interrupt (SIGINT) a workspace, return whether successful" function interrupt_workspace(session_notebook::Union{SN,Workspace}; verbose=true)::Bool workspace = get_workspace(session_notebook; allow_creation=false) - + if !(workspace isa Workspace) # verbose && @info "Can't interrupt this notebook: it is not running." return false @@ -626,7 +626,7 @@ function interrupt_workspace(session_notebook::Union{SN,Workspace}; verbose=true end verbose && println("Still running... starting sequence") - while !isready(workspace.dowork_token) + while !isready(workspace.dowork_token) for _ in 1:5 verbose && print(" 🔥 ") Distributed.interrupt(workspace.pid) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index c5df1cb1ab..a93bab3219 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -265,10 +265,10 @@ module CantReturnInPluto replace_returns_with_error_in_interpolation(ex) = ex end -function try_macroexpand(mod, cell_uuid, expr) +function try_macroexpand(mod::Module, notebook_id::UUID, cell_id::UUID, expr) # Remove the precvious cached expansion, so when we error somewhere before we update, # the old one won't linger around and get run accidentally. - delete!(cell_expanded_exprs, cell_uuid) + delete!(cell_expanded_exprs, cell_id) # Remove toplevel block, as that screws with the computer and everything expr_not_toplevel = if expr.head == :toplevel || expr.head == :block @@ -288,14 +288,14 @@ function try_macroexpand(mod, cell_uuid, expr) expr_without_globalrefs = globalref_to_workspaceref(expr_without_return) has_pluto_hook_features = has_hook_style_pluto_properties_in_expr(expr_without_globalrefs) - expr_to_save = replace_pluto_properties_in_expr(expr_without_globalrefs, - cell_id=cell_uuid, - rerun_cell_function=() -> rerun_cell_from_notebook(cell_uuid), - register_cleanup_function=(fn) -> UseEffectCleanups.register_cleanup(fn, cell_uuid), + expr_to_save = replace_pluto_properties_in_expr(expr_without_globalrefs; + cell_id, + rerun_cell_function=() -> rerun_cell_from_notebook(cell_id), + register_cleanup_function=(fn) -> UseEffectCleanups.register_cleanup(fn, cell_id), ) did_mention_expansion_time = false - cell_expanded_exprs[cell_uuid] = CachedMacroExpansion( + cell_expanded_exprs[cell_id] = CachedMacroExpansion( expr_hash(expr), expr_to_save, elapsed_ns, @@ -525,7 +525,7 @@ function run_expression( # .... But ideally we wouldn't re-macroexpand and store the error the first time (TODO-ish) if !haskey(cell_expanded_exprs, cell_id) || cell_expanded_exprs[cell_id].original_expr_hash != expr_hash(expr) try - try_macroexpand(m, cell_id, expr) + try_macroexpand(m, notebook_id, cell_id, expr) catch e result = CapturedException(e, stacktrace(catch_backtrace())) cell_results[cell_id], cell_runtimes[cell_id] = (result, nothing) From 343e239109f6c799938eda1aa75c27c3ef55b14d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 22 Sep 2022 16:03:28 +0200 Subject: [PATCH 434/821] Tests: retry auto reload tests 3 times --- test/ReloadFromFile.jl | 65 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/test/ReloadFromFile.jl b/test/ReloadFromFile.jl index 2ebc96f047..06277a77cd 100644 --- a/test/ReloadFromFile.jl +++ b/test/ReloadFromFile.jl @@ -5,8 +5,23 @@ import Distributed using Pluto.WorkspaceManager: poll import Pkg + +function retry(f::Function, n) + try + f() + catch e + if n > 0 + retry(f, n - 1) + else + rethrow(e) + end + end +end + @testset "Reload from file" begin + retry(3) do + 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false 🍭.options.server.auto_reload_from_file = true @@ -34,18 +49,18 @@ import Pkg write(notebook.path, file1) - @test poll(30) do + @assert poll(30) do length(notebook.cells) == 3 end - @test poll(5) do + @assert poll(5) do notebook.cells[1].output.body == "123" end - @test poll(5) do + @assert poll(5) do all(c -> !c.running, notebook.cells) end - @test notebook.cells[2].output.body == "246" - @test notebook.cells[3] |> noerror + @assert notebook.cells[2].output.body == "246" + @assert notebook.cells[3] |> noerror original_rand_output = notebook.cells[3].output.body @@ -58,12 +73,12 @@ import Pkg file2 = sprint(Pluto.save_notebook, nb2) write(notebook.path, file2) - @test poll(10) do + @assert poll(10) do notebook.cells[3].output.body == "123" end # notebook order reversed, but cell should not re-run - @test original_rand_output == notebook.cells[1].output.body + @assert original_rand_output == notebook.cells[1].output.body ### @@ -74,16 +89,16 @@ import Pkg write(notebook.path, file3) - @test poll(10) do + @assert poll(10) do notebook.cells[1].output.body == "6" end - @test poll(5) do + @assert poll(5) do all(c -> !c.running, notebook.cells) end - @test notebook.cells[2].output.body == "12" + @assert notebook.cells[2].output.body == "12" # notebook order reversed again, but cell should not re-run - @test original_rand_output == notebook.cells[3].output.body + @assert original_rand_output == notebook.cells[3].output.body @@ -96,18 +111,18 @@ import Pkg sleep(timeout_between_tests) file5 = read(notebook.path, String) - @test file4 != file5 + @assert file4 != file5 - @test notebook.cells[3].code_folded + @assert notebook.cells[3].code_folded write(notebook.path, file4) - @test poll(10) do + @assert poll(10) do notebook.cells[3].code_folded == false end # cell folded, but cell should not re-run - @test original_rand_output == notebook.cells[3].output.body + @assert original_rand_output == notebook.cells[3].output.body @@ -121,18 +136,19 @@ import Pkg sleep(timeout_between_tests) file7 = read(notebook.path, String) - @test file6 != file7 - @test Pluto.is_disabled(notebook.cells[3]) + @assert file6 != file7 + @assert Pluto.is_disabled(notebook.cells[3]) write(notebook.path, file6) - @test poll(10) do + @assert poll(10) do !Pluto.is_disabled(notebook.cells[3]) end # cell disabled and re-enabled, so it should re-run - @test original_rand_output != notebook.cells[3].output.body + @assert poll(10) do + original_rand_output != notebook.cells[3].output.body + end - ### sleep(timeout_between_tests) @@ -141,9 +157,12 @@ import Pkg write(notebook.path, file8) - @test poll(10) do + @assert poll(10) do notebook.cells[2].output.body == "false" end - @test length(notebook.cells) == 2 - @test notebook.nbpkg_restart_required_msg !== nothing + + @assert length(notebook.cells) == 2 + @assert notebook.nbpkg_restart_required_msg !== nothing +end +@test true end \ No newline at end of file From 57f4572a9c68dcf0e9bb7d98b9966c0e4dbd2604 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 23 Sep 2022 20:09:04 +0200 Subject: [PATCH 435/821] Add unicode preview for auto-complete (#2285) * Add unicode preview for auto-complete * Don't apply in strings, just complete (for markdown + latex) --- .../CellInput/pluto_autocomplete.js | 43 +++++++++++++++---- frontend/editor.css | 7 +++ src/runner/PlutoRunner.jl | 41 ++++++++++++------ 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js index dbfb382def..31c5ae4bf6 100644 --- a/frontend/components/CellInput/pluto_autocomplete.js +++ b/frontend/components/CellInput/pluto_autocomplete.js @@ -146,26 +146,34 @@ let match_latex_complete = (ctx) => ctx.matchBefore(/\\[^\s"'.`]*/) let match_symbol_complete = (ctx) => ctx.matchBefore(/\.\:[^\s"'`()\[\].]*/) /** Are we matching exactly `~/`? */ let match_expanduser_complete = (ctx) => ctx.matchBefore(/~\//) +/** Are we matching inside a string */ +function match_string_complete(ctx) { + const tree = syntaxTree(ctx.state) + const node = tree.resolve(ctx.pos) + if (node == null || (node.name !== "TripleString" && node.name !== "String")) { + return false + } + return true +} /** Use the completion results from the Julia server to create CM completion objects, but only for path completions (TODO: broken) and latex completions. */ let julia_special_completions_to_cm = (/** @type {PlutoRequestAutocomplete} */ request_autocomplete) => async (ctx) => { - let unicode_match = match_latex_complete(ctx) || match_expanduser_complete(ctx) - if (unicode_match == null) return null - let to_complete = ctx.state.sliceDoc(0, ctx.pos) let { start, stop, results } = await request_autocomplete({ text: to_complete }) + let should_apply_unicode_completion = !match_string_complete(ctx) + return { from: start, to: stop, // This is an important one when you not only complete, but also replace something. // @codemirror/autocomplete automatically filters out results otherwise >:( filter: false, - // TODO Add "detail" that shows the unicode character - // TODO Add "apply" with the unicode character so it autocompletes that immediately - options: results.map(([text], i) => { + options: results.map(([text, _, __, ___, ____, detail]) => { return { label: text, + apply: detail && should_apply_unicode_completion ? detail : text, + detail, } }), // TODO Do something docs_prefix ish when we also have the apply text @@ -273,8 +281,24 @@ const julia_code_completions_to_cm = (/** @type {PlutoRequestAutocomplete} */ re } } +const pluto_completion_fetcher = (request_autocomplete) => { + const unicode_completions = julia_special_completions_to_cm(request_autocomplete) + const code_completions = julia_code_completions_to_cm(request_autocomplete) + + return (ctx) => { + let unicode_match = match_latex_complete(ctx) || match_expanduser_complete(ctx) + if (unicode_match === null) { + return code_completions(ctx) + } else { + return unicode_completions(ctx) + } + } +} + const complete_anyword = async (ctx) => { const results_from_cm = await autocomplete.completeAnyWord(ctx) + if (results_from_cm === null) return null + return { from: results_from_cm.from, options: results_from_cm.options.map(({ label }, i) => ({ @@ -315,7 +339,7 @@ const local_variables_completion = (ctx) => { /** * @typedef PlutoAutocompleteResults - * @type {{ start: number, stop: number, results: Array<[string, (string | null), boolean, boolean, (string | null)]> }} + * @type {{ start: number, stop: number, results: Array<[string, (string | null), boolean, boolean, (string | null), (string | null)]> }} * * @typedef PlutoRequestAutocomplete * @type {(options: { text: string }) => Promise} @@ -351,8 +375,9 @@ export let pluto_autocomplete = ({ request_autocomplete, on_update_doc_query }) autocompletion({ activateOnTyping: false, override: [ - julia_special_completions_to_cm(memoize_last_request_autocomplete), - julia_code_completions_to_cm(memoize_last_request_autocomplete), + pluto_completion_fetcher(memoize_last_request_autocomplete), + // julia_special_completions_to_cm(memoize_last_request_autocomplete), + // julia_code_completions_to_cm(memoize_last_request_autocomplete), complete_anyword, // TODO: Disabled because of performance problems, see https://github.com/fonsp/Pluto.jl/pull/1925. Remove `complete_anyword` once fixed. See https://github.com/fonsp/Pluto.jl/pull/2013 // local_variables_completion, diff --git a/frontend/editor.css b/frontend/editor.css index ace8f5e3ff..f190f621a8 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2687,6 +2687,13 @@ pluto-input .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] .cm-completionLabel { border-color: transparent; } +.cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li .cm-completionDetail { + float: right; + margin-right: 0.5em; + font-size: 0.8em; + font-family: 'JuliaMono'; + font-style: normal; +} .cm-editor .cm-tooltip.cm-tooltip-autocomplete li.c_notexported { color: var(--cm-editor-li-notexported-color); diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index a93bab3219..50da8c8507 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -23,7 +23,7 @@ using Markdown import Markdown: html, htmlinline, LaTeX, withtag, htmlesc import Distributed import Base64 -import FuzzyCompletions: Completion, ModuleCompletion, PropertyCompletion, FieldCompletion, PathCompletion, DictCompletion, completions, completion_text, score +import FuzzyCompletions: Completion, BslashCompletion, ModuleCompletion, PropertyCompletion, FieldCompletion, PathCompletion, DictCompletion, completions, completion_text, score import Base: show, istextmime import UUIDs: UUID, uuid4 import Dates: DateTime @@ -1621,6 +1621,14 @@ catch end completion_description(::Completion) = nothing +completion_detail(::Completion) = nothing +completion_detail(completion::BslashCompletion) = + haskey(REPL.REPLCompletions.latex_symbols, completion.bslash) ? + REPL.REPLCompletions.latex_symbols[completion.bslash] : + haskey(REPL.REPLCompletions.emoji_symbols, completion.bslash) ? + REPL.REPLCompletions.emoji_symbols[completion.bslash] : + nothing + function is_pluto_workspace(m::Module) mod_name = nameof(m) |> string startswith(mod_name, "workspace#") @@ -1639,12 +1647,16 @@ function completions_exported(cs::Vector{<:Completion}) end end -completion_from_notebook(c::ModuleCompletion) = is_pluto_workspace(c.parent) && c.mod != "include" && c.mod != "eval" +completion_from_notebook(c::ModuleCompletion) = + is_pluto_workspace(c.parent) && + c.mod != "include" && + c.mod != "eval" && + !startswith(c.mod, "#") completion_from_notebook(c::Completion) = false -only_special_completion_types(c::PathCompletion) = :path -only_special_completion_types(c::DictCompletion) = :dict -only_special_completion_types(c::Completion) = nothing +only_special_completion_types(::PathCompletion) = :path +only_special_completion_types(::DictCompletion) = :dict +only_special_completion_types(::Completion) = nothing "You say Linear, I say Algebra!" function completion_fetcher(query, pos, workspace::Module) @@ -1664,13 +1676,16 @@ function completion_fetcher(query, pos, workspace::Module) filter!(isenough ∘ score, results) # too many candiates otherwise end - texts = completion_text.(results) - descriptions = completion_description.(results) exported = completions_exported(results) - from_notebook = completion_from_notebook.(results) - completion_type = only_special_completion_types.(results) - - smooshed_together = collect(zip(texts, descriptions, exported, from_notebook, completion_type)) + smooshed_together = [ + (completion_text(result), + completion_description(result), + rexported, + completion_from_notebook(result), + only_special_completion_types(result), + completion_detail(result)) + for (result, rexported) in zip(results, exported) + ] p = if endswith(query, '.') sortperm(smooshed_together; alg=MergeSort, by=basic_completion_priority) @@ -1680,8 +1695,8 @@ function completion_fetcher(query, pos, workspace::Module) sortperm(scores .+ 3.0 * exported; alg=MergeSort, rev=true) end - final = smooshed_together[p] - (final, loc, found) + permute!(smooshed_together, p) + (smooshed_together, loc, found) end is_dot_completion(::Union{ModuleCompletion,PropertyCompletion,FieldCompletion}) = true From d5123e6fdf4cbe3ccea1904a5d8b615735569676 Mon Sep 17 00:00:00 2001 From: Alberto Mengali Date: Mon, 26 Sep 2022 19:06:59 +0200 Subject: [PATCH 436/821] Attach hljs to the window object to allow custom highlighting within code blocks (#2244) Co-authored-by: Fons van der Plas --- frontend/imports/highlightjs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/imports/highlightjs.js b/frontend/imports/highlightjs.js index 17ffd64503..f10ca1e001 100644 --- a/frontend/imports/highlightjs.js +++ b/frontend/imports/highlightjs.js @@ -8,4 +8,8 @@ import hljs_juliarepl from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@ hljs.registerLanguage("julia", hljs_julia) hljs.registerLanguage("julia-repl", hljs_juliarepl) +// Attach the highlighter object to the window to allow custom highlighting from the frontend. See https://github.com/fonsp/Pluto.jl/pull/2244 +//@ts-ignore +window.hljs = hljs + export default hljs From a9fe37f84fc09dde1e61e8de0e465c498516cd14 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 27 Sep 2022 16:53:11 +0200 Subject: [PATCH 437/821] Don't do #2075 when running in CI --- src/packages/PkgCompat.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index b0766a641b..4cde461dc2 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -196,11 +196,15 @@ end # ✅ Public API """ -Check when the registries were last updated. If it is recent (max 7 days), `Pkg.UPDATED_REGISTRY_THIS_SESSION` is set to `true`, which will prevent Pkg from doing an automatic registry update. +Check when the registries were last updated. If it is recent (max 7 days), then `Pkg.UPDATED_REGISTRY_THIS_SESSION[]` is set to `true`, which will prevent Pkg from doing an automatic registry update. Returns the new value of `Pkg.UPDATED_REGISTRY_THIS_SESSION`. """ function check_registry_age(max_age_ms = 1000.0 * 60 * 60 * 24 * 7)::Bool + if get(ENV, "GITHUB_ACTIONS", "false") == "true" + # don't do this optimization in CI + return false + end paths = _get_registry_paths() isempty(paths) && return _updated_registries_compat[] From 89e10b8c9a342bf922a65472aca212d6273bf8cb Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 27 Sep 2022 18:35:57 +0200 Subject: [PATCH 438/821] nothing --- frontend/editor.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/editor.css b/frontend/editor.css index f190f621a8..610b1d971e 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -2691,7 +2691,7 @@ pluto-input .cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li { float: right; margin-right: 0.5em; font-size: 0.8em; - font-family: 'JuliaMono'; + font-family: var(--julia-mono-font-stack); font-style: normal; } From fa4e5fe2c309e9b6dcbb5ed0d7b009578708c4ef Mon Sep 17 00:00:00 2001 From: Rik Huijzer Date: Mon, 3 Oct 2022 14:50:57 +0200 Subject: [PATCH 439/821] Fix method invalidations around `Base.convert` (#2300) --- src/notebook/Cell.jl | 6 +---- src/webserver/Dynamic.jl | 2 +- src/webserver/Firebasey.jl | 45 ++++++++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/notebook/Cell.jl b/src/notebook/Cell.jl index ef8313c28b..48f4862bad 100644 --- a/src/notebook/Cell.jl +++ b/src/notebook/Cell.jl @@ -74,10 +74,6 @@ function Base.convert(::Type{Cell}, cell::Dict) metadata=cell["metadata"], ) end -function Base.convert(::Type{UUID}, string::String) - UUID(string) -end - "Returns whether or not the cell is **explicitely** disabled." is_disabled(c::Cell) = get(c.metadata, METADATA_DISABLED_KEY, DEFAULT_CELL_METADATA[METADATA_DISABLED_KEY]) @@ -88,4 +84,4 @@ else end can_show_logs(c::Cell) = get(c.metadata, METADATA_SHOW_LOGS_KEY, DEFAULT_CELL_METADATA[METADATA_SHOW_LOGS_KEY]) is_skipped_as_script(c::Cell) = get(c.metadata, METADATA_SKIP_AS_SCRIPT_KEY, DEFAULT_CELL_METADATA[METADATA_SKIP_AS_SCRIPT_KEY]) -must_be_commented_in_file(c::Cell) = is_disabled(c) || is_skipped_as_script(c) || c.depends_on_disabled_cells || c.depends_on_skipped_cells \ No newline at end of file +must_be_commented_in_file(c::Cell) = is_disabled(c) || is_skipped_as_script(c) || c.depends_on_disabled_cells || c.depends_on_skipped_cells diff --git a/src/webserver/Dynamic.jl b/src/webserver/Dynamic.jl index 835d56f5f8..8d24353ecd 100644 --- a/src/webserver/Dynamic.jl +++ b/src/webserver/Dynamic.jl @@ -181,7 +181,7 @@ function send_notebook_changes!(🙋::ClientRequest; commentary::Any=nothing) if client.connected_notebook !== nothing && client.connected_notebook.notebook_id == 🙋.notebook.notebook_id current_dict = get(current_state_for_clients, client, :empty) patches = Firebasey.diff(current_dict, notebook_dict) - patches_as_dicts::Array{Dict} = patches + patches_as_dicts::Array{Dict} = Firebasey._convert(Array{Dict}, patches) current_state_for_clients[client] = deep_enough_copy(notebook_dict) # Make sure we do send a confirmation to the client who made the request, even without changes diff --git a/src/webserver/Firebasey.jl b/src/webserver/Firebasey.jl index ab59d3ffd6..99a18e731c 100644 --- a/src/webserver/Firebasey.jl +++ b/src/webserver/Firebasey.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.19.9 +# v0.19.12 using Markdown using InteractiveUtils @@ -227,11 +227,6 @@ function Base.convert(::Type{Dict}, patch::RemovePatch) Dict{String,Any}("op" => "remove", "path" => patch.path) end -# ╔═╡ fafcb8b8-cde9-4f99-9bab-8128025953a4 -function Base.convert(::Type{<:Dict}, patch::ReplacePatch) - Dict{String,Any}("op" => "replace", "path" => patch.path, "value" => patch.value) -end - # ╔═╡ 921a130e-b028-4f91-b077-3bd79dcb6c6d function Base.convert(::Type{JSONPatch}, patch_dict::Dict) op = patch_dict["op"] @@ -258,12 +253,6 @@ Base.convert(Dict, AddPatch([:x, :y], 10)) Base.convert(Dict, RemovePatch([:x, :y])) ╠═╡ =# -# ╔═╡ 7feeee3a-3aec-47ce-b8d7-74a0d9b0b381 -# ╠═╡ skip_as_script = true -#=╠═╡ -Base.convert(Dict, ReplacePatch([:x, :y], 10)) - ╠═╡ =# - # ╔═╡ 6d67f8a5-0e0c-4b6e-a267-96b34d580946 # ╠═╡ skip_as_script = true #=╠═╡ @@ -702,6 +691,28 @@ end md"### applypatch! AddPatch" ╠═╡ =# +# ╔═╡ d7ea6052-9d9f-48e3-92fb-250afd69e417 +begin + _convert(::Type{Base.UUID}, s::String) = Base.UUID(s) + _convert(::Type{T}, a::AbstractArray) where {T<:Array} = _convert.(eltype(T), a) + _convert(x, y) = convert(x, y) + + function _convert(::Type{<:Dict}, patch::ReplacePatch) + Dict{String,Any}("op" => "replace", "path" => patch.path, "value" => patch.value) + end + + function _setproperty!(x, f::Symbol, v) + type = fieldtype(typeof(x), f) + return setfield!(x, f, _convert(type, v)) + end +end + +# ╔═╡ 7feeee3a-3aec-47ce-b8d7-74a0d9b0b381 +# ╠═╡ skip_as_script = true +#=╠═╡ +_convert(Dict, ReplacePatch([:x, :y], 10)) + ╠═╡ =# + # ╔═╡ dd87ca7e-2de1-11eb-2ec3-d5721c32f192 function applypatch!(value, patch::AddPatch) if length(patch.path) == 0 @@ -721,7 +732,7 @@ function applypatch!(value, patch::AddPatch) if strict_applypatch[] @assert getproperty(subvalue, key) === nothing end - setproperty!(subvalue, key, patch.value) + _setproperty!(subvalue, key, patch.value) end end return value @@ -758,7 +769,7 @@ function applypatch!(value, patch::ReplacePatch) if strict_applypatch[] @assert getproperty(subvalue, key) !== nothing end - setproperty!(subvalue, key, patch.value) + _setproperty!(subvalue, key, patch.value) end end return value @@ -795,7 +806,7 @@ function applypatch!(value, patch::RemovePatch) if strict_applypatch[] @assert getproperty(subvalue, key) !== nothing end - setproperty!(subvalue, key, nothing) + _setproperty!(subvalue, key, nothing) end end return value @@ -1037,7 +1048,7 @@ end # ╔═╡ 34d86e02-dd34-4691-bb78-3023568a5d16 #=╠═╡ -@track Base.convert(JSONPatch, convert(Dict, replace_patch)) == replace_patch +@track Base.convert(JSONPatch, _convert(Dict, replace_patch)) == replace_patch ╠═╡ =# # ╔═╡ 95ff676d-73c8-44cb-ac35-af94418737e9 @@ -1147,7 +1158,6 @@ end # ╟─07eeb122-6706-4544-a007-1c8d6581eec8 # ╠═b48e2c08-a94a-4247-877d-949d92dde626 # ╟─c59b30b9-f702-41f1-bb2e-1736c8cd5ede -# ╠═fafcb8b8-cde9-4f99-9bab-8128025953a4 # ╟─7feeee3a-3aec-47ce-b8d7-74a0d9b0b381 # ╠═921a130e-b028-4f91-b077-3bd79dcb6c6d # ╟─6d67f8a5-0e0c-4b6e-a267-96b34d580946 @@ -1220,6 +1230,7 @@ end # ╠═48a45941-2489-4666-b4e5-88d3f82e5145 # ╠═752b2da3-ff24-4758-8843-186368069888 # ╟─3e285076-1d97-4728-87cf-f71b22569e57 +# ╠═d7ea6052-9d9f-48e3-92fb-250afd69e417 # ╠═dd87ca7e-2de1-11eb-2ec3-d5721c32f192 # ╟─c3e4738f-4568-4910-a211-6a46a9d447ee # ╟─a11e4082-4ff4-4c1b-9c74-c8fa7dcceaa6 From 9f2b7e838c76450db41e41b9cbfba5f119064771 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 5 Oct 2022 16:21:35 +0200 Subject: [PATCH 440/821] =?UTF-8?q?=F0=9F=8D=94=20Logs:=20Vertical=20layou?= =?UTF-8?q?t=20(#2297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/CellInput.js | 1 + .../components/CellInput/highlight_line.js | 3 +- frontend/components/Logs.js | 176 +++++-------- frontend/dark_color.css | 13 +- frontend/editor.css | 237 ++++++++---------- frontend/light_color.css | 13 +- 6 files changed, 196 insertions(+), 247 deletions(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index 92582c4349..3b95ffa7bd 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -585,6 +585,7 @@ export const CellInput = ({ highlighted_line_compartment, global_definitions_compartment, editable_compartment, + highlightLinePlugin(), // This is waaaay in front of the keys it is supposed to override, // Which is necessary because it needs to run before *any* keymap, diff --git a/frontend/components/CellInput/highlight_line.js b/frontend/components/CellInput/highlight_line.js index cdf9082fc6..f66346f878 100644 --- a/frontend/components/CellInput/highlight_line.js +++ b/frontend/components/CellInput/highlight_line.js @@ -9,7 +9,7 @@ const highlighted_line = Decoration.line({ */ function create_line_decorations(view) { let line_number = view.state.facet(HighlightLineFacet) - if (line_number == null || line_number == undefined || line_number > view.state.doc.lines) { + if (line_number == null || line_number == undefined || line_number < 0 || line_number > view.state.doc.lines) { return Decoration.set([]) } @@ -36,6 +36,7 @@ export const highlightLinePlugin = () => * @param {EditorView} view */ constructor(view) { + this.decorations = Decoration.set([]) this.updateDecos(view) } diff --git a/frontend/components/Logs.js b/frontend/components/Logs.js index 80126d2728..406b30b67c 100644 --- a/frontend/components/Logs.js +++ b/frontend/components/Logs.js @@ -5,9 +5,8 @@ import { SimpleOutputBody } from "./TreeView.js" import { help_circle_icon, open_pluto_popup } from "./Popup.js" import AnsiUp from "../imports/AnsiUp.js" -// Defined in editor.css -const GRID_WIDTH = 10 -const RESIZE_THROTTLE = 60 +const LOGS_VISIBLE_START = 60 +const LOGS_VISIBLE_END = 20 const PROGRESS_LOG_LEVEL = "LogLevel(-1)" const is_progress_log = (log) => { @@ -15,9 +14,6 @@ const is_progress_log = (log) => { } export const Logs = ({ logs, line_heights, set_cm_highlighted_line }) => { - const container = useRef(/** @type {HTMLElement?} */ (null)) - const [from, setFrom] = useState(0) - const [to, setTo] = useState(Math.round(1000 / GRID_WIDTH)) const progress_logs = logs.filter(is_progress_log) const latest_progress_logs = progress_logs.reduce((progress_logs, log) => ({ ...progress_logs, [log.id]: log }), {}) const [_, grouped_progress_and_logs] = logs.reduce( @@ -32,51 +28,35 @@ export const Logs = ({ logs, line_heights, set_cm_highlighted_line }) => { }, [{}, []] ) - const logsWidth = grouped_progress_and_logs.length * GRID_WIDTH - const logsStyle = useMemo( - () => `grid-template-rows: ${line_heights.map((y) => y + "px").join(" ")} repeat(auto-fill, 15px); width: ${logsWidth}px;`, - [logsWidth, line_heights] - ) - - useEffect(() => { - const elem = container.current - if (!elem) return - const fn = () => { - const w = elem.clientWidth - const scroll_left = elem.scrollLeft - setFrom(Math.min(logs.length - 1, Math.round((scroll_left - w) / GRID_WIDTH))) - setTo(Math.round((scroll_left + 2 * w) / GRID_WIDTH)) - } - const l = fn // _.throttle(fn, RESIZE_THROTTLE) - document.addEventListener("resize", l) - elem.addEventListener("scroll", l) - return () => { - elem.removeEventListener("scroll", l) - document.removeEventListener("resize", l) - } - }, [container.current, logsWidth]) const is_hidden_input = line_heights[0] === 0 if (logs.length === 0) { return null } + const dot = (log, i) => html`<${Dot} + set_cm_highlighted_line=${set_cm_highlighted_line} + level=${log.level} + msg=${log.msg} + kwargs=${log.kwargs} + key=${i} + y=${is_hidden_input ? 0 : log.line - 1} + /> ` + return html` - - - ${grouped_progress_and_logs.map((log, i) => { - return html`<${Dot} - set_cm_highlighted_line=${set_cm_highlighted_line} - show=${logs.length < 50 || (from <= i && i < to)} - level=${log.level} - msg=${log.msg} - kwargs=${log.kwargs} - mykey=${`log${i}`} - key=${i} - x=${i} - y=${is_hidden_input ? 0 : log.line - 1} - /> ` - })} + + + ${grouped_progress_and_logs.length <= LOGS_VISIBLE_END + LOGS_VISIBLE_START + ? grouped_progress_and_logs.map(dot) + : [ + ...grouped_progress_and_logs.slice(0, LOGS_VISIBLE_START).map(dot), + html` + ${grouped_progress_and_logs.length - LOGS_VISIBLE_START - LOGS_VISIBLE_END} logs not shown... + `, + ...grouped_progress_and_logs + .slice(-LOGS_VISIBLE_END) + .map((log, i) => dot(log, i + grouped_progress_and_logs.length - LOGS_VISIBLE_END)), + ]} ` @@ -95,14 +75,7 @@ const Progress = ({ progress }) => { const mimepair_output = (pair) => html`<${SimpleOutputBody} cell_id=${"adsf"} mime=${pair[1]} body=${pair[0]} persist_js_state=${false} />` -const Dot = ({ set_cm_highlighted_line, show, msg, kwargs, x, y, level }) => { - const node_ref = useRef(/** @type{HTMLElement?} */ (null)) - // const label_ref = useRef(null) - // useEffect(() => { - // label_ref.current.innerHTML = body - // }, [body]) - const [inspecting, set_inspecting] = useState(false) - +const Dot = ({ set_cm_highlighted_line, msg, kwargs, y, level }) => { const is_progress = is_progress_log({ level, kwargs }) const is_stdout = level === "LogLevel(-555)" let progress = null @@ -117,77 +90,44 @@ const Dot = ({ set_cm_highlighted_line, show, msg, kwargs, x, y, level }) => { } level = "Progress" - y = 0 } if (is_stdout) { level = "Stdout" } - useLayoutEffect(() => { - if (!node_ref.current) return - node_ref.current.style.gridColumn = `${x + 1}` - node_ref.current.style.gridRow = `${y + 1}` - }, [node_ref.current, x, y]) - - useLayoutEffect(() => { - if (inspecting && show) { - const f = (e) => { - if (!e.target.closest || e.target.closest("pluto-log-dot-positioner") !== node_ref.current) { - set_inspecting(false) - set_cm_highlighted_line(null) - } - } - window.addEventListener("click", f) - window.addEventListener("blur", f) - - return () => { - window.removeEventListener("click", f) - window.removeEventListener("blur", f) - } - } - }, [inspecting]) - - return show - ? html` { - set_inspecting(true) - is_progress || set_cm_highlighted_line(y + 1) - }} - onMouseenter=${() => is_progress || set_cm_highlighted_line(y + 1)} - onMouseleave=${() => set_cm_highlighted_line(null)} - > - - ${is_stdout - ? html`<${MoreInfo} - body=${html`This text was written to the ${" "}terminal stream${" "}while running the cell. It is not the${" "}output value${" "}of this cell.`} - />` - : null} - ${is_progress - ? html`<${Progress} progress=${progress} />` - : is_stdout - ? html`<${MoreInfo} - body=${html`${"This text was written to the "} - terminal stream${" while running the cell. "}${"(It is not the "}return value${" of the cell.)"}`} - /> - <${LogViewAnsiUp} value=${msg[0]} />` - : html`${mimepair_output(msg)}${kwargs.map( - ([k, v]) => - html` - ${k} ${mimepair_output(v)} - ` - )}`} - - ` - : html`` + return html` is_progress || set_cm_highlighted_line(y + 1)} + onMouseleave=${() => { + console.log("leaving!") + set_cm_highlighted_line(null) + }} + > + + ${is_stdout + ? html`<${MoreInfo} + body=${html`This text was written to the ${" "}terminal stream${" "}while running the cell. It is not the${" "}output value${" "}of this cell.`} + />` + : null} + ${is_progress + ? html`<${Progress} progress=${progress} />` + : is_stdout + ? html`<${MoreInfo} + body=${html`${"This text was written to the "} + terminal stream${" while running the cell. "}${"(It is not the "}return value${" of the cell.)"}`} + /> + <${LogViewAnsiUp} value=${msg[0]} />` + : html`${mimepair_output(msg)}${kwargs.map( + ([k, v]) => + html` ${k} ${mimepair_output(v)} ` + )}`} + ` } const MoreInfo = (/** @type{{body: import("../imports/Preact.js").ReactElement}} */ { body }) => { diff --git a/frontend/dark_color.css b/frontend/dark_color.css index c29eb738b0..b6cdcb3a7c 100644 --- a/frontend/dark_color.css +++ b/frontend/dark_color.css @@ -66,10 +66,21 @@ --shoulder-hover-bg-color: rgba(255, 255, 255, 0.05); /* Logs */ - --pluto-logs-bg-color: hsl(240deg 10% 29%); + --pluto-logs-bg-color: hsl(0deg 0% 17%); + --pluto-logs-key-color: rgba(255, 255, 255, 0.6); --pluto-logs-progress-fill: #5f7f5b; + --pluto-logs-progress-bg: #3d3d3d; --pluto-logs-progress-border: hsl(210deg 35% 72%); + --pluto-logs-info-color: #484848; + --pluto-logs-info-accent-color: inherit; + --pluto-logs-warn-color: rgb(80 76 38); + --pluto-logs-warn-accent-color: rgb(239 231 135); + --pluto-logs-danger-color: rgb(100 47 39); + --pluto-logs-danger-accent-color: rgb(255 147 132); + --pluto-logs-debug-color: hsl(288deg 19% 25%); + --pluto-logs-debug-accent-color: hsl(283deg 56% 79%); + /*Top navbar styling*/ --nav-h1-text-color: white; --nav-filepicker-color: #b6b6b6; diff --git a/frontend/editor.css b/frontend/editor.css index 610b1d971e..0d63063965 100644 --- a/frontend/editor.css +++ b/frontend/editor.css @@ -34,9 +34,8 @@ --sans-serif-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; --lato-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif; - --system-ui-font-stack: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", + --system-ui-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Cantarell, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif; - color-scheme: light dark; } @@ -2327,71 +2326,19 @@ nav#undo_delete.hidden { pluto-logs-container { display: block; - width: max(100px, 100vw - (700px + 25px)); + /* Show logs up to the RunArea */ - max-height: calc(100% + 14px); - position: absolute; z-index: 25; - right: min(-100px, -100vw + (700px + 25px)); - top: 1px; - overflow-x: auto; - /* overflow-y: hidden; */ - background: hsl(0deg 0% 100% / 81%); -} - -/* @media screen and (max-width: calc(700px + 25px + 6px + 150px)) { */ -pluto-logs { - display: flex !important; -} - -pluto-logs-container { - position: static; - /* right: min(-6px, 700px + 25px - 100vw); */ - /* width: 20px; */ - /* transition: width ease-in-out 100ms; */ - /* padding-left: 80px; */ - width: 100%; + overflow-x: hidden; + overflow-y: auto; + max-height: 50vh; + margin-right: 1.3rem; } pluto-logs-container:not(:empty) { background: var(--pluto-logs-bg-color); - padding: 10px; -} - -pluto-logs-container:focus-within, -pluto-logs-container:hover { - /* width: 30vw; */ -} - -pluto-logs-container pluto-logs { - /* grid-template-columns: repeat(auto-fill, 0px); */ -} -pluto-logs-container:focus-within pluto-logs, -pluto-logs-container:hover pluto-logs { - /* grid-template-columns: repeat(auto-fill, 10px); */ -} -/* - pluto-log-dot-positioner { - flex: 0 0 auto; - width: auto; - } - pluto-log-dot-sizer { - width: auto; - } */ -/* } */ -/* -@media screen and (min-width: calc(700px + 25px + 6px + 500px)) and (max-width: calc(700px + 25px + 6px + 500px + 500px)) { - pluto-logs-container { - right: calc(-500px - 6px); - width: calc(500px + 6px); - } + padding: 6px; } -@media screen and (min-width: calc(700px + 25px + 6px + 500px + 500px)) { - pluto-logs-container { - width: calc(0.5 * (6px + 100vw - (700px + 25px))); - right: calc(-0.5 * (6px + 100vw - (700px + 25px))); - } -} */ pluto-logs-container pluto-progress-bars pluto-progress:not(:first-child) { margin-top: 10px; @@ -2399,12 +2346,13 @@ pluto-logs-container pluto-progress-bars pluto-progress:not(:first-child) { pluto-logs-container pluto-progress-bar { --c: var(--pluto-logs-progress-fill); - padding: 1px 3px; + padding: 0.3em 0.6em; background: linear-gradient(90deg, var(--c), var(--c)); background-repeat: no-repeat; width: 100%; transition: background-size cubic-bezier(0.14, 0.71, 0, 0.99) 2s, opacity linear 0.2s; - /* background-color: salmon; */ + display: grid; + align-items: center; } pluto-logs-container pluto-progress-bar.collapsed { @@ -2412,90 +2360,115 @@ pluto-logs-container pluto-progress-bar.collapsed { } pluto-logs { - display: grid; - grid-auto-rows: 15px; - flex-direction: row; - grid-template-rows: repeat(auto-fill, 15px); - grid-template-columns: repeat(auto-fill, 10px); - grid-auto-flow: column; + display: flex; + flex-direction: column; } pluto-logs:not(:first-child):not(:empty) { margin-top: 10px; } -pluto-log-dot-positioner { - display: block; - width: 10px; - transition: transform 100ms ease-in-out; +pluto-log-dot { + /* part 2 */ + /* box-shadow: -2px 0px 1px #00000014; */ + font-family: "Roboto Mono", monospace; + font-size: 0.6rem; + position: relative; + display: flex; + flex-grow: 1; + flex-direction: column; + padding: 1px 3px; + min-width: 18px; + min-height: 18px; + /* border-radius: 7px; */ + padding: 0.6em 0.9em 0.6em 0.3em; } -pluto-log-dot-positioner:hover, -pluto-log-dot-positioner.inspecting { - z-index: 5; - /* transform: translate(0px, -5px); */ +pluto-log-dot-positioner { + /* border-bottom: 1px solid #71717140; */ + display: flex; + flex-direction: row; + --bg-color: var(--pluto-logs-info-color); + --accent-color: var(--pluto-logs-info-accent-color); + --icon-image: unset; + background: var(--bg-color); + color: var(--accent-color); + margin: 2px; + border-radius: 6px; + /* border: 2px solid var(--accent-color); */ + /* border: 2px solid #0000001c; */ + /* box-shadow: 0px 0px 6px #00000036; */ + background: linear-gradient(148deg, var(--bg-color), transparent); + background-size: 200% 100%; } -/* pluto-log-dot-positioner { - transform: translateX(0px); -} */ -pluto-log-dot-positioner.inspecting { - transform: translateX(5px); -} -pluto-log-dot-positioner.inspecting pluto-log-dot { - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); -} -pluto-log-dot-positioner.inspecting ~ pluto-log-dot-positioner { - transform: translateX(10px); +pluto-log-dot-positioner:last-child { + /* border-bottom: none; */ } -pluto-log-dot-sizer { - display: block; - width: max-content; - transform: translateZ(10px); - max-width: 600px; - z-index: 300; - pointer-events: none; /* part 1: because the hit box is larger than the action log dot */ +pluto-log-truncated { + display: grid; + place-items: center; + font-family: var(--system-ui-font-stack); + opacity: 0.7; + padding: 0.7em; + font-style: italic; } -pluto-log-dot { - pointer-events: auto; /* part 2 */ - box-shadow: -2px 0px 1px #00000014; - font-family: "Roboto Mono", monospace; - font-size: 0.6rem; - /* position: absolute; */ +pluto-log-icon::before { + content: ""; + width: 1em; + height: 1em; + background-image: var(--icon-image); + background-size: 1em; + filter: var(--image-filters); display: inline-flex; - flex-direction: column; - padding: 1px 3px; - min-width: 18px; - min-height: 18px; - border-radius: 7px; + margin: 0.3em; } -pluto-log-dot { - background: var(--jl-info-color); - border: 3px solid var(--jl-info-accent-color); + +pluto-log-dot-positioner.Info { + --icon-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/information-circle-outline.svg"); } -pluto-log-dot.Warn { - background: var(--jl-warn-color); - border: 3px solid var(--jl-warn-accent-color); +pluto-log-dot-positioner.Info pluto-log-icon::before { + opacity: 0.4; } -pluto-log-dot.Error { - background: var(--jl-danger-color); - border: 3px solid var(--jl-danger-accent-color); +pluto-log-dot-positioner.Warn { + --bg-color: var(--pluto-logs-warn-color); + --accent-color: var(--pluto-logs-warn-accent-color); + --icon-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/warning-outline.svg"); +} +pluto-log-dot-positioner.Error { + --bg-color: var(--pluto-logs-danger-color); + --accent-color: var(--pluto-logs-danger-accent-color); + --icon-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/close-circle-outline.svg"); +} +pluto-log-dot-positioner.Debug { + --bg-color: var(--pluto-logs-debug-color); + --accent-color: var(--pluto-logs-debug-accent-color); + --icon-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/information-circle-outline.svg"); +} +pluto-log-dot-positioner.Stdout { + --icon-image: url("https://cdn.jsdelivr.net/gh/ionic-team/ionicons@5.5.1/src/svg/terminal-outline.svg"); } -pluto-log-dot.Debug { - background: var(--jl-debug-color); - border: 3px solid var(--jl-debug-accent-color); +pluto-log-dot-positioner.Stdout pluto-log-icon::before { + opacity: 0.4; } + pluto-log-dot.Progress { - width: 100px; - background: #94949466; - border: 3px solid var(--pluto-logs-progress-border); overflow: hidden; padding: 0px; + flex: 0 1 200px; + display: flex; + flex-direction: row; + align-items: stretch; + align-self: center; + outline: 3px solid var(--pluto-logs-progress-border); + outline-offset: -2px; + border-radius: 6px; + background: var(--pluto-logs-progress-bg); + font-size: 0.7rem; } pluto-log-dot.Stdout { - /* filter: sepia(0.3); */ --inner: hsl(36deg 20% 37%); --outer: hsl(31deg 12% 28%); background: radial-gradient(var(--inner), var(--inner) 20%, var(--outer)); @@ -2503,14 +2476,15 @@ pluto-log-dot.Stdout { border: 6px solid #b7b7b7; text-shadow: 1px 1px 2px #0000005e; min-width: 18em; + border-radius: 8px; } pluto-log-dot.Stdout::after, pluto-log-dot.Stdout::before { content: " "; - left: 6px; - right: 6px; - top: 6px; - bottom: 6px; + left: 0; + right: 0; + top: 0; + bottom: 0; position: absolute; display: block; pointer-events: none; @@ -2581,7 +2555,7 @@ pluto-log-dot-kwarg > * { flex: 0 1 auto; } pluto-log-dot-kwarg > pluto-key { - color: var(--pluto-schema-types-color); + color: var(--pluto-logs-key-color); margin-right: calc(1em - 30px); } pluto-log-dot-kwarg > pluto-key::after { @@ -2589,6 +2563,7 @@ pluto-log-dot-kwarg > pluto-key::after { } pluto-log-dot-kwarg > pluto-value { margin-left: 30px; + overflow-x: auto; } /* PRESENTATION MODE */ @@ -2991,3 +2966,13 @@ pluto-cell.hooked_up pluto-output { justify-content: flex-end; gap: 0.5em; } + +pluto-logs-container > header { + font-family: "Roboto Mono", monospace; + font-size: 1.3rem; + padding: 0.2em; + padding-bottom: 0; + opacity: 0.6; + font-weight: 700; + /* background: #494949; */ +} diff --git a/frontend/light_color.css b/frontend/light_color.css index a8b832b94a..e443706d26 100644 --- a/frontend/light_color.css +++ b/frontend/light_color.css @@ -69,10 +69,21 @@ --shoulder-hover-bg-color: rgba(0, 0, 0, 0.05); /* Logs */ - --pluto-logs-bg-color: #efeff3; + --pluto-logs-bg-color: hsl(0deg 0% 98%); + --pluto-logs-key-color: rgb(0 0 0 / 51%); --pluto-logs-progress-fill: #ffffff; + --pluto-logs-progress-bg: #e7e7e7; --pluto-logs-progress-border: hsl(210deg 16% 74%); + --pluto-logs-info-color: white; + --pluto-logs-info-accent-color: inherit; + --pluto-logs-warn-color: rgb(236 234 213); + --pluto-logs-warn-accent-color: #665f26; + --pluto-logs-danger-color: rgb(245 218 215); + --pluto-logs-danger-accent-color: rgb(172 66 52); + --pluto-logs-debug-color: rgb(236 223 247); + --pluto-logs-debug-accent-color: rgb(100 50 179); + /*Top navbar styling*/ --nav-h1-text-color: black; --nav-filepicker-color: #6f6f6f; From 490174487535a9a8fc82f691d765a532001500bb Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 5 Oct 2022 16:21:46 +0200 Subject: [PATCH 441/821] PlutoPkg: if Pkg.resolve fails, try updating the registries (#2298) --- src/Pluto.jl | 1 + src/packages/IOListener.jl | 32 ++++++++++++ src/packages/Packages.jl | 104 ++++++++++++++----------------------- src/packages/PkgCompat.jl | 6 +-- 4 files changed, 75 insertions(+), 68 deletions(-) create mode 100644 src/packages/IOListener.jl diff --git a/src/Pluto.jl b/src/Pluto.jl index 88e0cafce5..01b2878730 100644 --- a/src/Pluto.jl +++ b/src/Pluto.jl @@ -72,6 +72,7 @@ include("./analysis/MoreAnalysis.jl") include("./evaluation/WorkspaceManager.jl") include("./evaluation/MacroAnalysis.jl") +include("./packages/IOListener.jl") include("./packages/Packages.jl") include("./packages/PkgUtils.jl") include("./evaluation/Run.jl") diff --git a/src/packages/IOListener.jl b/src/packages/IOListener.jl new file mode 100644 index 0000000000..1de8d18c12 --- /dev/null +++ b/src/packages/IOListener.jl @@ -0,0 +1,32 @@ + +"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/Packages.jl b/src/packages/Packages.jl index c621924e6f..b284673507 100644 --- a/src/packages/Packages.jl +++ b/src/packages/Packages.jl @@ -113,26 +113,7 @@ function sync_nbpkg_core(notebook::Notebook, old_topology::NotebookTopology, new 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) - try - Pkg.resolve(notebook.nbpkg_ctx) - catch e - @warn "Failed to resolve Pkg environment. Removing Project compat entires and Manifest and trying again..." exception=e - reset_nbpkg(notebook; keep_project=true, save=false, backup=false) - notebook.nbpkg_ctx = PkgCompat.clear_compat_entries(notebook.nbpkg_ctx) - - Pkg.resolve(notebook.nbpkg_ctx) - end - end - end - end + resolve_with_auto_fixes(notebook, iolistener) end to_add = filter(PkgCompat.package_exists, added) @@ -337,6 +318,43 @@ function writebackup(notebook::Notebook) backup_path end +""" +Run `Pkg.resolve` on the notebook's package environment. Keep trying more and more invasive strategies to fix problems until the operation succeeds. +""" +function resolve_with_auto_fixes(notebook::Notebook, iolistener::IOListener) + + 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. Updating registries and trying again..." exception=e + + PkgCompat.update_registries(; force=true) + 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) + try + Pkg.resolve(notebook.nbpkg_ctx) + catch e + @warn "Failed to resolve Pkg environment. Removing Project compat entries and Manifest and trying again..." exception=e + + reset_nbpkg(notebook; keep_project=true, save=false, backup=false) + notebook.nbpkg_ctx = PkgCompat.clear_compat_entries(notebook.nbpkg_ctx) + + Pkg.resolve(notebook.nbpkg_ctx) + end + end + end + end + end +end + """ Reset the package environment of a notebook. This will remove the `Project.toml` and `Manifest.toml` files from the notebook's secret package environment folder, and if `save` is `true`, it will then save the notebook without embedded Project and Manifest. @@ -384,19 +402,7 @@ function update_nbpkg_core(notebook::Notebook; level::Pkg.UpgradeLevel=Pkg.UPLEV 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 - PkgCompat.update_registries(;force=false) - 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 + resolve_with_auto_fixes(notebook, iolistener) end startlistening(iolistener) @@ -526,35 +532,3 @@ function withinteractive(f::Function, value::Bool) 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 index 4cde461dc2..8e5f90759f 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -198,7 +198,7 @@ end """ Check when the registries were last updated. If it is recent (max 7 days), then `Pkg.UPDATED_REGISTRY_THIS_SESSION[]` is set to `true`, which will prevent Pkg from doing an automatic registry update. -Returns the new value of `Pkg.UPDATED_REGISTRY_THIS_SESSION`. +Returns the new value of `Pkg.UPDATED_REGISTRY_THIS_SESSION[]`. """ function check_registry_age(max_age_ms = 1000.0 * 60 * 60 * 24 * 7)::Bool if get(ENV, "GITHUB_ACTIONS", "false") == "true" @@ -208,7 +208,7 @@ function check_registry_age(max_age_ms = 1000.0 * 60 * 60 * 24 * 7)::Bool paths = _get_registry_paths() isempty(paths) && return _updated_registries_compat[] - ages = map(paths) do p + mtimes = map(paths) do p try mtime(p) catch @@ -216,7 +216,7 @@ function check_registry_age(max_age_ms = 1000.0 * 60 * 60 * 24 * 7)::Bool end end - if all(ages .> time() - max_age_ms) + if all(mtimes .> time() - max_age_ms) _updated_registries_compat[] = true end _updated_registries_compat[] From 6fd558b6c9eb8eb17d3dfa2c7b41192e9272f5c0 Mon Sep 17 00:00:00 2001 From: kcin96 <26337751+kcin96@users.noreply.github.com> Date: Wed, 5 Oct 2022 15:31:06 +0100 Subject: [PATCH 442/821] Using RegistryInstances.jl to avoid some Pkg internal API #2118 (#2289) Co-authored-by: Nicholas Ting Yik Ming Co-authored-by: Fons van der Plas --- Project.toml | 4 +- src/packages/PkgCompat.jl | 75 +++++++++++++------------------------- test/helpers.jl | 10 +++++ test/packages/Basic.jl | 11 +----- test/packages/PkgCompat.jl | 24 ++++++++++++ 5 files changed, 63 insertions(+), 61 deletions(-) diff --git a/Project.toml b/Project.toml index ca1a7306cc..e4c9a6bb95 100644 --- a/Project.toml +++ b/Project.toml @@ -21,6 +21,7 @@ MsgPack = "99f44e22-a591-53d1-9472-aa23ef4bd671" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrecompileSignatures = "91cefc8d-f054-46dc-8f8c-26e11d7c5411" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +RegistryInstances = "2792f1a3-b283-48e8-9a74-f99dce5104f3" RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" @@ -36,6 +37,7 @@ HypertextLiteral = "0.7, 0.8, 0.9" MIMEs = "0.1" MsgPack = "1.1" PrecompileSignatures = "3" +RegistryInstances = "0.1" RelocatableFolders = "0.1, 0.2, 0.3" Tables = "1" URIs = "1.3" @@ -43,12 +45,12 @@ julia = "^1.6" [extras] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" -Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" [targets] test = ["DataFrames", "OffsetArrays", "Random", "Sockets", "Test", "TimerOutputs", "Memoize"] diff --git a/src/packages/PkgCompat.jl b/src/packages/PkgCompat.jl index 8e5f90759f..939ec7a075 100644 --- a/src/packages/PkgCompat.jl +++ b/src/packages/PkgCompat.jl @@ -4,7 +4,7 @@ export package_versions, package_completions import Pkg import Pkg.Types: VersionRange - +import RegistryInstances import ..Pluto # Should be in Base @@ -142,34 +142,15 @@ 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 +# (✅ "Public" API using RegistryInstances) +"Return all installed registries as `RegistryInstances.RegistryInstance` structs." +_get_registries() = RegistryInstances.reachable_registries() -# (⛔️ 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." +# (✅ "Public" API using RegistryInstances) +"The cached output value of `_get_registries`." const _parsed_registries = Ref(_get_registries()) -# (⛔️ Internal API) +# (✅ "Public" API using RegistryInstances) "Re-parse the installed registries from disk." function refresh_registry_cache() _parsed_registries[] = _get_registries() @@ -205,7 +186,7 @@ function check_registry_age(max_age_ms = 1000.0 * 60 * 60 * 24 * 7)::Bool # don't do this optimization in CI return false end - paths = _get_registry_paths() + paths = [s.path for s in _get_registries()] isempty(paths) && return _updated_registries_compat[] mtimes = map(paths) do p @@ -294,7 +275,7 @@ end # Package versions ### -# (⛔️ Internal API) +# (✅ "Public" API) """ Return paths to all found registry entries of a given package name. @@ -306,17 +287,17 @@ julia> Pluto.PkgCompat._registry_entries("Pluto") ``` """ function _registry_entries(package_name::AbstractString, registries::Vector=_parsed_registries[])::Vector{String} - flatmap(registries) do (rpath, r) - packages = values(r["packages"]) + flatmap(registries) do reg + packages = values(reg.pkgs) String[ - joinpath(rpath, d["path"]) + joinpath(reg.path, d.path) for d in packages - if d["name"] == package_name + if d.name == package_name ] end end -# (⛔️ Internal API) +# (🐸 "Public API", but using PkgContext) 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,)) @@ -327,8 +308,7 @@ function _package_versions_from_path(registry_entry_fullpath::AbstractString)::V vd |> keys |> collect end -# ⚠️ Internal API with fallback -# See https://github.com/JuliaLang/Pkg.jl/issues/2607 +# ✅ "Public" API using RegistryInstances """ Return all registered versions of the given package. Returns `["stdlib"]` for standard libraries, and a `Vector{VersionNumber}` for registered packages. """ @@ -337,23 +317,18 @@ function package_versions(package_name::AbstractString)::Vector ["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 + flatmap(_parsed_registries[]) do reg + uuids_with_name = RegistryInstances.uuids_from_name(reg, package_name) + flatmap(uuids_with_name) do u + pkg = get(reg, u, nothing) + if pkg !== nothing + info = RegistryInstances.registry_info(pkg) + collect(keys(info.version_info)) + else + [] end end - else - ps = _registry_entries(package_name) - flatmap(_package_versions_from_path, ps) - end) |> sort + end catch e @warn "Pkg compat: failed to get installable versions." exception=(e,catch_backtrace()) ["latest"] diff --git a/test/helpers.jl b/test/helpers.jl index ee5c51b3e5..c254a748f2 100644 --- a/test/helpers.jl +++ b/test/helpers.jl @@ -17,6 +17,7 @@ using Sockets using Test using HTTP import Distributed +import Pkg function Base.show(io::IO, s::SymbolsState) print(io, "SymbolsState([") @@ -234,3 +235,12 @@ function verify_no_running_processes() @error "Not all notebook processes were closed during tests!" Distributed.procs() end end + +# We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. + +const pluto_test_registry_spec = Pkg.RegistrySpec(; + url="https://github.com/JuliaPluto/PlutoPkgTestRegistry", + uuid=Base.UUID("96d04d5f-8721-475f-89c4-5ee455d3eda0"), + name="PlutoPkgTestRegistry", +) + diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl index f64466899f..5df3a3135b 100644 --- a/test/packages/Basic.jl +++ b/test/packages/Basic.jl @@ -8,21 +8,12 @@ import Pluto.PkgUtils import Pluto.PkgCompat import Distributed -# We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. - -const pluto_test_registry_spec = Pkg.RegistrySpec(; - url="https://github.com/JuliaPluto/PlutoPkgTestRegistry", - uuid=Base.UUID("96d04d5f-8721-475f-89c4-5ee455d3eda0"), - name="PlutoPkgTestRegistry", -) - @testset "Built-in Pkg" begin - # Pkg.Registry.rm("General") + # We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. Pkg.Registry.add(pluto_test_registry_spec) - # We have our own registry for these test! Take a look at https://github.com/JuliaPluto/PlutoPkgTestRegistry#readme for more info about the test packages and their dependencies. @testset "Basic" begin 🍭 = ServerSession() diff --git a/test/packages/PkgCompat.jl b/test/packages/PkgCompat.jl index 9ca319b6fb..b600f3df15 100644 --- a/test/packages/PkgCompat.jl +++ b/test/packages/PkgCompat.jl @@ -6,6 +6,7 @@ import Pkg @testset "PkgCompat" begin + PkgCompat.refresh_registry_cache() @testset "Available versions" begin vs = PkgCompat.package_versions("HTTP") @@ -28,6 +29,29 @@ import Pkg @test isempty(vs) @test !PkgCompat.package_exists("Dateskjashdfkjahsdfkjh") + + end + + @testset "Registry queries" begin + Pkg.Registry.add(pluto_test_registry_spec) + PkgCompat.refresh_registry_cache() + + es = PkgCompat._registry_entries("PlutoPkgTestA") + @test length(es) == 1 + @test occursin("P/PlutoPkgTestA", only(es)) + @test occursin("PlutoPkgTestRegistry", only(es)) + + es = PkgCompat._registry_entries("Pluto") + @test length(es) == 1 + @test occursin("P/Pluto", only(es)) + @test occursin("General", only(es)) + + es = PkgCompat._registry_entries("HelloWorldC_jll") + @test length(es) == 1 + @test occursin(joinpath("H", "HelloWorldC_jll"), only(es)) + @test occursin("General", only(es)) + + Pkg.Registry.rm(pluto_test_registry_spec) end @testset "Installed versions" begin From 9ad421a9fc28693c8935fb4434cefa908584f1ed Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 5 Oct 2022 17:48:29 +0200 Subject: [PATCH 443/821] small performance optimization for topological_order --- 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 4f21de965b..a2b0bb6146 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -19,7 +19,7 @@ function topological_order(topology::NotebookTopology, roots::AbstractVector{Cel return Ok() elseif haskey(errable, cell) return Ok() - elseif length(entries) > 0 && entries[end] == cell + elseif length(entries) > 0 && entries[end] === cell return Ok() # a cell referencing itself is legal elseif cell in entries currently_in = setdiff(entries, exits) @@ -49,7 +49,7 @@ function topological_order(topology::NotebookTopology, roots::AbstractVector{Cel end referencers = where_referenced(topology, cell) |> Iterators.reverse for c in (allow_multiple_defs ? referencers : union(assigners, referencers)) - if c != cell + if c !== cell child_result = bfs(c) # No cycle for this child or the cycle has no soft edges @@ -74,7 +74,7 @@ function topological_order(topology::NotebookTopology, roots::AbstractVector{Cel delete!(errable, cycled_cell) end # 2. Remove the current child (c) from the entries if it was just added - if entries[end] == c + if entries[end] === c pop!(entries) end From e2b94400f2074a188f660b6f161fbf911da64d5d Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 5 Oct 2022 19:07:03 +0200 Subject: [PATCH 444/821] fix windows test --- test/packages/PkgCompat.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/packages/PkgCompat.jl b/test/packages/PkgCompat.jl index b600f3df15..ad0bdc0298 100644 --- a/test/packages/PkgCompat.jl +++ b/test/packages/PkgCompat.jl @@ -48,7 +48,7 @@ import Pkg es = PkgCompat._registry_entries("HelloWorldC_jll") @test length(es) == 1 - @test occursin(joinpath("H", "HelloWorldC_jll"), only(es)) + @test occursin("H/HelloWorldC_jll", only(es)) @test occursin("General", only(es)) Pkg.Registry.rm(pluto_test_registry_spec) From 0e4f16f03c40beeeb4a4215b405f4421fefc7dca Mon Sep 17 00:00:00 2001 From: Rik Huijzer Date: Wed, 5 Oct 2022 21:53:36 +0200 Subject: [PATCH 445/821] Allow rich MIME override (#2299) Co-authored-by: Fons van der Plas --- src/Configuration.jl | 8 +++++++- src/evaluation/WorkspaceManager.jl | 2 ++ src/runner/PlutoRunner.jl | 16 ++++++++++++---- test/Configuration.jl | 23 +++++++++++++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Configuration.jl b/src/Configuration.jl index 5e12d5b06f..920782d25d 100644 --- a/src/Configuration.jl +++ b/src/Configuration.jl @@ -131,22 +131,26 @@ const RUN_NOTEBOOK_ON_LOAD_DEFAULT = true const WORKSPACE_USE_DISTRIBUTED_DEFAULT = true const LAZY_WORKSPACE_CREATION_DEFAULT = false const CAPTURE_STDOUT_DEFAULT = true +const WORKSPACE_CUSTOM_STARTUP_EXPR_DEFAULT = nothing """ EvaluationOptions([; kwargs...]) -Options to change Pluto's evaluation behaviour during internal testing. These options are not intended to be changed during normal use. +Options to change Pluto's evaluation behaviour during internal testing and by downstream packages. +These options are not intended to be changed during normal use. - `run_notebook_on_load::Bool = $RUN_NOTEBOOK_ON_LOAD_DEFAULT` Whether to evaluate a notebook on load. - `workspace_use_distributed::Bool = $WORKSPACE_USE_DISTRIBUTED_DEFAULT` Whether to start notebooks in a separate process. - `lazy_workspace_creation::Bool = $LAZY_WORKSPACE_CREATION_DEFAULT` - `capture_stdout::Bool = $CAPTURE_STDOUT_DEFAULT` +- `workspace_custom_startup_expr::Union{Nothing,Expr} = $WORKSPACE_CUSTOM_STARTUP_EXPR_DEFAULT` An expression to be evaluated in the workspace process before running notebook code. """ @option mutable struct EvaluationOptions run_notebook_on_load::Bool = RUN_NOTEBOOK_ON_LOAD_DEFAULT workspace_use_distributed::Bool = WORKSPACE_USE_DISTRIBUTED_DEFAULT lazy_workspace_creation::Bool = LAZY_WORKSPACE_CREATION_DEFAULT capture_stdout::Bool = CAPTURE_STDOUT_DEFAULT + workspace_custom_startup_expr::Union{Nothing,Expr} = WORKSPACE_CUSTOM_STARTUP_EXPR_DEFAULT end const COMPILE_DEFAULT = nothing @@ -253,6 +257,7 @@ function from_flat_kwargs(; workspace_use_distributed::Bool = WORKSPACE_USE_DISTRIBUTED_DEFAULT, lazy_workspace_creation::Bool = LAZY_WORKSPACE_CREATION_DEFAULT, capture_stdout::Bool = CAPTURE_STDOUT_DEFAULT, + workspace_custom_startup_expr::Union{Nothing,Expr} = WORKSPACE_CUSTOM_STARTUP_EXPR_DEFAULT, compile::Union{Nothing,String} = COMPILE_DEFAULT, sysimage::Union{Nothing,String} = SYSIMAGE_DEFAULT, banner::Union{Nothing,String} = BANNER_DEFAULT, @@ -292,6 +297,7 @@ function from_flat_kwargs(; workspace_use_distributed, lazy_workspace_creation, capture_stdout, + workspace_custom_startup_expr, ) compiler = CompilerOptions(; compile, diff --git a/src/evaluation/WorkspaceManager.jl b/src/evaluation/WorkspaceManager.jl index e34d82df87..c556e97e15 100644 --- a/src/evaluation/WorkspaceManager.jl +++ b/src/evaluation/WorkspaceManager.jl @@ -60,6 +60,8 @@ function make_workspace((session, notebook)::SN; is_offline_renderer::Bool=false pid end + Distributed.remotecall_eval(Main, [pid], session.options.evaluation.workspace_custom_startup_expr) + Distributed.remotecall_eval(Main, [pid], :(PlutoRunner.notebook_id[] = $(notebook.notebook_id))) remote_log_channel = Core.eval(Main, quote diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index 50da8c8507..5a3d69cbf6 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -947,7 +947,7 @@ const default_stdout_iocontext = IOContext(devnull, :is_pluto => false, ) -const imagemimes = [MIME"image/svg+xml"(), MIME"image/png"(), MIME"image/jpg"(), MIME"image/jpeg"(), MIME"image/bmp"(), MIME"image/gif"()] +const imagemimes = MIME[MIME"image/svg+xml"(), MIME"image/png"(), MIME"image/jpg"(), MIME"image/jpeg"(), MIME"image/bmp"(), MIME"image/gif"()] # in descending order of coolness # text/plain always matches - almost always """ @@ -955,7 +955,7 @@ The MIMEs that Pluto supports, in order of how much I like them. `text/plain` should always match - the difference between `show(::IO, ::MIME"text/plain", x)` and `show(::IO, x)` is an unsolved mystery. """ -const allmimes = [MIME"application/vnd.pluto.table+object"(); MIME"application/vnd.pluto.divelement+object"(); MIME"text/html"(); imagemimes; MIME"application/vnd.pluto.tree+object"(); MIME"text/latex"(); MIME"text/plain"()] +const allmimes = MIME[MIME"application/vnd.pluto.table+object"(); MIME"application/vnd.pluto.divelement+object"(); MIME"text/html"(); imagemimes; MIME"application/vnd.pluto.tree+object"(); MIME"text/latex"(); MIME"text/plain"()] """ @@ -1086,11 +1086,19 @@ function use_tree_viewer_for_struct(@nospecialize(x::T))::Bool where T end end +""" + is_mime_enabled(::MIME) -> Bool + +Return whether the argument's mimetype is enabled. +This defaults to `true`, but additional dispatches can be set to `false` by downstream packages. +""" +is_mime_enabled(::MIME) = true + "Return the first mimetype in `allmimes` which can show `x`." function mimetype(x) # ugly code to fix an ugly performance problem for m in allmimes - if pluto_showable(m, x) + if pluto_showable(m, x) && is_mime_enabled(m) return m end end @@ -1105,7 +1113,7 @@ Like two-argument `Base.show`, except: function show_richest(io::IO, @nospecialize(x))::Tuple{<:Any,MIME} mime = mimetype(x) - if mime isa MIME"text/plain" && use_tree_viewer_for_struct(x) + if mime isa MIME"text/plain" && is_mime_enabled(MIME"application/vnd.pluto.tree+object"()) && use_tree_viewer_for_struct(x) tree_data(x, io), MIME"application/vnd.pluto.tree+object"() elseif mime isa MIME"application/vnd.pluto.tree+object" try diff --git a/test/Configuration.jl b/test/Configuration.jl index c45d0751d0..0b6b131618 100644 --- a/test/Configuration.jl +++ b/test/Configuration.jl @@ -61,8 +61,6 @@ end port = 1238 options = Pluto.Configuration.from_flat_kwargs(; port, launch_browser=false, workspace_use_distributed=false) 🍭 = Pluto.ServerSession(; options) - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient host = 🍭.options.server.host secret = 🍭.secret println("Launching test server...") @@ -164,6 +162,27 @@ end end +@testset "disable mimetype via workspace_custom_startup_expr" begin + 🍭 = ServerSession() + 🍭.options.evaluation.workspace_use_distributed = true + 🍭.options.evaluation.workspace_custom_startup_expr = quote + PlutoRunner.is_mime_enabled(m::MIME"application/vnd.pluto.tree+object") = false + end + + nb = Pluto.Notebook([ + Pluto.Cell("x = [1, 2]") + Pluto.Cell("struct Foo; x; end") + Pluto.Cell("Foo(x)") + ]) + + Pluto.update_run!(🍭, nb, nb.cells) + @test nb.cells[1].output.body == repr(MIME"text/plain"(), [1,2]) + @test nb.cells[1].output.mime isa MIME"text/plain" + @test nb.cells[3].output.mime isa MIME"text/plain" + + Pluto.WorkspaceManager.unmake_workspace((🍭, nb)) +end + # TODO are the processes closed properly? # TODO we reuse the same port without awaiting the shutdown of the previous server From bf9f128be27967ca625d3fa44d2abe41217cccfb Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 5 Oct 2022 22:13:55 +0200 Subject: [PATCH 446/821] Performance: skip save during bond runs --- src/evaluation/Run.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 2d79cc6460..42b68a4af3 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -13,6 +13,7 @@ function run_reactive!( old_topology::NotebookTopology, new_topology::NotebookTopology, roots::Vector{Cell}; + save::Bool=true, deletion_hook::Function = WorkspaceManager.move_vars, user_requested_run::Bool = true, bond_value_pairs=zip(Symbol[],Any[]), @@ -24,6 +25,7 @@ function run_reactive!( old_topology, new_topology, roots; + save, deletion_hook, user_requested_run, bond_value_pairs, @@ -43,6 +45,7 @@ function run_reactive_core!( old_topology::NotebookTopology, new_topology::NotebookTopology, roots::Vector{Cell}; + save::Bool=true, deletion_hook::Function = WorkspaceManager.move_vars, user_requested_run::Bool = true, already_run::Vector{Cell} = Cell[], @@ -118,7 +121,7 @@ function run_reactive_core!( end # Save the notebook. This is the only time that we save the notebook, so any state changes that influence the file contents (like `depends_on_disabled_cells`) should be behind this point. - save_notebook(session, notebook) + save && save_notebook(session, notebook) # Send intermediate updates to the clients at most 20 times / second during a reactive run. (The effective speed of a slider is still unbounded, because the last update is not throttled.) # flush_send_notebook_changes_throttled, @@ -196,7 +199,7 @@ function run_reactive_core!( update_dependency_cache!(notebook) save_notebook(session, notebook) - return run_reactive_core!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook, user_requested_run, already_run = to_run[1:i]) + return run_reactive_core!(session, notebook, new_topology, new_new_topology, to_run; save, deletion_hook, user_requested_run, already_run = to_run[1:i]) elseif !isempty(implicit_usings) new_soft_definitions = WorkspaceManager.collect_soft_definitions((session, notebook), implicit_usings) notebook.topology = new_new_topology = with_new_soft_definitions(new_topology, cell, new_soft_definitions) @@ -205,7 +208,7 @@ function run_reactive_core!( update_dependency_cache!(notebook) save_notebook(session, notebook) - return run_reactive_core!(session, notebook, new_topology, new_new_topology, to_run; deletion_hook, user_requested_run, already_run = to_run[1:i]) + return run_reactive_core!(session, notebook, new_topology, new_new_topology, to_run; save, deletion_hook, user_requested_run, already_run = to_run[1:i]) end end @@ -387,7 +390,7 @@ function update_save_run!( sync_nbpkg(session, notebook, old, new; save=(save && !session.options.server.disable_writing_notebook_files), take_token=false) if !(isempty(to_run_online) && session.options.evaluation.lazy_workspace_creation) && will_run_code(notebook) # not async because that would be double async - run_reactive_core!(session, notebook, old, new, to_run_online; kwargs...) + run_reactive_core!(session, notebook, old, new, to_run_online; save, kwargs...) # run_reactive_async!(session, notebook, old, new, to_run_online; deletion_hook=deletion_hook, run_async=false, kwargs...) end end From e2e29017cccca7e513ce4a6ee39184be2a582f3e Mon Sep 17 00:00:00 2001 From: Rik Huijzer Date: Wed, 5 Oct 2022 23:32:56 +0200 Subject: [PATCH 447/821] Allow downstream packages to disable stacktrace prettification (#2302) --- src/runner/PlutoRunner.jl | 46 +++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl index 5a3d69cbf6..765b59cc02 100644 --- a/src/runner/PlutoRunner.jl +++ b/src/runner/PlutoRunner.jl @@ -986,33 +986,41 @@ format_output(@nospecialize(x); context=default_iocontext) = format_output_defau format_output(::Nothing; context=default_iocontext) = ("", MIME"text/plain"()) +"Downstream packages can set this to false to obtain unprettified stack traces." +const PRETTY_STACKTRACES = Ref(true) + function format_output(val::CapturedException; context=default_iocontext) - ## We hide the part of the stacktrace that belongs to Pluto's evalling of user code. - stack = [s for (s, _) in val.processed_bt] + stacktrace = if PRETTY_STACKTRACES[] + ## We hide the part of the stacktrace that belongs to Pluto's evalling of user code. + stack = [s for (s, _) in val.processed_bt] + + # function_wrap_index = findfirst(f -> occursin("function_wrapped_cell", String(f.func)), stack) - # function_wrap_index = findfirst(f -> occursin("function_wrapped_cell", String(f.func)), stack) + function_wrap_index = findlast(f -> occursin("#==#", String(f.file)), stack) - function_wrap_index = findlast(f -> occursin("#==#", String(f.file)), stack) + if function_wrap_index === nothing + for _ in 1:2 + until = findfirst(b -> b.func == :eval, reverse(stack)) + stack = until === nothing ? stack : stack[1:end - until] + end + else + stack = stack[1:function_wrap_index] + end - if function_wrap_index === nothing - for _ in 1:2 - until = findfirst(b -> b.func == :eval, reverse(stack)) - stack = until === nothing ? stack : stack[1:end - until] + pretty = map(stack) do s + Dict( + :call => pretty_stackcall(s, s.linfo), + :inlined => s.inlined, + :file => basename(String(s.file)), + :path => String(s.file), + :line => s.line, + ) end else - stack = stack[1:function_wrap_index] + val end - pretty = map(stack) do s - Dict( - :call => pretty_stackcall(s, s.linfo), - :inlined => s.inlined, - :file => basename(String(s.file)), - :path => String(s.file), - :line => s.line, - ) - end - Dict{Symbol,Any}(:msg => sprint(try_showerror, val.ex), :stacktrace => pretty), MIME"application/vnd.pluto.stacktrace+object"() + Dict{Symbol,Any}(:msg => sprint(try_showerror, val.ex), :stacktrace => stacktrace), MIME"application/vnd.pluto.stacktrace+object"() end function format_output(binding::Base.Docs.Binding; context=default_iocontext) From 1e803843d7c8ee742e1e6c22624f99ab83660b18 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 6 Oct 2022 00:46:35 +0200 Subject: [PATCH 448/821] =?UTF-8?q?=F0=9F=A7=B9=20remove=20fakeclient=20in?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Bonds.jl | 7 ------- test/DependencyCache.jl | 4 ---- test/Dynamic.jl | 5 ----- test/Events.jl | 4 ---- test/Logging.jl | 3 --- test/MacroAnalysis.jl | 6 ------ test/Notebook.jl | 11 ----------- test/React.jl | 26 -------------------------- test/ReloadFromFile.jl | 5 ----- test/RichOutput.jl | 9 --------- test/WorkspaceManager.jl | 16 ---------------- test/cell_disabling.jl | 4 ---- 12 files changed, 100 deletions(-) diff --git a/test/Bonds.jl b/test/Bonds.jl index d8469e2b16..21ab86d598 100644 --- a/test/Bonds.jl +++ b/test/Bonds.jl @@ -7,8 +7,6 @@ import Distributed 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient @testset "AbstractPlutoDingetjes.jl" begin 🍭.options.evaluation.workspace_use_distributed = true @@ -178,7 +176,6 @@ import Distributed # 34 Cell("@bind pv5 PossibleValuesTest(1:10)"), ]) - fakeclient.connected_notebook = notebook function set_bond_value(name, value, is_first_value=false) @@ -326,9 +323,6 @@ import Distributed @testset "Dependent Bound Variables" begin 🍭 = ServerSession() - 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient 🍭.options.evaluation.workspace_use_distributed = true notebook = Notebook([ Cell(raw"""@bind x HTML("")"""), @@ -362,7 +356,6 @@ import Distributed Cell(raw"""hello2"""), #11 Cell(raw"""using AbstractPlutoDingetjes"""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) function set_bond_values!(notebook:: Notebook, bonds:: Dict; is_first_value=false) diff --git a/test/DependencyCache.jl b/test/DependencyCache.jl index 96d69bbd6d..77b2c0b660 100644 --- a/test/DependencyCache.jl +++ b/test/DependencyCache.jl @@ -7,9 +7,6 @@ using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = Notebook([ Cell("x = 1"), # prerequisite of test cell Cell("f(x) = x + y"), # depends on test cell @@ -23,7 +20,6 @@ using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook Cell("g(6) + g(6,6)"), Cell("using Dates"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) state = Pluto.notebook_to_js(notebook) diff --git a/test/Dynamic.jl b/test/Dynamic.jl index b3bc9d4b44..fcdcadc984 100644 --- a/test/Dynamic.jl +++ b/test/Dynamic.jl @@ -35,8 +35,6 @@ end client = ClientSession(:buffery, buffer) 🍭 = ServerSession() - # 🍭.connected_clients[client.id] = client - notebook = Notebook([ Cell( @@ -179,10 +177,8 @@ end end @testset "PlutoRunner API" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = true - 🍭.connected_clients[fakeclient.id] = fakeclient notebook = Notebook([ Cell("PlutoRunner.notebook_id[] |> Text"), @@ -203,7 +199,6 @@ end Cell("x = Dict(:a => 6)"), Cell("PlutoRunner.publish_to_js(x)"), ]) - fakeclient.connected_notebook = notebook update_save_run!(🍭, notebook, notebook.cells) @test notebook.cells[1].output.body == notebook.notebook_id |> string diff --git a/test/Events.jl b/test/Events.jl index 990b223b6e..1dbfdaee03 100644 --- a/test/Events.jl +++ b/test/Events.jl @@ -16,15 +16,11 @@ import UUIDs: UUID 🍭.options.server.on_event = test_listener 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient notebook = Notebook([ Cell("[1,1,[1]]"), Cell("Dict(:a => [:b, :c])"), ]) - fakeclient.connected_notebook = notebook - update_run!(🍭, notebook, notebook.cells) WorkspaceManager.unmake_workspace((🍭, notebook); verbose=false) @test_broken events[1:3] == ["NewNotebookEvent", "OpenNotebookEvent" , "FileSaveEvent"] diff --git a/test/Logging.jl b/test/Logging.jl index 1a29535d8f..125f048f2f 100644 --- a/test/Logging.jl +++ b/test/Logging.jl @@ -7,9 +7,6 @@ using Pluto.WorkspaceManager: poll 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = true - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = Notebook(Cell.([ "println(123)", "println(stdout, 123)", diff --git a/test/MacroAnalysis.jl b/test/MacroAnalysis.jl index d96d15cd82..59e79157c3 100644 --- a/test/MacroAnalysis.jl +++ b/test/MacroAnalysis.jl @@ -7,9 +7,6 @@ import Memoize: @memoize 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - @testset "Base macro call" begin notebook = Notebook([ Cell("@enum Fruit 🍎 🍐"), @@ -887,9 +884,6 @@ import Memoize: @memoize 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = Notebook([ Cell("using Memoize"), Cell(""" diff --git a/test/Notebook.jl b/test/Notebook.jl index 24ee129a0c..7e7d5480ec 100644 --- a/test/Notebook.jl +++ b/test/Notebook.jl @@ -190,11 +190,6 @@ end 🍭 = ServerSession() for (name, nb) in nbs nb.path = tempname() * "é🧡💛.jl" - - client = ClientSession(Symbol("client", rand(UInt16)), nothing) - client.connected_notebook = nb - - 🍭.connected_clients[client.id] = client end @testset "I/O basic" begin @@ -209,8 +204,6 @@ end @testset "Cell Metadata" begin 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient @testset "Disabling & Metadata" begin nb = cell_metadata_notebook() @@ -246,8 +239,6 @@ end @testset "Notebook Metadata" begin 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient nb = notebook_metadata_notebook() update_run!(🍭, nb, nb.cells) @@ -273,8 +264,6 @@ end @testset "Skip as script" begin 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient nb = skip_as_script_notebook() update_run!(🍭, nb, nb.cells) diff --git a/test/React.jl b/test/React.jl index a4b112a02c..eb1b5e0d7a 100644 --- a/test/React.jl +++ b/test/React.jl @@ -7,9 +7,6 @@ import Distributed 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - @testset "Basic $(parallel ? "distributed" : "single-process")" for parallel in [false, true] 🍭.options.evaluation.workspace_use_distributed = parallel @@ -28,7 +25,6 @@ import Distributed Cell("import Distributed"), Cell("Distributed.myid()"), ]) - fakeclient.connected_notebook = notebook @test !haskey(WorkspaceManager.workspaces, notebook.notebook_id) @@ -95,7 +91,6 @@ import Distributed Cell("g(x) = 5"), Cell("g = 6"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1]) @@ -218,7 +213,6 @@ import Distributed write(file, read(normpath(Pluto.project_relative_path("src", "webserver", "Firebasey.jl")))) notebook = Pluto.load_notebook_nobackup(file) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -313,7 +307,6 @@ import Distributed Cell("using Dates"), Cell("July"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:1]) @@ -333,7 +326,6 @@ import Distributed Cell("December"), Cell(""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -365,7 +357,6 @@ import Distributed Cell("archive_artifact"), Cell("using Unknown.Package"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -389,7 +380,6 @@ import Distributed Cell(""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -417,7 +407,6 @@ import Distributed "December = 3", ])) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -450,7 +439,6 @@ import Distributed "b = 10", ])) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @test :conj ∈ notebook.topology.nodes[notebook.cells[3]].soft_definitions @@ -476,7 +464,6 @@ import Distributed "MyStruct(1.) |> inv" ])) cell(idx) = notebook.cells[idx] - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @test cell(1) |> noerror @@ -505,7 +492,6 @@ import Distributed "inv(a)", ])) cell(idx) = notebook.cells[idx] - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @test all(noerror, notebook.cells) @@ -537,7 +523,6 @@ import Distributed "Base.inv(x::Float64) = a", "d = Float64(c)", ])) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @test all(noerror, notebook.cells) @@ -552,7 +537,6 @@ import Distributed "Base.inv(::Float64) = y", "inv(1.0)", ])) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @test notebook.cells[end].errored == true @@ -575,7 +559,6 @@ import Distributed "", ])) cell(idx) = notebook.cells[idx] - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) output_21 = cell(2).output.body @@ -683,7 +666,6 @@ import Distributed e14 end"""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:4]) @test notebook.cells[1] |> noerror @@ -902,7 +884,6 @@ import Distributed Cell("j(x) = (x > 0) ? f(x-1) : :done") Cell("f(8)") ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:3]) @test occursinerror("Cyclic reference", notebook.cells[1]) @@ -1003,7 +984,6 @@ import Distributed Cell("struct a; x end"), Cell("a") ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:2]) @test notebook.cells[1].output.body == notebook.cells[2].output.body @@ -1045,7 +1025,6 @@ import Distributed Cell("h(4)"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1]) @test notebook.cells[1].output.body == "f" || startswith(notebook.cells[1].output.body, "f (generic function with ") @@ -1071,7 +1050,6 @@ import Distributed notebook = Notebook([ Cell("x = 3") ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1]) setcode!(notebook.cells[1], "x = x + 1") @@ -1138,7 +1116,6 @@ import Distributed Cell("using Dates"), Cell("year(DateTime(31))"), ]) - fakeclient.connected_notebook = notebook @testset "Changing functions" begin @@ -1299,7 +1276,6 @@ import Distributed Cell("h = [x -> x + b][1]"), Cell("h(8)"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1:2]) @test notebook.cells[1].output.body == "1" @@ -1355,7 +1331,6 @@ import Distributed Cell("map(14:14) do i; global apple = orange; end"), Cell("orange = 15"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1]) update_run!(🍭, notebook, notebook.cells[2]) @@ -1651,7 +1626,6 @@ import Distributed Cell("g(x) = x + y"), Cell("y = 22"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells[1]) diff --git a/test/ReloadFromFile.jl b/test/ReloadFromFile.jl index 06277a77cd..01bf9358ca 100644 --- a/test/ReloadFromFile.jl +++ b/test/ReloadFromFile.jl @@ -27,14 +27,9 @@ end 🍭.options.server.auto_reload_from_file = true - timeout_between_tests = 🍭.options.server.auto_reload_from_file_cooldown * 1.5 - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = SessionActions.new(🍭; run_async=false) - fakeclient.connected_notebook = notebook ### sleep(timeout_between_tests) diff --git a/test/RichOutput.jl b/test/RichOutput.jl index 26d1abb445..6675aa4008 100644 --- a/test/RichOutput.jl +++ b/test/RichOutput.jl @@ -7,8 +7,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient @testset "Tree viewer" begin @testset "Basics" begin @@ -44,7 +42,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb Cell("[ rand(50,50) ]"), Cell("[ rand(500,500) ]"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -122,7 +119,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb C(3) end"""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -173,7 +169,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb Cell("OneTwoThree()"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -224,7 +219,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb x[] = (1,x) end"""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -263,7 +257,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb ]"""), Cell("Union{}[]"), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) @@ -334,7 +327,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb Cell("0 + 10;\n10;"), Cell("0 + 11;\n11"), ]) - fakeclient.connected_notebook = notebook @testset "Strange code" begin update_run!(🍭, notebook, notebook.cells[1]) @@ -403,7 +395,6 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb @testset "$(wrapped ? "With" : "Without") function wrapping" for wrapped in [false, true] notebook = wrapped ? notebook1 : notebook2 - fakeclient.connected_notebook = notebook @test_nowarn update_run!(🍭, notebook, notebook.cells[1:5]) diff --git a/test/WorkspaceManager.jl b/test/WorkspaceManager.jl index 218587e1e6..92975be90a 100644 --- a/test/WorkspaceManager.jl +++ b/test/WorkspaceManager.jl @@ -8,24 +8,15 @@ import Distributed # basic functionality is already tested by the reactivity tests @testset "Multiple notebooks" begin - - fakeclientA = ClientSession(:fakeA, nothing) - fakeclientB = ClientSession(:fakeB, nothing) 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = true - 🍭.connected_clients[fakeclientA.id] = fakeclientA - 🍭.connected_clients[fakeclientB.id] = fakeclientB - notebookA = Notebook([ Cell("x = 3") ]) - fakeclientA.connected_notebook = notebookA - notebookB = Notebook([ Cell("x") ]) - fakeclientB.connected_notebook = notebookB @test notebookA.path != notebookB.path @@ -42,10 +33,8 @@ import Distributed WorkspaceManager.unmake_workspace((🍭, notebookB)) end @testset "Variables with secret names" begin - fakeclient = ClientSession(:fake, nothing) 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - 🍭.connected_clients[fakeclient.id] = fakeclient notebook = Notebook([ Cell("result = 1"), @@ -53,7 +42,6 @@ import Distributed Cell("elapsed_ns = 3"), Cell("elapsed_ns"), ]) - fakeclient.connected_notebook = notebook update_save_run!(🍭, notebook, notebook.cells[1:4]) @test notebook.cells[1].output.body == "1" @@ -65,11 +53,8 @@ import Distributed end Sys.iswindows() || @testset "Pluto inside Pluto" begin - - client = ClientSession(:fakeA, nothing) 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = true - 🍭.connected_clients[client.id] = client notebook = Notebook([ Cell("""begin @@ -88,7 +73,6 @@ import Distributed Cell("length(nb.cells)"), Cell(""), ]) - client.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) diff --git a/test/cell_disabling.jl b/test/cell_disabling.jl index 19bc64aa83..5e1dcd0308 100644 --- a/test/cell_disabling.jl +++ b/test/cell_disabling.jl @@ -6,9 +6,6 @@ using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false - fakeclient = ClientSession(:fake, nothing) - 🍭.connected_clients[fakeclient.id] = fakeclient - notebook = Notebook([ Cell("""y = begin 1 + x @@ -19,7 +16,6 @@ using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook Cell("w = z^5"), Cell(""), ]) - fakeclient.connected_notebook = notebook update_run!(🍭, notebook, notebook.cells) # helper functions From 95ece0da3e8c37acc1327bd2404548287627164c Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 7 Oct 2022 13:22:38 +0200 Subject: [PATCH 449/821] Allow solving multiple definitions by disabling one, attempt 2 (#2303) --- src/analysis/Topology.jl | 11 ++ src/analysis/topological_order.jl | 48 +++++++- src/evaluation/Run.jl | 42 ++++--- test/cell_disabling.jl | 186 +++++++++++++++++++++++++++++- 4 files changed, 264 insertions(+), 23 deletions(-) diff --git a/src/analysis/Topology.jl b/src/analysis/Topology.jl index d1c22477bc..b1f2f32237 100644 --- a/src/analysis/Topology.jl +++ b/src/analysis/Topology.jl @@ -53,3 +53,14 @@ function set_unresolved(topology::NotebookTopology, unresolved_cells::Vector{Cel disabled_cells=topology.disabled_cells, ) end + + +function Base.setdiff(topology::NotebookTopology, cells::Vector{Cell}) + NotebookTopology( + nodes=setdiffkeys(topology.nodes, cells), + codes=setdiffkeys(topology.codes, cells), + unresolved_cells=ImmutableSet{Cell}(setdiff(topology.unresolved_cells.c, cells); skip_copy=true), + cell_order=ImmutableVector{Cell}(setdiff(topology.cell_order.c, cells); skip_copy=true), + disabled_cells=ImmutableSet{Cell}(setdiff(topology.disabled_cells.c, cells); skip_copy=true), + ) +end diff --git a/src/analysis/topological_order.jl b/src/analysis/topological_order.jl index a2b0bb6146..4853ef4445 100644 --- a/src/analysis/topological_order.jl +++ b/src/analysis/topological_order.jl @@ -7,8 +7,33 @@ end @deprecate topological_order(::Notebook, topology::NotebookTopology, args...; kwargs...) topological_order(topology, args...; kwargs...) -"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(topology::NotebookTopology, roots::AbstractVector{Cell}; allow_multiple_defs=false)::TopologicalOrder +""" +Return a `TopologicalOrder` that lists the cells to be evaluated in a single reactive run, in topological order. Includes the given roots. + +# Keyword arguments + +- `allow_multiple_defs::Bool = false` + + If `false` (default), multiple definitions are not allowed. When a cell is found that defines a variable that is also defined by another cell (this other cell is called a *fellow assigner*), then both cells are marked as `errable` and not `runnable`. + + If `true`, then multiple definitions are allowed, in the sense that we ignore the existance of other cells that define the same variable. + + +- `skip_at_partial_multiple_defs::Bool = false` + + If `true` (not default), and `allow_multiple_defs = true` (not default), then the search stops going downward when finding a cell that has fellow assigners, *unless all fellow assigners can be reached by the `roots`*, in which case we continue searching downward. + + In other words, if there is a set of fellow assigners that can only be reached **partially** by the roots, then this set blocks the search, and cells that depend on the set are not found. +""" +function topological_order(topology::NotebookTopology, roots::AbstractVector{Cell}; + allow_multiple_defs::Bool=false, + skip_at_partial_multiple_defs::Bool=false, +)::TopologicalOrder + + if skip_at_partial_multiple_defs + @assert allow_multiple_defs + end + entries = Cell[] exits = Cell[] errable = Dict{Cell,ReactivityError}() @@ -42,13 +67,28 @@ function topological_order(topology::NotebookTopology, roots::AbstractVector{Cel push!(entries, cell) assigners = where_assigned(topology, cell) + referencers = where_referenced(topology, cell) |> Iterators.reverse + if !allow_multiple_defs && length(assigners) > 1 for c in assigners errable[c] = MultipleDefinitionsError(topology, c, assigners) end end - referencers = where_referenced(topology, cell) |> Iterators.reverse - for c in (allow_multiple_defs ? referencers : union(assigners, referencers)) + + should_continue_search_down = !skip_at_partial_multiple_defs || all(c -> c === cell || c ∈ exits, assigners) + should_search_fellow_assigners_if_any = !allow_multiple_defs + + to_search_next = if should_continue_search_down + if should_search_fellow_assigners_if_any + union(assigners, referencers) + else + referencers + end + else + Cell[] + end + + for c in to_search_next if c !== cell child_result = bfs(c) diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl index 42b68a4af3..ff106ef5e7 100644 --- a/src/evaluation/Run.jl +++ b/src/evaluation/Run.jl @@ -63,6 +63,9 @@ function run_reactive_core!( # update cache and save notebook because the dependencies might have changed after expanding macros update_dependency_cache!(notebook) end + + # find (indirectly) skipped-as-script cells and update their status + update_skipped_cells_dependency!(notebook, new_topology) removed_cells = setdiff(all_cells(old_topology), all_cells(new_topology)) roots = vcat(roots, removed_cells) @@ -81,42 +84,45 @@ function run_reactive_core!( cell_order = new_topology.cell_order, disabled_cells=new_topology.disabled_cells, ) + + + # find (indirectly) deactivated cells and update their status + indirectly_deactivated = collect(topological_order(new_topology, collect(new_topology.disabled_cells); allow_multiple_defs=true, skip_at_partial_multiple_defs=true)) + + for cell in indirectly_deactivated + cell.running = false + cell.queued = false + cell.depends_on_disabled_cells = true + end + + new_topology = setdiff(new_topology, indirectly_deactivated) # save the old topological order - we'll delete variables assigned from its # and re-evalutate its cells unless the cells have already run previously in the reactive run old_order = topological_order(old_topology, roots) - old_runnable = setdiff(old_order.runnable, already_run) + old_runnable = setdiff(old_order.runnable, already_run, indirectly_deactivated) to_delete_vars = union!(Set{Symbol}(), defined_variables(old_topology, old_runnable)...) to_delete_funcs = union!(Set{Tuple{UUID,FunctionName}}(), defined_functions(old_topology, old_runnable)...) - + + + new_roots = setdiff(union(roots, keys(old_order.errable)), indirectly_deactivated) # get the new topological order - new_order = topological_order(new_topology, union(roots, keys(old_order.errable))) + new_order = topological_order(new_topology, new_roots) new_runnable = setdiff(new_order.runnable, already_run) - to_run_raw = setdiff(union(new_runnable, old_runnable), keys(new_order.errable))::Vector{Cell} # TODO: think if old error cell order matters + to_run = setdiff(union(new_runnable, old_runnable), keys(new_order.errable))::Vector{Cell} # TODO: think if old error cell order matters - # find (indirectly) deactivated cells and update their status - indirectly_deactivated = collect(topological_order(new_topology, collect(new_topology.disabled_cells))) - for cell in indirectly_deactivated - cell.running = false - cell.queued = false - cell.depends_on_disabled_cells = true - end - - # find (indirectly) skipped cells and update their status - update_skipped_cells_dependency!(notebook, new_topology) - - to_run = setdiff(to_run_raw, indirectly_deactivated) # change the bar on the sides of cells to "queued" for cell in to_run cell.queued = true cell.depends_on_disabled_cells = false end - + for (cell, error) in new_order.errable cell.running = false cell.queued = false + cell.depends_on_disabled_cells = false relay_reactivity_error!(cell, error) end @@ -504,7 +510,7 @@ function update_skipped_cells_dependency!(notebook::Notebook, topology::Notebook for cell in indirectly_skipped cell.depends_on_skipped_cells = true end - for cell in setdiff(notebook.cells, indirectly_skipped) + for cell in setdiff(notebook.cells, indirectly_skipped) cell.depends_on_skipped_cells = false end end diff --git a/test/cell_disabling.jl b/test/cell_disabling.jl index 5e1dcd0308..66cd79b760 100644 --- a/test/cell_disabling.jl +++ b/test/cell_disabling.jl @@ -1,11 +1,195 @@ using Test using Pluto -using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook +using Pluto: update_run!, ServerSession, ClientSession, Cell, Notebook, set_disabled, is_disabled + + + + @testset "Cell Disabling" begin 🍭 = ServerSession() 🍭.options.evaluation.workspace_use_distributed = false + notebook = Notebook([ + Cell("const a = 1") + Cell("const b = 2") + Cell("const c = 3") + Cell("const d = 4") + + Cell("const x = a") # 5 + # these cells will be uncommented later + Cell("# const x = b") # 6 + Cell("# const x = c") # 7 + + Cell("const z = x") # 8 + Cell("# const z = d") # 9 + + Cell("const y = z") # 10 + + Cell("things = []") # 11 + Cell("""begin + cool = 1 + push!(things, 1) + end""") # 12 + Cell("""begin + # cool = 2 + # push!(things, 2) + end""") # 13 + Cell("cool; length(things)") # 14 + ]) + update_run!(🍭, notebook, notebook.cells) + + # helper functions + id(i) = notebook.cells[i].cell_id + c(i) = notebook.cells[i] + get_indirectly_disabled_cells(notebook) = [i for (i, c) in pairs(notebook.cells) if c.depends_on_disabled_cells] + + + + @test !any(is_disabled, notebook.cells) + @test get_indirectly_disabled_cells(notebook) == [] + @test all(noerror, notebook.cells) + + ### + setcode!(c(6), "const x = b") + update_run!(🍭, notebook, c(6)) + + @test c(5).errored + @test c(6).errored + @test c(8).errored + @test c(10).errored + @test get_indirectly_disabled_cells(notebook) == [] + + ### + set_disabled(c(1), true) + update_run!(🍭, notebook, c(1)) + + @test noerror(c(1)) + @test noerror(c(6)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [1, 5] + + update_run!(🍭, notebook, c(5:6)) + @test noerror(c(1)) + @test noerror(c(6)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [1, 5] + + ### + set_disabled(c(1), false) + update_run!(🍭, notebook, c(1)) + + @test noerror(c(1)) + @test c(5).errored + @test c(6).errored + @test c(8).errored + @test c(10).errored + @test get_indirectly_disabled_cells(notebook) == [] + + ### + set_disabled(c(5), true) + update_run!(🍭, notebook, c(5)) + + @test noerror(c(1)) + @test noerror(c(6)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [5] + + ### + set_disabled(c(1), true) + update_run!(🍭, notebook, c(1)) + + @test noerror(c(1)) + @test noerror(c(6)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [1, 5] + + + ### + set_disabled(c(5), false) + setcode!(c(7), "const x = c") + update_run!(🍭, notebook, c([5,7])) + + @test c(5).errored + @test c(6).errored + @test c(7).errored + @test c(8).errored + @test c(10).errored + @test get_indirectly_disabled_cells(notebook) == [1, 5] + + ### + set_disabled(c(2), true) + update_run!(🍭, notebook, c(2)) + + @test noerror(c(3)) + @test noerror(c(7)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [1, 2, 5, 6] + + + ### + setcode!(c(9), "const z = d") + update_run!(🍭, notebook, c([9])) + + @test noerror(c(7)) + @test c(8).errored + @test c(9).errored + @test c(10).errored + @test get_indirectly_disabled_cells(notebook) == [1, 2, 5, 6] + + + ### + set_disabled(c(4), true) + update_run!(🍭, notebook, c(4)) + + @test noerror(c(3)) + @test noerror(c(4)) + @test noerror(c(7)) + @test noerror(c(8)) + @test noerror(c(10)) + @test get_indirectly_disabled_cells(notebook) == [1, 2, 4, 5, 6, 9] + + + + ### check that they really don't run when disabled + @test c(14).output.body == "1" + + setcode!(c(13), replace(c(13).code, "#" => "")) + update_run!(🍭, notebook, c([11,13])) + + + @test c(12).errored + @test c(13).errored + @test c(14).errored + + set_disabled(c(13), true) + update_run!(🍭, notebook, c([13])) + + @test noerror(c(12)) + @test noerror(c(14)) + + @test c(14).output.body == "1" + update_run!(🍭, notebook, c([11])) + @test c(14).output.body == "1" + update_run!(🍭, notebook, c([12])) + update_run!(🍭, notebook, c([12])) + @test c(14).output.body == "3" + +end + + + + + +@testset "Cell Disabling 1" begin + 🍭 = ServerSession() + 🍭.options.evaluation.workspace_use_distributed = false + notebook = Notebook([ Cell("""y = begin 1 + x From f4bd3d4e89b391b5d92154b02ccb3195b9240660 Mon Sep 17 00:00:00 2001 From: Dhruv <51574716+Illusion47586@users.noreply.github.com> Date: Fri, 7 Oct 2022 18:58:34 +0530 Subject: [PATCH 450/821] Preparations for native Windows app (#2177) Co-authored-by: Connor Burns Co-authored-by: Fons van der Plas --- frontend/Desktop.d.ts | 55 +++++++++++++++++++++++++++++ frontend/components/Editor.js | 33 ++++++++++++++++- frontend/components/ExportBanner.js | 16 +++++++-- frontend/components/FilePicker.js | 49 +++++++++++++++++++------ frontend/components/welcome/Open.js | 22 ++++++++++-- frontend/editor.css | 38 ++++++++++++++++---- frontend/index.css | 25 ++++++++++++- frontend/welcome.css | 14 ++++++-- src/webserver/Dynamic.jl | 11 +----- src/webserver/SessionActions.jl | 14 +++++++- src/webserver/Static.jl | 49 +++++++++++++++++++++++++ tsconfig.json | 2 +- 12 files changed, 289 insertions(+), 39 deletions(-) create mode 100644 frontend/Desktop.d.ts diff --git a/frontend/Desktop.d.ts b/frontend/Desktop.d.ts new file mode 100644 index 0000000000..44d98d1749 --- /dev/null +++ b/frontend/Desktop.d.ts @@ -0,0 +1,55 @@ +declare global { + /** + * This namespace is meant for [PlutoDesktop](https://github.com/JuliaPluto/PlutoDesktop) + * related types and interfaces. + */ + namespace Desktop { + /** + * This enum has to be in sync with the enum "PlutoExport" + * defined in PlutoDesktop/{branch:master}/types/enums.ts + * + * @note Unfortunately enums can't be exported from .d.ts files. + * Inorder to use this, just map integers to the enum values + * - PlutoExport.FILE -> **0** + * - PlutoExport.HTML -> **1** + * - PlutoExport.STATE -> **2** + * - PlutoExport.PDF -> **3** + */ + enum PlutoExport { + FILE, + HTML, + STATE, + PDF, + } + + /** + * This type has to be in sync with the interface "Window" + * defined in PlutoDesktop/{branch:master}/src/renderer/preload.d.ts + */ + type PlutoDesktop = { + ipcRenderer: { + sendMessage(channel: String, args: unknown[]): void + on(channel: string, func: (...args: unknown[]) => void): (() => void) | undefined + once(channel: string, func: (...args: unknown[]) => void): void + } + fileSystem: { + /** + * @param type [default = 'new'] whether you want to open a new notebook + * open a notebook from a path or from a url + * @param pathOrURL location to the file, not needed if opening a new file, + * opens that notebook. If false and no path is there, opens the file selector. + * If true, opens a new blank notebook. + */ + openNotebook(type?: "url" | "path" | "new", pathOrURL?: string): void + shutdownNotebook(id?: string): void + moveNotebook(id?: string): void + exportNotebook(id: string, type: PlutoExport): void + } + } + } + interface Window { + plutoDesktop?: Desktop.PlutoDesktop + } +} + +export {} diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 2394272b7f..2f757bc2cd 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -862,7 +862,7 @@ patch: ${JSON.stringify( if (!this.state.static_preview && document.visibilityState === "visible") { // view stats on https://stats.plutojl.org/ //@ts-ignore - count_stat(`editing/${window?.version_info?.pluto ?? "unknown"}`) + count_stat(`editing/${window?.version_info?.pluto ?? "unknown"}${window.plutoDesktop ? "-desktop" : ""}`) } }, 1000 * 15 * 60) setInterval(() => { @@ -1046,6 +1046,35 @@ patch: ${JSON.stringify( } } + this.desktop_submit_file_change = async () => { + this.setState({ moving_file: true }) + /** + * `window.plutoDesktop?.ipcRenderer` is basically what allows the + * frontend to communicate with the electron side. It is an IPC + * bridge between render process and main process. More info + * [here](https://www.electronjs.org/docs/latest/api/ipc-renderer). + * + * "PLUTO-MOVE-NOTEBOOK" is an event triggered in the main process + * once the move is complete, we listen to it using `once`. + * More info [here](https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendereroncechannel-listener) + */ + window.plutoDesktop?.ipcRenderer.once("PLUTO-MOVE-NOTEBOOK", async (/** @type {string?} */ loc) => { + if (!!loc) + await this.setStatePromise( + immer((state) => { + state.notebook.in_temp_dir = false + state.notebook.path = loc + }) + ) + this.setState({ moving_file: false }) + // @ts-ignore + document.activeElement?.blur() + }) + + // ask the electron backend to start moving the notebook. The event above will be fired once it is done. + window.plutoDesktop?.fileSystem.moveNotebook() + } + this.delete_selected = (verb) => { if (this.state.selected_cells.length > 0) { this.actions.confirm_delete_multiple(verb, this.state.selected_cells) @@ -1346,6 +1375,7 @@ patch: ${JSON.stringify( <${ProgressBar} notebook=${this.state.notebook} backend_launch_phase=${this.state.backend_launch_phase} status=${status}/>
    <${ExportBanner} + notebook_id=${this.state.notebook.notebook_id} notebookfile_url=${this.export_url("notebookfile")} notebookexport_url=${this.export_url("notebookexport")} open=${export_menu_open} @@ -1382,6 +1412,7 @@ patch: ${JSON.stringify( client=${this.client} value=${notebook.in_temp_dir ? "" : notebook.path} on_submit=${this.submit_file_change} + on_desktop_submit=${this.desktop_submit_file_change} suggest_new_file=${{ base: this.client.session_options == null ? "" : this.client.session_options.server.notebook_path_suggestion, name: notebook.shortpath, diff --git a/frontend/components/ExportBanner.js b/frontend/components/ExportBanner.js index 531306b0e7..458af3e9c7 100644 --- a/frontend/components/ExportBanner.js +++ b/frontend/components/ExportBanner.js @@ -29,17 +29,27 @@ const Square = ({ fill }) => html` //@ts-ignore window.enable_secret_pluto_recording = true -export const ExportBanner = ({ onClose, notebookfile_url, notebookexport_url, start_recording }) => { +export const ExportBanner = ({ notebook_id, onClose, notebookfile_url, notebookexport_url, start_recording }) => { + // @ts-ignore + const isDesktop = !!window.plutoDesktop + + const exportNotebook = (/** @type {{ preventDefault: () => void; }} */ e, /** @type {Desktop.PlutoExport} */ type) => { + if (isDesktop) { + e.preventDefault() + window.plutoDesktop?.fileSystem.exportNotebook(notebook_id, type) + } + } + return html`