`
: null}
`
diff --git a/frontend/editor.css b/frontend/editor.css
index 52bddb302b..d2cbba88e3 100644
--- a/frontend/editor.css
+++ b/frontend/editor.css
@@ -1810,6 +1810,11 @@ pluto-popup > * {
position: absolute;
}
+pluto-popup.warn > * {
+ background: var(--pluto-logs-warn-color);
+ border-color: var(--pluto-logs-warn-accent-color);
+}
+
pluto-popup code.auto_disabled_variable {
font-family: var(--julia-mono-font-stack);
font-size: 0.8rem;
diff --git a/frontend/light_color.css b/frontend/light_color.css
index 6685329f48..0f7e2ce49f 100644
--- a/frontend/light_color.css
+++ b/frontend/light_color.css
@@ -111,7 +111,7 @@
/*saveall container*/
--overlay-button-bg: #ffffff;
- --overlay-button-border: #f3f2f2;
+ --overlay-button-border: hsl(0 4% 91% / 1);
--overlay-button-border-save: #f3f2f2;
/*input_context_menu*/
From 9c052d39d47055444bc03a48abadc9134a27e951 Mon Sep 17 00:00:00 2001
From: Fons van der Plas
Date: Mon, 17 Jul 2023 18:23:47 +0200
Subject: [PATCH 18/52] =?UTF-8?q?=F0=9F=90=9D=20Fix=20#2518?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/components/CellInput.js | 28 +++++++++++++++++-----------
1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js
index e3a57c6421..9133b305a2 100644
--- a/frontend/components/CellInput.js
+++ b/frontend/components/CellInput.js
@@ -631,17 +631,23 @@ export const CellInput = ({
// Remove selection on blur
EditorView.domEventHandlers({
blur: (event, view) => {
- // collapse the selection into a single point
- view.dispatch({
- selection: {
- anchor: view.state.selection.main.head,
- },
- scrollIntoView: false,
- })
- // and blur the DOM again (because the previous transaction might have re-focused it)
- view.contentDOM.blur()
-
- set_cm_forced_focus(null)
+ // it turns out that this condition is true *exactly* if and only if the blur event was triggered by blurring the window
+ let caused_by_window_blur = document.activeElement === view.contentDOM
+
+ if (!caused_by_window_blur) {
+ // then it's caused by focusing something other than this cell in the editor.
+ // in this case, we want to collapse the selection into a single point, for aesthetic reasons.
+ view.dispatch({
+ selection: {
+ anchor: view.state.selection.main.head,
+ },
+ scrollIntoView: false,
+ })
+ // and blur the DOM again (because the previous transaction might have re-focused it)
+ view.contentDOM.blur()
+
+ set_cm_forced_focus(null)
+ }
},
}),
pluto_paste_plugin({
From 48337a589895839869ad8d1ebcf7bf80f4f2cce1 Mon Sep 17 00:00:00 2001
From: Fons van der Plas
Date: Thu, 20 Jul 2023 21:03:49 +0200
Subject: [PATCH 19/52] frontmatter editor: remove autocapitalizationr
---
frontend/editor.css | 1 -
1 file changed, 1 deletion(-)
diff --git a/frontend/editor.css b/frontend/editor.css
index d2cbba88e3..c573b7c38c 100644
--- a/frontend/editor.css
+++ b/frontend/editor.css
@@ -3415,7 +3415,6 @@ pluto-cell.hooked_up pluto-output {
}
.pluto-frontmatter label {
- text-transform: capitalize;
font-weight: 500;
}
From 5dc359dd7883c585eb89b213e6538eaa25f9ecee Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Jul 2023 21:34:12 +0200
Subject: [PATCH 20/52] Bump word-wrap from 1.2.3 to 1.2.4 in /frontend-bundler
(#2617)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
frontend-bundler/package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/frontend-bundler/package-lock.json b/frontend-bundler/package-lock.json
index ee443a186a..56b24cf5b2 100644
--- a/frontend-bundler/package-lock.json
+++ b/frontend-bundler/package-lock.json
@@ -6874,9 +6874,9 @@
}
},
"node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"engines": {
"node": ">=0.10.0"
}
@@ -11986,9 +11986,9 @@
}
},
"word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA=="
},
"wrap-ansi": {
"version": "7.0.0",
From 56541139902dda3890d5b0130a8ec04c35efbdad Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Jul 2023 21:34:59 +0200
Subject: [PATCH 21/52] Bump terser from 5.9.0 to 5.14.2 in /frontend-bundler
(#2220)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
frontend-bundler/package-lock.json | 142 ++++++++++++++++++++++++-----
1 file changed, 121 insertions(+), 21 deletions(-)
diff --git a/frontend-bundler/package-lock.json b/frontend-bundler/package-lock.json
index 56b24cf5b2..7fdd990813 100644
--- a/frontend-bundler/package-lock.json
+++ b/frontend-bundler/package-lock.json
@@ -385,6 +385,58 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+ "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
+ "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@lezer/common": {
"version": "0.15.12",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz",
@@ -6427,12 +6479,13 @@
}
},
"node_modules/terser": {
- "version": "5.9.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz",
- "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==",
+ "version": "5.14.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
+ "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"dependencies": {
+ "@jridgewell/source-map": "^0.3.2",
+ "acorn": "^8.5.0",
"commander": "^2.20.0",
- "source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -6442,19 +6495,22 @@
"node": ">=10"
}
},
+ "node_modules/terser/node_modules/acorn": {
+ "version": "8.7.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
+ "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
- "node_modules/terser/node_modules/source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/timers-browserify": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
@@ -7304,6 +7360,49 @@
"to-fast-properties": "^2.0.0"
}
},
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "requires": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+ },
+ "@jridgewell/source-map": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+ "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
+ "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"@lezer/common": {
"version": "0.15.12",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz",
@@ -11618,24 +11717,25 @@
"integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="
},
"terser": {
- "version": "5.9.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz",
- "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==",
+ "version": "5.14.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
+ "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
"requires": {
+ "@jridgewell/source-map": "^0.3.2",
+ "acorn": "^8.5.0",
"commander": "^2.20.0",
- "source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
+ "acorn": {
+ "version": "8.7.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
+ "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A=="
+ },
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
- },
- "source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
}
}
},
From ff88e45cb07e9b7ef1b5182040b0a7f7f8b42bea Mon Sep 17 00:00:00 2001
From: Paul Berg
Date: Thu, 20 Jul 2023 12:36:01 -0700
Subject: [PATCH 22/52] Update the folded state when reloading from file
(#2602)
Co-authored-by: Tor Erlend Fjelde <11074788+torfjelde@users.noreply.github.com>
Co-authored-by: Fons van der Plas
---
src/evaluation/Run.jl | 18 ++++++++++--------
test/ReloadFromFile.jl | 13 ++++++++-----
2 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/src/evaluation/Run.jl b/src/evaluation/Run.jl
index 035796a8e0..b9eb7679f9 100644
--- a/src/evaluation/Run.jl
+++ b/src/evaluation/Run.jl
@@ -524,6 +524,7 @@ function notebook_differences(from::Notebook, to::Notebook)
end
end,
+ folded_changed = any(from_cells[id].code_folded != to_cells[id].code_folded for id in keys(from_cells) if haskey(to_cells, id)),
order_changed = from.cell_order != to.cell_order,
nbpkg_changed = !is_nbpkg_equal(from.nbpkg_ctx, to.nbpkg_ctx),
)
@@ -555,16 +556,17 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...)
# @show added removed changed
cells_changed = !(isempty(added) && isempty(removed) && isempty(changed))
+ folded_changed = d.folded_changed
order_changed = d.order_changed
nbpkg_changed = d.nbpkg_changed
- something_changed = cells_changed || order_changed || (include_nbpg && nbpkg_changed)
-
+ something_changed = cells_changed || folded_changed || order_changed || (include_nbpg && nbpkg_changed)
+
if something_changed
@info "Reloading notebook from file and applying changes!"
notebook.last_hot_reload_time = time()
end
-
+
for c in added
notebook.cells_dict[c] = just_loaded.cells_dict[c]
end
@@ -579,10 +581,10 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...)
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)
@@ -599,11 +601,11 @@ function update_from_file(session::ServerSession, notebook::Notebook; kwargs...)
end
notebook.nbpkg_restart_required_msg = "Yes, because the file was changed externally and the embedded Pkg changed."
end
-
+
if something_changed
- update_save_run!(session, notebook, Cell[notebook.cells_dict[c] for c in union(added, changed)]; kwargs...) # this will also update nbpkg
+ update_save_run!(session, notebook, Cell[notebook.cells_dict[c] for c in union(added, changed)]; kwargs...) # this will also update nbpkg if needed
end
-
+
return true
end
diff --git a/test/ReloadFromFile.jl b/test/ReloadFromFile.jl
index 01bf9358ca..7cc6b9aac4 100644
--- a/test/ReloadFromFile.jl
+++ b/test/ReloadFromFile.jl
@@ -101,14 +101,14 @@ end
sleep(timeout_between_tests)
file4 = read(notebook.path, String)
+ last_hot_reload_time4 = notebook.last_hot_reload_time
notebook.cells[3].code_folded = true
save_notebook(notebook)
sleep(timeout_between_tests)
file5 = read(notebook.path, String)
- @assert file4 != file5
-
- @assert notebook.cells[3].code_folded
+ @test file4 != file5
+ @test notebook.cells[3].code_folded
write(notebook.path, file4)
@@ -119,7 +119,10 @@ end
# cell folded, but cell should not re-run
@assert original_rand_output == notebook.cells[3].output.body
-
+ @assert poll(10) do
+ last_hot_reload_time5 = notebook.last_hot_reload_time
+ last_hot_reload_time5 != last_hot_reload_time4
+ end
###
sleep(timeout_between_tests)
@@ -160,4 +163,4 @@ end
@assert notebook.nbpkg_restart_required_msg !== nothing
end
@test true
-end
\ No newline at end of file
+end
From 532587dde06a3953a1090e1d6490524111c7f422 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Jul 2023 21:37:52 +0200
Subject: [PATCH 23/52] Bump word-wrap from 1.2.3 to 1.2.4 in /test/frontend
(#2616)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
test/frontend/package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/frontend/package-lock.json b/test/frontend/package-lock.json
index df0fc81029..27f3b646a0 100644
--- a/test/frontend/package-lock.json
+++ b/test/frontend/package-lock.json
@@ -9077,9 +9077,9 @@
"dev": true
},
"node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -16124,9 +16124,9 @@
"dev": true
},
"word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wrap-ansi": {
From c8f80f47ec751d37fc5a3a4de79ff7d58505d478 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Jul 2023 21:38:11 +0200
Subject: [PATCH 24/52] Bump semver from 5.7.1 to 5.7.2 in /frontend-bundler
(#2599)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
frontend-bundler/package-lock.json | 36 +++++++++++++++---------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/frontend-bundler/package-lock.json b/frontend-bundler/package-lock.json
index 7fdd990813..a9b30f6cff 100644
--- a/frontend-bundler/package-lock.json
+++ b/frontend-bundler/package-lock.json
@@ -76,9 +76,9 @@
}
},
"node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -114,9 +114,9 @@
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -6148,9 +6148,9 @@
}
},
"node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
@@ -7135,9 +7135,9 @@
},
"dependencies": {
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -7163,9 +7163,9 @@
},
"dependencies": {
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
}
}
},
@@ -11463,9 +11463,9 @@
}
},
"semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
},
"serve-handler": {
"version": "6.1.5",
From c5dbeb14a655c66f6b8a650684f7bc4c6cfc2387 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 20 Jul 2023 21:38:43 +0200
Subject: [PATCH 25/52] Bump tough-cookie from 4.0.0 to 4.1.3 in /test/frontend
(#2597)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
test/frontend/package-lock.json | 74 ++++++++++++++++++++++++++-------
1 file changed, 60 insertions(+), 14 deletions(-)
diff --git a/test/frontend/package-lock.json b/test/frontend/package-lock.json
index 27f3b646a0..9f056296f2 100644
--- a/test/frontend/package-lock.json
+++ b/test/frontend/package-lock.json
@@ -7320,6 +7320,12 @@
}
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -7519,6 +7525,12 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -8724,14 +8736,15 @@
}
},
"node_modules/tough-cookie": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
- "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dev": true,
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
- "universalify": "^0.1.2"
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
@@ -8866,9 +8879,9 @@
}
},
"node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
@@ -8929,6 +8942,16 @@
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
"dev": true
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -14716,6 +14739,12 @@
}
}
},
+ "querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -14879,6 +14908,12 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -15842,14 +15877,15 @@
}
},
"tough-cookie": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
- "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dev": true,
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
- "universalify": "^0.1.2"
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
}
},
"tr46": {
@@ -15950,9 +15986,9 @@
}
},
"universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"dev": true
},
"unset-value": {
@@ -16001,6 +16037,16 @@
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
"dev": true
},
+ "url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
From 1d2c3038032108fff3a9c565eaa84609f1e75b4e Mon Sep 17 00:00:00 2001
From: Alberto Mengali
Date: Thu, 20 Jul 2023 22:58:48 +0200
Subject: [PATCH 26/52] Make `published_to_js` (now `publish_to_js`) official
API through AbstractPlutoDingetjes.jl (v2) (#2608)
Co-authored-by: Fons van der Plas
---
src/runner/PlutoRunner.jl | 109 ++++++++++-----------
test/Dynamic.jl | 72 +++++++++++---
test/frontend/__tests__/published_to_js.js | 58 +++++++++++
test/frontend/fixtures/published_to_js.jl | 50 ++++++++++
4 files changed, 216 insertions(+), 73 deletions(-)
create mode 100644 test/frontend/__tests__/published_to_js.js
create mode 100644 test/frontend/fixtures/published_to_js.jl
diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl
index 8ff95058ac..6916596b7a 100644
--- a/src/runner/PlutoRunner.jl
+++ b/src/runner/PlutoRunner.jl
@@ -908,7 +908,13 @@ function formatted_result_of(
output_formatted = if (!ends_with_semicolon || errored)
logger = get!(() -> PlutoCellLogger(notebook_id, cell_id), pluto_cell_loggers, cell_id)
with_logger_and_io_to_logs(logger; capture_stdout, stdio_loglevel=stdout_log_level) do
- format_output(ans; context=IOContext(default_iocontext, :extra_items=>extra_items, :module => workspace))
+ format_output(ans; context=IOContext(
+ default_iocontext,
+ :extra_items=>extra_items,
+ :module => workspace,
+ :pluto_notebook_id => notebook_id,
+ :pluto_cell_id => cell_id,
+ ))
end
else
("", MIME"text/plain"())
@@ -972,6 +978,7 @@ const default_iocontext = IOContext(devnull,
:displaysize => (18, 88),
:is_pluto => true,
:pluto_supported_integration_features => supported_integration_features,
+ :pluto_published_to_js => (io, x) -> core_published_to_js(io, x),
)
const default_stdout_iocontext = IOContext(devnull,
@@ -1494,17 +1501,29 @@ const integrations = Integration[
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
- transform_value_ref[] = AbstractPlutoDingetjes.Bonds.transform_value
- possible_bond_values_ref[] = AbstractPlutoDingetjes.Bonds.possible_values
-
- push!(supported_integration_features,
+
+ supported!(xs...) = push!(supported_integration_features, xs...)
+
+ # don't need feature checks for these because they existed in every version of AbstractPlutoDingetjes:
+ supported!(
AbstractPlutoDingetjes,
AbstractPlutoDingetjes.Bonds,
AbstractPlutoDingetjes.Bonds.initial_value,
AbstractPlutoDingetjes.Bonds.transform_value,
AbstractPlutoDingetjes.Bonds.possible_values,
)
+ initial_value_getter_ref[] = AbstractPlutoDingetjes.Bonds.initial_value
+ transform_value_ref[] = AbstractPlutoDingetjes.Bonds.transform_value
+ possible_bond_values_ref[] = AbstractPlutoDingetjes.Bonds.possible_values
+
+ # feature checks because these were added in a later release of AbstractPlutoDingetjes
+ if isdefined(AbstractPlutoDingetjes, :Display)
+ supported!(AbstractPlutoDingetjes.Display)
+ if isdefined(AbstractPlutoDingetjes.Display, :published_to_js)
+ supported!(AbstractPlutoDingetjes.Display.published_to_js)
+ end
+ end
+
end,
),
Integration(
@@ -2125,74 +2144,48 @@ end"""
"""
const currently_running_cell_id = Ref{UUID}(uuid4())
-function _publish(x, id_start, cell_id)::String
- id = "$(notebook_id[])/$cell_id/$id_start"
- d = get!(Dict{String,Any}, cell_published_objects, cell_id)
+function core_published_to_js(io, x)
+ assertpackable(x)
+
+ id_start = objectid2str(x)
+
+ _notebook_id = get(io, :pluto_notebook_id, notebook_id[])::UUID
+ _cell_id = get(io, :pluto_cell_id, currently_running_cell_id[])::UUID
+
+ # The unique identifier of this object
+ id = "$_notebook_id/$id_start"
+
+ d = get!(Dict{String,Any}, cell_published_objects, _cell_id)
d[id] = x
- return id
+
+ write(io, "/* See the documentation for AbstractPlutoDingetjes.Display.published_to_js */ getPublishedObject(\"$(id)\")")
+
+ return nothing
end
-# TODO? Possibly move this to it's own package, with fallback that actually msgpack?
-# ..... Ideally we'd make this require `await` on the javascript side too...
-Base.@kwdef struct PublishedToJavascript
+# TODO: This is the deprecated old function. Remove me at some point.
+struct PublishedToJavascript
published_object
- published_id_start
- cell_id
end
function Base.show(io::IO, ::MIME"text/javascript", published::PublishedToJavascript)
- id = _publish(published.published_object, published.published_id_start, published.cell_id)
- # if published.cell_id != currently_running_cell_id[]
- # error("Showing result from PlutoRunner.publish_to_js() in a cell different from where it was created, not (yet?) supported.")
- # end
- write(io, "/* See the documentation for PlutoRunner.publish_to_js */ getPublishedObject(\"$(id)\")")
+ core_published_to_js(io, published.published_object)
end
Base.show(io::IO, ::MIME"text/plain", published::PublishedToJavascript) = show(io, MIME("text/javascript"), published)
Base.show(io::IO, published::PublishedToJavascript) = show(io, MIME("text/javascript"), published)
-"""
- publish_to_js(x)
-
-Make the object `x` available to the JS runtime of this cell. The returned string is a JS command that, when executed in this cell's output, gives the object.
-
-!!! warning
-
- This function is not yet public API, it will become public in the next weeks. Only use for experiments.
-
-# Example
-```julia
-let
- x = Dict(
- "data" => rand(Float64, 20),
- "name" => "juliette",
- )
-
- HTML("\""
-
- "\"")
-end
-```
-"""
-publish_to_js(x) = publish_to_js(x, objectid2str(x))
+# TODO: This is the deprecated old function. Remove me at some point.
+function publish_to_js(x)
+ @warn "Deprecated, use `AbstractPlutoDingetjes.Display.published_to_js(x)` instead of `PlutoRunner.publish_to_js(x)`."
-function publish_to_js(x, id_start)
assertpackable(x)
- PublishedToJavascript(
- published_object=x,
- published_id_start=id_start,
- cell_id=currently_running_cell_id[],
- )
+ PublishedToJavascript(x)
end
const Packable = Union{Nothing,Missing,String,Symbol,Int64,Int32,Int16,Int8,UInt64,UInt32,UInt16,UInt8,Float32,Float64,Bool,MIME,UUID,DateTime}
-assertpackable(::Packable) = true
+assertpackable(::Packable) = nothing
assertpackable(t::Any) = throw(ArgumentError("Only simple objects can be shared with JS, like vectors and dictionaries. $(string(typeof(t))) is not compatible."))
-assertpackable(::Vector{<:Packable}) = true
-assertpackable(::Dict{<:Packable,<:Packable}) = true
+assertpackable(::Vector{<:Packable}) = nothing
+assertpackable(::Dict{<:Packable,<:Packable}) = nothing
assertpackable(x::Vector) = foreach(assertpackable, x)
assertpackable(d::Dict) = let
foreach(assertpackable, keys(d))
diff --git a/test/Dynamic.jl b/test/Dynamic.jl
index f833933ff6..669017f640 100644
--- a/test/Dynamic.jl
+++ b/test/Dynamic.jl
@@ -199,20 +199,45 @@ end
notebook = Notebook([
Cell("PlutoRunner.notebook_id[] |> Text"),
+ # These cells tests `core_published_to_js`, which is the function used by the official API (`AbtractPlutoDingetjes.Display.published_to_js`).
Cell(cid, """
- let
- # not actually public API but we test it anyways
- a = PlutoRunner._publish(Dict(
+ begin
+
+ a = Dict(
"hello" => "world",
"xx" => UInt8[6,7,8],
- ), "aaa", Base.UUID("$cid"))
- b = PlutoRunner._publish("cool", "bbb", Base.UUID("$cid"))
- Text((a, b))
+ )
+ b = "cool"
+
+ struct ZZZ
+ x
+ y
+ end
+
+ function Base.show(io::IO, ::MIME"text/html", z::ZZZ)
+ write(io, "")
+ end
+
+ ZZZ(a, b)
+ end
+ """),
+ Cell("""
+ begin
+ struct ABC
+ x
+ end
+ ZZZ(
+ 123,
+ Dict("a" => 234, "b" => ABC(4)),
+ )
end
"""),
- Cell("3"),
+ # This is the deprecated API:
Cell("PlutoRunner.publish_to_js(Ref(4))"),
- Cell("PlutoRunner.publish_to_js((ref=4,))"),
+ Cell("PlutoRunner.publish_to_js((ref=5,))"),
Cell("x = Dict(:a => 6)"),
Cell("PlutoRunner.publish_to_js(x)"),
])
@@ -220,11 +245,17 @@ end
update_save_run!(🍭, notebook, notebook.cells)
@test notebook.cells[1].output.body == notebook.notebook_id |> string
- @test !notebook.cells[2].errored
- a, b = Meta.parse(notebook.cells[2].output.body) |> eval
+ @test notebook.cells[2] |> noerror
+ @test notebook.cells[2].output.mime isa MIME"text/html"
+
+ ab1, ab2 = keys(notebook.cells[2].published_objects)
+ @test occursin(ab1, notebook.cells[2].output.body)
+ @test occursin(ab2, notebook.cells[2].output.body)
+
+ ab() = sort(collect(keys(notebook.cells[2].published_objects)); by=(s -> findfirst(s, notebook.cells[2].output.body) |> first))
+ a, b = ab()
+
p = notebook.cells[2].published_objects
- @test sort(collect(keys(p))) == sort([a,b])
- @test isempty(notebook.cells[3].published_objects)
@test p[a] == Dict(
"hello" => "world",
@@ -236,18 +267,29 @@ end
old_pb = p[b]
update_save_run!(🍭, notebook, notebook.cells)
p = notebook.cells[2].published_objects
- a, b = Meta.parse(notebook.cells[2].output.body) |> eval
+ a, b = ab()
@test p[a] == old_pa
@test p[b] == old_pb
@test !isempty(notebook.cells[2].published_objects)
+ # display should have failed
+ @test only(values(notebook.cells[3].published_objects)) == 123
+ msg = notebook.cells[3].output.body[:msg]
+ @test occursin("Failed to show value", msg)
+ @test occursin("ABC is not compatible", msg)
+
+
+
setcode!(notebook.cells[2], "2")
update_save_run!(🍭, notebook, notebook.cells)
@test isempty(notebook.cells[2].published_objects)
-
+ @test isempty(notebook.cells[2].published_objects)
+
+
+
@test notebook.cells[4].errored
- @test !notebook.cells[5].errored
+ @test notebook.cells[5] |> noerror
@test !isempty(notebook.cells[5].published_objects)
diff --git a/test/frontend/__tests__/published_to_js.js b/test/frontend/__tests__/published_to_js.js
new file mode 100644
index 0000000000..348b3cd6bf
--- /dev/null
+++ b/test/frontend/__tests__/published_to_js.js
@@ -0,0 +1,58 @@
+import puppeteer from "puppeteer"
+import { lastElement, saveScreenshot, getTestScreenshotPath, createPage } from "../helpers/common"
+import {
+ getCellIds,
+ importNotebook,
+ waitForCellOutput,
+ getPlutoUrl,
+ prewarmPluto,
+ writeSingleLineInPlutoInput,
+ waitForNoUpdateOngoing,
+ shutdownCurrentNotebook,
+ setupPlutoBrowser,
+} from "../helpers/pluto"
+
+describe("published_to_js", () => {
+ /**
+ * Launch a shared browser instance for all tests.
+ * I don't use jest-puppeteer because it takes away a lot of control and works buggy for me,
+ * so I need to manually create the shared browser.
+ * @type {puppeteer.Browser}
+ */
+ let browser = null
+ /** @type {puppeteer.Page} */
+ let page = null
+ beforeAll(async () => {
+ browser = await setupPlutoBrowser()
+ })
+ beforeEach(async () => {
+ page = await createPage(browser)
+ await page.goto(getPlutoUrl(), { waitUntil: "networkidle0" })
+ })
+ afterEach(async () => {
+ await saveScreenshot(page)
+ await shutdownCurrentNotebook(page)
+ await page.close()
+ page = null
+ })
+ afterAll(async () => {
+ await browser.close()
+ browser = null
+ })
+
+ it("Should correctly show published_to_js in cell output, and in logs", async () => {
+ await importNotebook(page, "published_to_js.jl")
+ await waitForNoUpdateOngoing(page, { polling: 100 })
+ let output_of_published = await page.evaluate(() => {
+ return document.querySelector("#to_cell_output")?.textContent
+ })
+ expect(output_of_published).toBe("[1,2,3] MAGIC!")
+
+ // The log content is not shown, so #to_cell_log does not exist
+ let log_of_published = await page.evaluate(() => {
+ return document.querySelector("#to_cell_log")?.textContent
+ })
+ // This test is currently broken, due to https://github.com/fonsp/Pluto.jl/issues/2092
+ // expect(log_of_published).toBe("[4,5,6] MAGIC!")
+ })
+})
diff --git a/test/frontend/fixtures/published_to_js.jl b/test/frontend/fixtures/published_to_js.jl
new file mode 100644
index 0000000000..1e8356e57c
--- /dev/null
+++ b/test/frontend/fixtures/published_to_js.jl
@@ -0,0 +1,50 @@
+### A Pluto.jl notebook ###
+# v0.19.27
+
+using Markdown
+using InteractiveUtils
+
+# ╔═╡ 2d69377e-23f8-11ee-116b-fb6a8f328528
+begin
+ using Pkg
+ Pkg.activate(temp=true)
+ # the latest versions of these packages:
+ Pkg.add(url="https://github.com/JuliaPluto/AbstractPlutoDingetjes.jl", rev="main")
+ Pkg.add("HypertextLiteral")
+end
+
+# ╔═╡ 2ea26a4b-2d1e-4bcb-8b7b-cace79f7926a
+begin
+ using AbstractPlutoDingetjes.Display: published_to_js
+ using HypertextLiteral
+end
+
+# ╔═╡ 043829fc-af3a-40b9-bb4f-f848ab50eb25
+a = [1,2,3];
+
+# ╔═╡ 2f4609fd-7361-4048-985a-2cc74bb25606
+@htl """
+
+"""
+
+# ╔═╡ 28eba9fd-0416-49b8-966e-03a381c19ca7
+b = [4,5,6];
+
+# ╔═╡ 0a4e8a19-6d43-4161-bb8c-1ebf8f8f68ba
+@info @htl """
+
+"""
+
+# ╔═╡ Cell order:
+# ╠═2d69377e-23f8-11ee-116b-fb6a8f328528
+# ╠═2ea26a4b-2d1e-4bcb-8b7b-cace79f7926a
+# ╠═043829fc-af3a-40b9-bb4f-f848ab50eb25
+# ╠═2f4609fd-7361-4048-985a-2cc74bb25606
+# ╠═28eba9fd-0416-49b8-966e-03a381c19ca7
+# ╠═0a4e8a19-6d43-4161-bb8c-1ebf8f8f68ba
From f297b127214ea7790b344b5c7687a21044c6323f Mon Sep 17 00:00:00 2001
From: Paul Berg
Date: Sun, 13 Aug 2023 11:52:29 +0200
Subject: [PATCH 27/52] Test Julia 1.10 (#2626)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Test Julia 1.10
* Disable Forward compat test 🤔
* Bump Symbolics to v5.5.1 in MacroAnalysis test
---
.github/workflows/Test.yml | 2 +-
test/MacroAnalysis.jl | 6 +--
test/packages/Basic.jl | 76 +++++++++++++++++++-------------------
3 files changed, 43 insertions(+), 41 deletions(-)
diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml
index 37e73f3784..ecf12bbf1f 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", "1.9"] #, "nightly"]
+ julia-version: ["1.6", "1.7", "1.8", "1.9", "nightly"]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
diff --git a/test/MacroAnalysis.jl b/test/MacroAnalysis.jl
index 171f9e8ce0..4c54571674 100644
--- a/test/MacroAnalysis.jl
+++ b/test/MacroAnalysis.jl
@@ -624,7 +624,7 @@ import Memoize: @memoize
@testset "Package macro 2" begin
🍭.options.evaluation.workspace_use_distributed = true
-
+
notebook = Notebook([
Cell("z = x^2 + y"),
Cell("@variables x y"),
@@ -632,7 +632,7 @@ import Memoize: @memoize
begin
import Pkg
Pkg.activate(mktempdir())
- Pkg.add(Pkg.PackageSpec(name="Symbolics", version="1"))
+ Pkg.add(Pkg.PackageSpec(name="Symbolics", version="5.5.1"))
import Symbolics: @variables
end
"""),
@@ -648,7 +648,7 @@ import Memoize: @memoize
@test cell(1) |> noerror
@test cell(2) |> noerror
- @test cell(2) |> noerror
+ @test cell(3) |> noerror
update_run!(🍭, notebook, notebook.cells)
diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl
index 6a8af7e9f5..773be1ec71 100644
--- a/test/packages/Basic.jl
+++ b/test/packages/Basic.jl
@@ -446,50 +446,52 @@ import Distributed
WorkspaceManager.unmake_workspace((🍭, notebook))
end
- @testset "File format -- Forwards compat" begin
- # Using Distributed, we will create a new Julia process in which we install Pluto 0.14.7 (before PlutoPkg). We run the new notebook file on the old Pluto.
- p = Distributed.addprocs(1) |> first
-
- @test post_pkg_notebook isa String
-
- Distributed.remotecall_eval(Main, p, quote
- path = tempname()
- write(path, $(post_pkg_notebook))
- import Pkg
- # optimization:
- if isdefined(Pkg, :UPDATED_REGISTRY_THIS_SESSION)
- Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true
- end
+ @static if VERSION ≤ v"1.9"
+ @testset "File format -- Forwards compat" begin
+ # Using Distributed, we will create a new Julia process in which we install Pluto 0.14.7 (before PlutoPkg). We run the new notebook file on the old Pluto.
+ p = Distributed.addprocs(1) |> first
+
+ @test post_pkg_notebook isa String
+
+ Distributed.remotecall_eval(Main, p, quote
+ path = tempname()
+ write(path, $(post_pkg_notebook))
+ import Pkg
+ # optimization:
+ if isdefined(Pkg, :UPDATED_REGISTRY_THIS_SESSION)
+ Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true
+ end
- Pkg.activate(mktempdir())
- Pkg.add(Pkg.PackageSpec(;name="Pluto",version=v"0.14.7"))
- import Pluto
- @assert Pluto.PLUTO_VERSION == v"0.14.7"
+ Pkg.activate(mktempdir())
+ Pkg.add(Pkg.PackageSpec(;name="Pluto",version=v"0.14.7"))
+ import Pluto
+ @assert Pluto.PLUTO_VERSION == v"0.14.7"
- s = Pluto.ServerSession()
- s.options.evaluation.workspace_use_distributed = false
+ s = Pluto.ServerSession()
+ s.options.evaluation.workspace_use_distributed = false
- nb = Pluto.SessionActions.open(s, path; run_async=false)
+ nb = Pluto.SessionActions.open(s, path; run_async=false)
- nothing
- end)
+ nothing
+ end)
- # Cells that use Example will error because the package is not installed.
+ # Cells that use Example will error because the package is not installed.
- # @test Distributed.remotecall_eval(Main, p, quote
- # nb.cells[1].errored == false
- # end)
- @test Distributed.remotecall_eval(Main, p, quote
- nb.cells[2].errored == false
- end)
- # @test Distributed.remotecall_eval(Main, p, quote
- # nb.cells[3].errored == false
- # end)
- # @test Distributed.remotecall_eval(Main, p, quote
- # nb.cells[3].output.body == "25"
- # end)
+ # @test Distributed.remotecall_eval(Main, p, quote
+ # nb.cells[1].errored == false
+ # end)
+ @test Distributed.remotecall_eval(Main, p, quote
+ nb.cells[2].errored == false
+ end)
+ # @test Distributed.remotecall_eval(Main, p, quote
+ # nb.cells[3].errored == false
+ # end)
+ # @test Distributed.remotecall_eval(Main, p, quote
+ # nb.cells[3].output.body == "25"
+ # end)
- Distributed.rmprocs([p])
+ Distributed.rmprocs([p])
+ end
end
@testset "PkgUtils -- reset" begin
From bfd7830925bd8932d1b83eee94218e8b66aed8fc Mon Sep 17 00:00:00 2001
From: Paul Berg
Date: Thu, 24 Aug 2023 20:01:30 +0200
Subject: [PATCH 28/52] integrate JuliaSyntax.jl (#2526)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* integrate JuliaSyntax.jl
* Delete test_bed.jl
* improve display
* update Parse.jl
* Move code to PlutoRunner
* Add tests
* No stacktrace for syntax error in <=1.9
* Update TypeScriptCheck.yml
* Update CodemirrorPlutoSetup.d.ts
* Update React.jl
* Update is_just_text.jl
* Update TypeScriptCheck.yml
* Fixes for the codemirror update
* Update TypeScriptCheck.yml
* Disable Forward compat test 🤔
* Set `z-index` for pluto-runarea to 19 (pluto-input.cm-editor.z-index - 1)
* Change Docs test to adapt to nightly
* tighten is_just_text
---
frontend/components/Cell.js | 101 ++--
frontend/components/CellInput.js | 23 +-
.../components/CellInput/highlight_line.js | 58 ++
.../CellInput/pluto_autocomplete.js | 6 +-
frontend/components/CellOutput.js | 5 +-
frontend/components/ErrorMessage.js | 59 +-
frontend/editor.css | 14 +-
frontend/imports/CodemirrorPlutoSetup.d.ts | 557 +++++++++++++++++-
frontend/imports/CodemirrorPlutoSetup.js | 6 +-
src/analysis/ExpressionExplorer.jl | 1 +
src/analysis/Parse.jl | 11 +-
src/analysis/is_just_text.jl | 18 +-
src/runner/PlutoRunner.jl | 172 +++++-
test/Dynamic.jl | 2 +-
test/ExpressionExplorer.jl | 14 +
test/React.jl | 24 +
test/packages/Basic.jl | 20 +-
17 files changed, 1011 insertions(+), 80 deletions(-)
diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js
index 247740152a..b43b843ae2 100644
--- a/frontend/components/Cell.js
+++ b/frontend/components/Cell.js
@@ -135,7 +135,31 @@ export const Cell = ({
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_range, set_cm_highlighted_range] = useState(null)
const [cm_highlighted_line, set_cm_highlighted_line] = useState(null)
+ const [cm_diagnostics, set_cm_diagnostics] = useState([])
+
+ useEffect(() => {
+ const diagnosticListener = (e) => {
+ if (e.detail.cell_id === cell_id) {
+ set_cm_diagnostics(e.detail.diagnostics)
+ }
+ }
+ window.addEventListener("cell_diagnostics", diagnosticListener)
+ return () => window.removeEventListener("cell_diagnostics", diagnosticListener)
+ }, [cell_id])
+
+ useEffect(() => {
+ const highlightRangeListener = (e) => {
+ if (e.detail.cell_id == cell_id && e.detail.from != null && e.detail.to != null) {
+ set_cm_highlighted_range({ from: e.detail.from, to: e.detail.to })
+ } else {
+ set_cm_highlighted_range(null)
+ }
+ }
+ window.addEventListener("cell_highlight_range", highlightRangeListener)
+ return () => window.removeEventListener("cell_highlight_range", highlightRangeListener)
+ }, [cell_id])
useEffect(() => {
const focusListener = (e) => {
@@ -248,21 +272,21 @@ export const Cell = ({
key=${cell_key}
ref=${node_ref}
class=${cl({
- queued: queued || (waiting_to_run && is_process_ready),
- running,
- activate_animation,
- errored,
- selected,
- code_differs: class_code_differs,
- code_folded: class_code_folded,
- skip_as_script,
- running_disabled,
- depends_on_disabled_cells,
- depends_on_skipped_cells,
- show_input,
- shrunk: Object.values(logs).length > 0,
- hooked_up: output?.has_pluto_hook_features ?? false,
- })}
+ queued: queued || (waiting_to_run && is_process_ready),
+ running,
+ activate_animation,
+ errored,
+ selected,
+ code_differs: class_code_differs,
+ code_folded: class_code_folded,
+ skip_as_script,
+ running_disabled,
+ depends_on_disabled_cells,
+ depends_on_skipped_cells,
+ show_input,
+ shrunk: Object.values(logs).length > 0,
+ hooked_up: output?.has_pluto_hook_features ?? false,
+ })}
id=${cell_id}
>
${variables.map((name) => html``)}
@@ -274,14 +298,14 @@ export const Cell = ({
- ${cell_api_ready ? html`<${CellOutput} errored=${errored} ...${output} cell_id=${cell_id} />` : html``}
+ ${cell_api_ready ? html`<${CellOutput} errored=${errored} ...${output} cell_id=${cell_id} />` : html``}
<${CellInput}
local_code=${cell_input_local?.code ?? code}
remote_code=${code}
@@ -308,7 +332,8 @@ export const Cell = ({
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}
+ cm_highlighted_range=${cm_highlighted_range}
+ cm_diagnostics=${cm_diagnostics}
onerror=${remount}
/>
${show_logs && cell_api_ready
@@ -320,8 +345,8 @@ export const Cell = ({
depends_on_disabled_cells=${depends_on_disabled_cells}
on_run=${on_run}
on_interrupt=${() => {
- pluto_actions.interrupt_remote(cell_id)
- }}
+ pluto_actions.interrupt_remote(cell_id)
+ }}
set_cell_disabled=${set_cell_disabled}
runtime=${runtime}
running=${running}
@@ -331,42 +356,42 @@ 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.
+ 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.
Use the context menu to enable it again`,
- })
- }}
+ })
+ }}
>
{
- 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.
+ 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.
An upstream cell is indirectlydisabling in file this one; enable
the upstream one to affect
this cell.`,
- })
- }}
+ })
+ }}
>
`
: null}
@@ -388,7 +413,7 @@ export const IsolatedCell = ({ cell_input: { cell_id, metadata }, cell_result: {
return html`
${cell_api_ready ? html`<${CellOutput} ...${output} cell_id=${cell_id} />` : html``}
- ${show_logs ? html`<${Logs} logs=${Object.values(logs)} line_heights=${[15]} set_cm_highlighted_line=${() => {}} />` : null}
+ ${show_logs ? html`<${Logs} logs=${Object.values(logs)} line_heights=${[15]} set_cm_highlighted_line=${() => { }} />` : null}
`
}
diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js
index 9133b305a2..33cdb5e2e8 100644
--- a/frontend/components/CellInput.js
+++ b/frontend/components/CellInput.js
@@ -48,6 +48,7 @@ import {
pythonLanguage,
syntaxHighlighting,
cssLanguage,
+ setDiagnostics,
} from "../imports/CodemirrorPlutoSetup.js"
import { markdown, html as htmlLang, javascript, sqlLang, python, julia_mixed } from "./CellInput/mixedParsers.js"
@@ -59,7 +60,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"
+import { HighlightLineFacet, HighlightRangeFacet, highlightLinePlugin, highlightRangePlugin } from "./CellInput/highlight_line.js"
import { commentKeymap } from "./CellInput/comment_mixed_parsers.js"
import { ScopeStateField } from "./CellInput/scopestate_statefield.js"
import { mod_d_command } from "./CellInput/mod_d_command.js"
@@ -373,8 +374,10 @@ export const CellInput = ({
set_show_logs,
set_cell_disabled,
cm_highlighted_line,
+ cm_highlighted_range,
metadata,
global_definition_locations,
+ cm_diagnostics,
}) => {
let pluto_actions = useContext(PlutoActionsContext)
const { disabled: running_disabled, skip_as_script } = metadata
@@ -384,6 +387,7 @@ export const CellInput = ({
set_error(null)
throw to_throw
}
+
const notebook_id_ref = useRef(notebook_id)
notebook_id_ref.current = notebook_id
@@ -394,6 +398,7 @@ export const CellInput = ({
let nbpkg_compartment = useCompartment(newcm_ref, NotebookpackagesFacet.of(nbpkg))
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 highlighted_range_compartment = useCompartment(newcm_ref, HighlightRangeFacet.of(cm_highlighted_range))
let editable_compartment = useCompartment(newcm_ref, EditorState.readOnly.of(disable_input))
let on_change_compartment = useCompartment(
@@ -589,9 +594,11 @@ export const CellInput = ({
// Compartments coming from react state/props
nbpkg_compartment,
highlighted_line_compartment,
+ highlighted_range_compartment,
global_definitions_compartment,
editable_compartment,
highlightLinePlugin(),
+ highlightRangePlugin(),
// This is waaaay in front of the keys it is supposed to override,
// Which is necessary because it needs to run before *any* keymap,
@@ -713,6 +720,12 @@ export const CellInput = ({
// Wowww this has been enabled for some time now... wonder if there are issues about this yet ;) - DRAL
awesome_line_wrapping,
+ // Reset diagnostics on change
+ EditorView.updateListener.of((update) => {
+ if (!update.docChanged) return
+ update.view.dispatch(setDiagnostics(update.state, []))
+ }),
+
on_change_compartment,
// This is my weird-ass extension that checks the AST and shows you where
@@ -778,6 +791,14 @@ export const CellInput = ({
}
}, [])
+ useEffect(() => {
+ if (newcm_ref.current == null) return
+ const cm = newcm_ref.current
+ const diagnostics = cm_diagnostics
+
+ cm.dispatch(setDiagnostics(cm.state, diagnostics))
+ }, [cm_diagnostics])
+
// Effect to apply "remote_code" to the cell when it changes...
// ideally this won't be necessary as we'll have actual multiplayer,
// or something to tell the user that the cell is out of sync.
diff --git a/frontend/components/CellInput/highlight_line.js b/frontend/components/CellInput/highlight_line.js
index f66346f878..d5938350d4 100644
--- a/frontend/components/CellInput/highlight_line.js
+++ b/frontend/components/CellInput/highlight_line.js
@@ -4,6 +4,10 @@ const highlighted_line = Decoration.line({
attributes: { class: "cm-highlighted-line" },
})
+const highlighted_range = Decoration.mark({
+ attributes: { class: "cm-highlighted-range" },
+})
+
/**
* @param {EditorView} view
*/
@@ -17,6 +21,22 @@ function create_line_decorations(view) {
return Decoration.set([highlighted_line.range(line.from, line.from)])
}
+/**
+ * @param {EditorView} view
+ */
+function create_range_decorations(view) {
+ let range = view.state.facet(HighlightRangeFacet)
+ if (range == null) {
+ return Decoration.set([])
+ }
+ let { from, to } = range
+ if (from < 0 || from == to) {
+ return Decoration.set([])
+ }
+
+ return Decoration.set([highlighted_range.range(from, to)])
+}
+
/**
* @type Facet
*/
@@ -25,6 +45,14 @@ export const HighlightLineFacet = Facet.define({
compare: (a, b) => a === b,
})
+/**
+ * @type Facet<{from: number, to: number}?, {from: number, to: number}?>
+ */
+export const HighlightRangeFacet = Facet.define({
+ combine: (values) => values[0],
+ compare: (a, b) => a === b,
+})
+
export const highlightLinePlugin = () =>
ViewPlugin.fromClass(
class {
@@ -53,3 +81,33 @@ export const highlightLinePlugin = () =>
decorations: (v) => v.decorations,
}
)
+
+
+export const highlightRangePlugin = () =>
+ ViewPlugin.fromClass(
+ class {
+ updateDecos(view) {
+ this.decorations = create_range_decorations(view)
+ }
+
+ /**
+ * @param {EditorView} view
+ */
+ constructor(view) {
+ this.decorations = Decoration.set([])
+ this.updateDecos(view)
+ }
+
+ /**
+ * @param {ViewUpdate} update
+ */
+ update(update) {
+ if (update.docChanged || update.state.facet(HighlightRangeFacet) !== update.startState.facet(HighlightRangeFacet)) {
+ this.updateDecos(update.view)
+ }
+ }
+ },
+ {
+ decorations: (v) => v.decorations,
+ }
+ )
diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js
index 2130d6a484..94885157fc 100644
--- a/frontend/components/CellInput/pluto_autocomplete.js
+++ b/frontend/components/CellInput/pluto_autocomplete.js
@@ -131,7 +131,11 @@ let update_docs_from_autocomplete_selection = (on_update_doc_query) => {
// The nice thing about this is that we can use the resulting state from the transaction,
// without updating the actual state of the editor.
let result_transaction = update.state.update({
- changes: { from: selected_option.source.from, to: selected_option.source.to, insert: text_to_apply },
+ changes: {
+ from: selected_option.source.from,
+ to: Math.min(selected_option.source.to, update.state.doc.length),
+ insert: text_to_apply,
+ },
})
// So we can use `get_selected_doc_from_state` on our virtual state
diff --git a/frontend/components/CellOutput.js b/frontend/components/CellOutput.js
index ff4188cf36..8eb7b4943a 100644
--- a/frontend/components/CellOutput.js
+++ b/frontend/components/CellOutput.js
@@ -1,6 +1,6 @@
import { html, Component, useRef, useLayoutEffect, useContext } from "../imports/Preact.js"
-import { ErrorMessage } from "./ErrorMessage.js"
+import { ErrorMessage, ParseError } from "./ErrorMessage.js"
import { TreeView, TableView, DivElement } from "./TreeView.js"
import {
@@ -148,6 +148,9 @@ export const OutputBody = ({ mime, body, cell_id, persist_js_state = false, last
case "application/vnd.pluto.table+object":
return html`<${TableView} cell_id=${cell_id} body=${body} persist_js_state=${persist_js_state} />`
break
+ case "application/vnd.pluto.parseerror+object":
+ return html`
<${ParseError} cell_id=${cell_id} ...${body} />
`
+ break
case "application/vnd.pluto.stacktrace+object":
return html`
`
- )}
+ )}
`}
`
diff --git a/frontend/editor.css b/frontend/editor.css
index c573b7c38c..73964d87d5 100644
--- a/frontend/editor.css
+++ b/frontend/editor.css
@@ -1232,6 +1232,7 @@ pluto-input .cm-editor .cm-line {
transition: background-color 0.15s ease-in-out;
}
+pluto-input .cm-editor span.cm-highlighted-range,
pluto-input .cm-editor .cm-line.cm-highlighted-line {
background-color: #bdbdbd68;
border-radius: 3px;
@@ -1934,6 +1935,9 @@ pluto-runarea {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
border-top: none;
+ /* One less than z-index for pluto-input .cm-editor.
+ Otherwise it gets on top of the tooltips */
+ z-index: 19;
}
pluto-runarea > span {
@@ -3048,6 +3052,12 @@ button.floating_back_button {
border-radius: 4px;
}
+.cm-tooltip-lint {
+ font-family: "JuliaMono";
+ font-size: 0.75rem;
+ z-index: 100;
+}
+
.cm-tooltip-autocomplete {
max-height: calc(20 * 16px);
box-sizing: content-box;
@@ -3185,10 +3195,10 @@ pluto-input .cm-editor .cm-content {
padding: 2px 0px;
}
-.cm-editor .cm-selectionBackground {
+.cm-editor .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
background: var(--cm-selection-background-blurred);
}
-.cm-editor.cm-focused .cm-selectionBackground {
+.cm-editor.cm-focused .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
background: var(--cm-selection-background);
}
diff --git a/frontend/imports/CodemirrorPlutoSetup.d.ts b/frontend/imports/CodemirrorPlutoSetup.d.ts
index 6919393ed4..4e298e6d9d 100644
--- a/frontend/imports/CodemirrorPlutoSetup.d.ts
+++ b/frontend/imports/CodemirrorPlutoSetup.d.ts
@@ -1865,6 +1865,17 @@ declare type Attrs = {
[name: string]: string;
};
+/**
+Basic rectangle type.
+*/
+interface Rect {
+ readonly left: number;
+ readonly right: number;
+ readonly top: number;
+ readonly bottom: number;
+}
+declare type ScrollStrategy = "nearest" | "start" | "end" | "center";
+
interface MarkDecorationSpec {
/**
Whether the mark covers its start and end position or not. This
@@ -2013,7 +2024,7 @@ declare abstract class WidgetType {
couldn't (in which case the widget will be redrawn). The default
implementation just returns false.
*/
- updateDOM(dom: HTMLElement): boolean;
+ updateDOM(dom: HTMLElement, view: EditorView): boolean;
/**
The estimated height this widget will have, to be used when
estimating the height of content that hasn't been drawn. May
@@ -2028,6 +2039,14 @@ declare abstract class WidgetType {
*/
ignoreEvent(event: Event): boolean;
/**
+ Override the way screen coordinates for positions at/in the
+ widget are found. `pos` will be the offset into the widget, and
+ `side` the side of the position that is being queried—less than
+ zero for before, greater than zero for after, and zero for
+ directly at that position.
+ */
+ coordsAt(dom: HTMLElement, pos: number, side: number): Rect | null;
+ /**
This is called when the an instance of the widget is removed
from the editor view.
*/
@@ -2130,17 +2149,6 @@ declare abstract class Decoration extends RangeValue {
static none: DecorationSet;
}
-/**
-Basic rectangle type.
-*/
-interface Rect {
- readonly left: number;
- readonly right: number;
- readonly top: number;
- readonly bottom: number;
-}
-declare type ScrollStrategy = "nearest" | "start" | "end" | "center";
-
/**
Command functions are used in key bindings and other types of user
actions. Given an editor view, they check whether their effect can
@@ -2841,6 +2849,11 @@ declare class EditorView {
*/
static inputHandler: Facet<(view: EditorView, from: number, to: number, text: string) => boolean, readonly ((view: EditorView, from: number, to: number, text: string) => boolean)[]>;
/**
+ This facet can be used to provide functions that create effects
+ to be dispatched when the editor's focus state changes.
+ */
+ static focusChangeEffect: Facet<(state: EditorState, focusing: boolean) => StateEffect | null, readonly ((state: EditorState, focusing: boolean) => StateEffect | null)[]>;
+ /**
By default, the editor assumes all its content has the same
[text direction](https://codemirror.net/6/docs/ref/#view.Direction). Configure this with a `true`
value to make it read the text direction of every (rendered)
@@ -3216,99 +3229,458 @@ Create a line number gutter extension.
*/
declare function lineNumbers(config?: LineNumberConfig): Extension;
+/**
+Highlighting tags are markers that denote a highlighting category.
+They are [associated](#highlight.styleTags) with parts of a syntax
+tree by a language mode, and then mapped to an actual CSS style by
+a [highlighter](#highlight.Highlighter).
+
+Because syntax tree node types and highlight styles have to be
+able to talk the same language, CodeMirror uses a mostly _closed_
+[vocabulary](#highlight.tags) of syntax tags (as opposed to
+traditional open string-based systems, which make it hard for
+highlighting themes to cover all the tokens produced by the
+various languages).
+
+It _is_ possible to [define](#highlight.Tag^define) your own
+highlighting tags for system-internal use (where you control both
+the language package and the highlighter), but such tags will not
+be picked up by regular highlighters (though you can derive them
+from standard tags to allow highlighters to fall back to those).
+*/
declare class Tag {
+ /**
+ The set of this tag and all its parent tags, starting with
+ this one itself and sorted in order of decreasing specificity.
+ */
readonly set: Tag[];
+ /**
+ Define a new tag. If `parent` is given, the tag is treated as a
+ sub-tag of that parent, and
+ [highlighters](#highlight.tagHighlighter) that don't mention
+ this tag will try to fall back to the parent tag (or grandparent
+ tag, etc).
+ */
static define(parent?: Tag): Tag;
+ /**
+ Define a tag _modifier_, which is a function that, given a tag,
+ will return a tag that is a subtag of the original. Applying the
+ same modifier to a twice tag will return the same value (`m1(t1)
+ == m1(t1)`) and applying multiple modifiers will, regardless or
+ order, produce the same tag (`m1(m2(t1)) == m2(m1(t1))`).
+
+ When multiple modifiers are applied to a given base tag, each
+ smaller set of modifiers is registered as a parent, so that for
+ example `m1(m2(m3(t1)))` is a subtype of `m1(m2(t1))`,
+ `m1(m3(t1)`, and so on.
+ */
static defineModifier(): (tag: Tag) => Tag;
}
+/**
+A highlighter defines a mapping from highlighting tags and
+language scopes to CSS class names. They are usually defined via
+[`tagHighlighter`](#highlight.tagHighlighter) or some wrapper
+around that, but it is also possible to implement them from
+scratch.
+*/
interface Highlighter {
+ /**
+ Get the set of classes that should be applied to the given set
+ of highlighting tags, or null if this highlighter doesn't assign
+ a style to the tags.
+ */
style(tags: readonly Tag[]): string | null;
+ /**
+ When given, the highlighter will only be applied to trees on
+ whose [top](#common.NodeType.isTop) node this predicate returns
+ true.
+ */
scope?(node: NodeType): boolean;
}
+/**
+The default set of highlighting [tags](#highlight.Tag).
+
+This collection is heavily biased towards programming languages,
+and necessarily incomplete. A full ontology of syntactic
+constructs would fill a stack of books, and be impractical to
+write themes for. So try to make do with this set. If all else
+fails, [open an
+issue](https://github.com/codemirror/codemirror.next) to propose a
+new tag, or [define](#highlight.Tag^define) a local custom tag for
+your use case.
+
+Note that it is not obligatory to always attach the most specific
+tag possible to an element—if your grammar can't easily
+distinguish a certain type of element (such as a local variable),
+it is okay to style it as its more general variant (a variable).
+
+For tags that extend some parent tag, the documentation links to
+the parent.
+*/
declare const tags: {
+ /**
+ A comment.
+ */
comment: Tag;
+ /**
+ A line [comment](#highlight.tags.comment).
+ */
lineComment: Tag;
+ /**
+ A block [comment](#highlight.tags.comment).
+ */
blockComment: Tag;
+ /**
+ A documentation [comment](#highlight.tags.comment).
+ */
docComment: Tag;
+ /**
+ Any kind of identifier.
+ */
name: Tag;
+ /**
+ The [name](#highlight.tags.name) of a variable.
+ */
variableName: Tag;
+ /**
+ A type [name](#highlight.tags.name).
+ */
typeName: Tag;
+ /**
+ A tag name (subtag of [`typeName`](#highlight.tags.typeName)).
+ */
tagName: Tag;
+ /**
+ A property or field [name](#highlight.tags.name).
+ */
propertyName: Tag;
+ /**
+ An attribute name (subtag of [`propertyName`](#highlight.tags.propertyName)).
+ */
attributeName: Tag;
+ /**
+ The [name](#highlight.tags.name) of a class.
+ */
className: Tag;
+ /**
+ A label [name](#highlight.tags.name).
+ */
labelName: Tag;
+ /**
+ A namespace [name](#highlight.tags.name).
+ */
namespace: Tag;
+ /**
+ The [name](#highlight.tags.name) of a macro.
+ */
macroName: Tag;
+ /**
+ A literal value.
+ */
literal: Tag;
+ /**
+ A string [literal](#highlight.tags.literal).
+ */
string: Tag;
+ /**
+ A documentation [string](#highlight.tags.string).
+ */
docString: Tag;
+ /**
+ A character literal (subtag of [string](#highlight.tags.string)).
+ */
character: Tag;
+ /**
+ An attribute value (subtag of [string](#highlight.tags.string)).
+ */
attributeValue: Tag;
+ /**
+ A number [literal](#highlight.tags.literal).
+ */
number: Tag;
+ /**
+ An integer [number](#highlight.tags.number) literal.
+ */
integer: Tag;
+ /**
+ A floating-point [number](#highlight.tags.number) literal.
+ */
float: Tag;
+ /**
+ A boolean [literal](#highlight.tags.literal).
+ */
bool: Tag;
+ /**
+ Regular expression [literal](#highlight.tags.literal).
+ */
regexp: Tag;
+ /**
+ An escape [literal](#highlight.tags.literal), for example a
+ backslash escape in a string.
+ */
escape: Tag;
+ /**
+ A color [literal](#highlight.tags.literal).
+ */
color: Tag;
+ /**
+ A URL [literal](#highlight.tags.literal).
+ */
url: Tag;
+ /**
+ A language keyword.
+ */
keyword: Tag;
+ /**
+ The [keyword](#highlight.tags.keyword) for the self or this
+ object.
+ */
self: Tag;
+ /**
+ The [keyword](#highlight.tags.keyword) for null.
+ */
null: Tag;
+ /**
+ A [keyword](#highlight.tags.keyword) denoting some atomic value.
+ */
atom: Tag;
+ /**
+ A [keyword](#highlight.tags.keyword) that represents a unit.
+ */
unit: Tag;
+ /**
+ A modifier [keyword](#highlight.tags.keyword).
+ */
modifier: Tag;
+ /**
+ A [keyword](#highlight.tags.keyword) that acts as an operator.
+ */
operatorKeyword: Tag;
+ /**
+ A control-flow related [keyword](#highlight.tags.keyword).
+ */
controlKeyword: Tag;
+ /**
+ A [keyword](#highlight.tags.keyword) that defines something.
+ */
definitionKeyword: Tag;
+ /**
+ A [keyword](#highlight.tags.keyword) related to defining or
+ interfacing with modules.
+ */
moduleKeyword: Tag;
+ /**
+ An operator.
+ */
operator: Tag;
+ /**
+ An [operator](#highlight.tags.operator) that dereferences something.
+ */
derefOperator: Tag;
+ /**
+ Arithmetic-related [operator](#highlight.tags.operator).
+ */
arithmeticOperator: Tag;
+ /**
+ Logical [operator](#highlight.tags.operator).
+ */
logicOperator: Tag;
+ /**
+ Bit [operator](#highlight.tags.operator).
+ */
bitwiseOperator: Tag;
+ /**
+ Comparison [operator](#highlight.tags.operator).
+ */
compareOperator: Tag;
+ /**
+ [Operator](#highlight.tags.operator) that updates its operand.
+ */
updateOperator: Tag;
+ /**
+ [Operator](#highlight.tags.operator) that defines something.
+ */
definitionOperator: Tag;
+ /**
+ Type-related [operator](#highlight.tags.operator).
+ */
typeOperator: Tag;
+ /**
+ Control-flow [operator](#highlight.tags.operator).
+ */
controlOperator: Tag;
+ /**
+ Program or markup punctuation.
+ */
punctuation: Tag;
+ /**
+ [Punctuation](#highlight.tags.punctuation) that separates
+ things.
+ */
separator: Tag;
+ /**
+ Bracket-style [punctuation](#highlight.tags.punctuation).
+ */
bracket: Tag;
+ /**
+ Angle [brackets](#highlight.tags.bracket) (usually `<` and `>`
+ tokens).
+ */
angleBracket: Tag;
+ /**
+ Square [brackets](#highlight.tags.bracket) (usually `[` and `]`
+ tokens).
+ */
squareBracket: Tag;
+ /**
+ Parentheses (usually `(` and `)` tokens). Subtag of
+ [bracket](#highlight.tags.bracket).
+ */
paren: Tag;
+ /**
+ Braces (usually `{` and `}` tokens). Subtag of
+ [bracket](#highlight.tags.bracket).
+ */
brace: Tag;
+ /**
+ Content, for example plain text in XML or markup documents.
+ */
content: Tag;
+ /**
+ [Content](#highlight.tags.content) that represents a heading.
+ */
heading: Tag;
+ /**
+ A level 1 [heading](#highlight.tags.heading).
+ */
heading1: Tag;
+ /**
+ A level 2 [heading](#highlight.tags.heading).
+ */
heading2: Tag;
+ /**
+ A level 3 [heading](#highlight.tags.heading).
+ */
heading3: Tag;
+ /**
+ A level 4 [heading](#highlight.tags.heading).
+ */
heading4: Tag;
+ /**
+ A level 5 [heading](#highlight.tags.heading).
+ */
heading5: Tag;
+ /**
+ A level 6 [heading](#highlight.tags.heading).
+ */
heading6: Tag;
+ /**
+ A prose separator (such as a horizontal rule).
+ */
contentSeparator: Tag;
+ /**
+ [Content](#highlight.tags.content) that represents a list.
+ */
list: Tag;
+ /**
+ [Content](#highlight.tags.content) that represents a quote.
+ */
quote: Tag;
+ /**
+ [Content](#highlight.tags.content) that is emphasized.
+ */
emphasis: Tag;
+ /**
+ [Content](#highlight.tags.content) that is styled strong.
+ */
strong: Tag;
+ /**
+ [Content](#highlight.tags.content) that is part of a link.
+ */
link: Tag;
+ /**
+ [Content](#highlight.tags.content) that is styled as code or
+ monospace.
+ */
monospace: Tag;
+ /**
+ [Content](#highlight.tags.content) that has a strike-through
+ style.
+ */
strikethrough: Tag;
+ /**
+ Inserted text in a change-tracking format.
+ */
inserted: Tag;
+ /**
+ Deleted text.
+ */
deleted: Tag;
+ /**
+ Changed text.
+ */
changed: Tag;
+ /**
+ An invalid or unsyntactic element.
+ */
invalid: Tag;
+ /**
+ Metadata or meta-instruction.
+ */
meta: Tag;
+ /**
+ [Metadata](#highlight.tags.meta) that applies to the entire
+ document.
+ */
documentMeta: Tag;
+ /**
+ [Metadata](#highlight.tags.meta) that annotates or adds
+ attributes to a given syntactic element.
+ */
annotation: Tag;
+ /**
+ Processing instruction or preprocessor directive. Subtag of
+ [meta](#highlight.tags.meta).
+ */
processingInstruction: Tag;
+ /**
+ [Modifier](#highlight.Tag^defineModifier) that indicates that a
+ given element is being defined. Expected to be used with the
+ various [name](#highlight.tags.name) tags.
+ */
definition: (tag: Tag) => Tag;
+ /**
+ [Modifier](#highlight.Tag^defineModifier) that indicates that
+ something is constant. Mostly expected to be used with
+ [variable names](#highlight.tags.variableName).
+ */
constant: (tag: Tag) => Tag;
+ /**
+ [Modifier](#highlight.Tag^defineModifier) used to indicate that
+ a [variable](#highlight.tags.variableName) or [property
+ name](#highlight.tags.propertyName) is being called or defined
+ as a function.
+ */
function: (tag: Tag) => Tag;
+ /**
+ [Modifier](#highlight.Tag^defineModifier) that can be applied to
+ [names](#highlight.tags.name) to indicate that they belong to
+ the language's standard environment.
+ */
standard: (tag: Tag) => Tag;
+ /**
+ [Modifier](#highlight.Tag^defineModifier) that indicates a given
+ [names](#highlight.tags.name) is local to some scope.
+ */
local: (tag: Tag) => Tag;
+ /**
+ A generic variant [modifier](#highlight.Tag^defineModifier) that
+ can be used to tag language-specific alternative variants of
+ some common tag. It is recommended for themes to define special
+ forms of at least the [string](#highlight.tags.string) and
+ [variable name](#highlight.tags.variableName) tags, since those
+ come up a lot.
+ */
special: (tag: Tag) => Tag;
};
@@ -3561,9 +3933,9 @@ declare class LanguageDescription {
static matchLanguageName(descs: readonly LanguageDescription[], name: string, fuzzy?: boolean): LanguageDescription | null;
}
/**
-Facet for overriding the unit by which indentation happens.
-Should be a string consisting either entirely of spaces or
-entirely of tabs. When not set, this defaults to 2 spaces.
+Facet for overriding the unit by which indentation happens. Should
+be a string consisting either entirely of the same whitespace
+character. When not set, this defaults to 2 spaces.
*/
declare const indentUnit: Facet;
/**
@@ -3844,7 +4216,7 @@ The default keymap. Includes all bindings from
- Shift-Alt-ArrowUp: [`copyLineUp`](https://codemirror.net/6/docs/ref/#commands.copyLineUp)
- Shift-Alt-ArrowDown: [`copyLineDown`](https://codemirror.net/6/docs/ref/#commands.copyLineDown)
- Escape: [`simplifySelection`](https://codemirror.net/6/docs/ref/#commands.simplifySelection)
-- Ctrl-Enter (Comd-Enter on macOS): [`insertBlankLine`](https://codemirror.net/6/docs/ref/#commands.insertBlankLine)
+- Ctrl-Enter (Cmd-Enter on macOS): [`insertBlankLine`](https://codemirror.net/6/docs/ref/#commands.insertBlankLine)
- Alt-l (Ctrl-l on macOS): [`selectLine`](https://codemirror.net/6/docs/ref/#commands.selectLine)
- Ctrl-i (Cmd-i on macOS): [`selectParentSyntax`](https://codemirror.net/6/docs/ref/#commands.selectParentSyntax)
- Ctrl-[ (Cmd-[ on macOS): [`indentLess`](https://codemirror.net/6/docs/ref/#commands.indentLess)
@@ -3933,6 +4305,19 @@ interface CompletionConfig {
position: number;
}[];
/**
+ By default, [info](https://codemirror.net/6/docs/ref/#autocomplet.Completion.info) tooltips are
+ placed to the side of the selected. This option can be used to
+ override that. It will be given rectangles for the list of
+ completions, the selected option, the info element, and the
+ availble [tooltip space](https://codemirror.net/6/docs/ref/#view.tooltips^config.tooltipSpace),
+ and should return style and/or class strings for the info
+ element.
+ */
+ positionInfo?: (view: EditorView, list: Rect, option: Rect, info: Rect, space: Rect) => {
+ style?: string;
+ class?: string;
+ };
+ /**
The comparison function to use when sorting completions with the same
match score. Defaults to using
[`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
@@ -3997,6 +4382,39 @@ interface Completion {
down the list, a positive number moves it up.
*/
boost?: number;
+ /**
+ Can be used to divide the completion list into sections.
+ Completions in a given section (matched by name) will be grouped
+ together, with a heading above them. Options without section
+ will appear above all sections. A string value is equivalent to
+ a `{name}` object.
+ */
+ section?: string | CompletionSection;
+}
+/**
+Object used to describe a completion
+[section](https://codemirror.net/6/docs/ref/#autocomplete.Completion.section). It is recommended to
+create a shared object used by all the completions in a given
+section.
+*/
+interface CompletionSection {
+ /**
+ The name of the section. If no `render` method is present, this
+ will be displayed above the options.
+ */
+ name: string;
+ /**
+ An optional function that renders the section header. Since the
+ headers are shown inside a list, you should make sure the
+ resulting element has a `display: list-item` style.
+ */
+ header?: (section: CompletionSection) => HTMLElement;
+ /**
+ By default, sections are ordered alphabetically by name. To
+ specify an explicit order, `rank` can be used. Sections with a
+ lower rank will be shown above sections with a higher rank.
+ */
+ rank?: number;
}
/**
An instance of this is passed to completion source functions.
@@ -4192,7 +4610,7 @@ interpreted as indicating a placeholder.
declare function snippet(template: string): (editor: {
state: EditorState;
dispatch: (tr: Transaction) => void;
-}, _completion: Completion, from: number, to: number) => void;
+}, completion: Completion, from: number, to: number) => void;
/**
A command that clears the active snippet, if any.
*/
@@ -4347,6 +4765,7 @@ type index_d_Completion = Completion;
type index_d_CompletionContext = CompletionContext;
declare const index_d_CompletionContext: typeof CompletionContext;
type index_d_CompletionResult = CompletionResult;
+type index_d_CompletionSection = CompletionSection;
type index_d_CompletionSource = CompletionSource;
declare const index_d_acceptCompletion: typeof acceptCompletion;
declare const index_d_autocompletion: typeof autocompletion;
@@ -4381,6 +4800,7 @@ declare namespace index_d {
index_d_Completion as Completion,
index_d_CompletionContext as CompletionContext,
index_d_CompletionResult as CompletionResult,
+ index_d_CompletionSection as CompletionSection,
index_d_CompletionSource as CompletionSource,
index_d_acceptCompletion as acceptCompletion,
index_d_autocompletion as autocompletion,
@@ -4697,6 +5117,101 @@ declare function html(config?: {
nestedAttributes?: NestedAttr[];
}): LanguageSupport;
+/**
+Describes a problem or hint for a piece of code.
+*/
+interface Diagnostic {
+ /**
+ The start position of the relevant text.
+ */
+ from: number;
+ /**
+ The end position. May be equal to `from`, though actually
+ covering text is preferable.
+ */
+ to: number;
+ /**
+ The severity of the problem. This will influence how it is
+ displayed.
+ */
+ severity: "info" | "warning" | "error";
+ /**
+ An optional source string indicating where the diagnostic is
+ coming from. You can put the name of your linter here, if
+ applicable.
+ */
+ source?: string;
+ /**
+ The message associated with this diagnostic.
+ */
+ message: string;
+ /**
+ An optional custom rendering function that displays the message
+ as a DOM node.
+ */
+ renderMessage?: () => Node;
+ /**
+ An optional array of actions that can be taken on this
+ diagnostic.
+ */
+ actions?: readonly Action[];
+}
+/**
+An action associated with a diagnostic.
+*/
+interface Action {
+ /**
+ The label to show to the user. Should be relatively short.
+ */
+ name: string;
+ /**
+ The function to call when the user activates this action. Is
+ given the diagnostic's _current_ position, which may have
+ changed since the creation of the diagnostic, due to editing.
+ */
+ apply: (view: EditorView, from: number, to: number) => void;
+}
+declare type DiagnosticFilter = (diagnostics: readonly Diagnostic[]) => Diagnostic[];
+interface LintConfig {
+ /**
+ Time to wait (in milliseconds) after a change before running
+ the linter. Defaults to 750ms.
+ */
+ delay?: number;
+ /**
+ Optional predicate that can be used to indicate when diagnostics
+ need to be recomputed. Linting is always re-done on document
+ changes.
+ */
+ needsRefresh?: null | ((update: ViewUpdate) => boolean);
+ /**
+ Optional filter to determine which diagnostics produce markers
+ in the content.
+ */
+ markerFilter?: null | DiagnosticFilter;
+ /**
+ Filter applied to a set of diagnostics shown in a tooltip. No
+ tooltip will appear if the empty set is returned.
+ */
+ tooltipFilter?: null | DiagnosticFilter;
+}
+/**
+Returns a transaction spec which updates the current set of
+diagnostics, and enables the lint extension if if wasn't already
+active.
+*/
+declare function setDiagnostics(state: EditorState, diagnostics: readonly Diagnostic[]): TransactionSpec;
+/**
+The type of a function that produces diagnostics.
+*/
+declare type LintSource = (view: EditorView) => readonly Diagnostic[] | Promise;
+/**
+Given a diagnostic source, this function returns an extension that
+enables linting with that source. It will be called whenever the
+editor is idle (after its content changed).
+*/
+declare function linter(source: LintSource, config?: LintConfig): Extension;
+
/**
A language provider based on the [Lezer JavaScript
parser](https://github.com/lezer-parser/javascript), extended with
@@ -4832,6 +5347,12 @@ interface SQLConfig {
*/
tables?: readonly Completion[];
/**
+ Similar to `tables`, if you want to provide completion objects
+ for your schemas rather than using the generated ones, pass them
+ here.
+ */
+ schemas?: readonly Completion[];
+ /**
When given, columns from the named table can be completed
directly at the top level.
*/
@@ -4893,4 +5414,4 @@ Create an instance of the collaborative editing plugin.
*/
declare function collab(config?: CollabConfig): Extension;
-export { Annotation, Compartment, Decoration, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, index_d as autocomplete, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, completionKeymap, css, cssLanguage, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, keymap, lineNumbers, markdown, markdownLanguage, parseCode, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, selectNextOccurrence, sql, syntaxHighlighting, syntaxTree, syntaxTreeAvailable, tags };
+export { Annotation, Compartment, Decoration, Diagnostic, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, index_d as autocomplete, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, completionKeymap, css, cssLanguage, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, keymap, lineNumbers, linter, markdown, markdownLanguage, parseCode, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, selectNextOccurrence, setDiagnostics, sql, syntaxHighlighting, syntaxTree, syntaxTreeAvailable, tags };
diff --git a/frontend/imports/CodemirrorPlutoSetup.js b/frontend/imports/CodemirrorPlutoSetup.js
index ce8c635152..da54f20909 100644
--- a/frontend/imports/CodemirrorPlutoSetup.js
+++ b/frontend/imports/CodemirrorPlutoSetup.js
@@ -59,10 +59,14 @@ import {
css,
cssLanguage,
selectNextOccurrence,
+ linter,
+ setDiagnostics,
//@ts-ignore
-} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@0.28.4/dist/index.es.min.js"
+} from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@1234.0.0/dist/index.es.min.js"
export {
+ linter,
+ setDiagnostics,
EditorState,
EditorSelection,
Compartment,
diff --git a/src/analysis/ExpressionExplorer.jl b/src/analysis/ExpressionExplorer.jl
index 1fd4b5837c..67edc5c82a 100644
--- a/src/analysis/ExpressionExplorer.jl
+++ b/src/analysis/ExpressionExplorer.jl
@@ -1348,6 +1348,7 @@ function can_be_function_wrapped(x::Expr)
x.head === :using ||
x.head === :import ||
x.head === :module ||
+ x.head === :incomplete ||
# Only bail on named functions, but anonymous functions (args[1].head == :tuple) are fine.
# TODO Named functions INSIDE other functions should be fine too
(x.head === :function && !Meta.isexpr(x.args[1], :tuple)) ||
diff --git a/src/analysis/Parse.jl b/src/analysis/Parse.jl
index 200b5faf4b..b3ac5162d5 100644
--- a/src/analysis/Parse.jl
+++ b/src/analysis/Parse.jl
@@ -1,4 +1,5 @@
import .ExpressionExplorer
+import Markdown
"Generate a file name to be given to the parser (will show up in stack traces)."
pluto_filename(notebook::Notebook, cell::Cell)::String = notebook.path * "#==#" * string(cell.cell_id)
@@ -19,7 +20,7 @@ function parse_custom(notebook::Notebook, cell::Cell)::Expr
raw = if can_insert_filename
filename = pluto_filename(notebook, cell)
ex = Base.parse_input_line(cell.code, filename=filename)
- if (ex isa Expr) && (ex.head == :toplevel)
+ if Meta.isexpr(ex, :toplevel)
# if there is more than one expression:
if count(a -> !(a isa LineNumberNode), ex.args) > 1
Expr(:error, "extra token after end of expression\n\nBoundaries: $(expression_boundaries(cell.code))")
@@ -100,12 +101,14 @@ Make some small adjustments to the `expr` to make it work nicely inside a timed,
3. If `expr` is a `:(=)` expression with a curly assignment, wrap it in a `:const` to allow execution - see https://github.com/fonsp/Pluto.jl/issues/517
"""
function preprocess_expr(expr::Expr)
- if expr.head == :toplevel
+ if expr.head === :toplevel
Expr(:block, expr.args...)
- elseif expr.head == :module
+ elseif expr.head === :module
Expr(:toplevel, expr)
- elseif expr.head == :(=) && (expr.args[1] isa Expr && expr.args[1].head == :curly)
+ elseif expr.head === :(=) && (expr.args[1] isa Expr && expr.args[1].head == :curly)
Expr(:const, expr)
+ elseif expr.head === :incomplete
+ Expr(:call, :(PlutoRunner.throw_syntax_error), expr.args...)
else
expr
end
diff --git a/src/analysis/is_just_text.jl b/src/analysis/is_just_text.jl
index f4402a6b2a..38339f68af 100644
--- a/src/analysis/is_just_text.jl
+++ b/src/analysis/is_just_text.jl
@@ -1,12 +1,22 @@
-const md_and_friends = [Symbol("@md_str"), Symbol("@html_str"), :getindex]
+const md_and_friends = [
+ # Text
+ Symbol("@md_str"),
+ Symbol("@html_str"),
+ :getindex,
+]
"""Does the cell only contain md"..." and html"..."?
This is used to run these cells first."""
function is_just_text(topology::NotebookTopology, cell::Cell)::Bool
# https://github.com/fonsp/Pluto.jl/issues/209
- isempty(topology.nodes[cell].definitions) && isempty(topology.nodes[cell].funcdefs_with_signatures) &&
- topology.nodes[cell].references ⊆ md_and_friends &&
+ node = topology.nodes[cell]
+ ((isempty(node.definitions) &&
+ isempty(node.funcdefs_with_signatures) &&
+ node.references ⊆ md_and_friends) ||
+ (length(node.references) == 2 &&
+ :PlutoRunner in node.references &&
+ Symbol("PlutoRunner.throw_syntax_error") in node.references)) &&
no_loops(ExpressionExplorer.maybe_macroexpand(topology.codes[cell].parsedcode; recursive=true))
end
@@ -24,4 +34,4 @@ function no_loops(ex::Expr)
end
end
-no_loops(x) = true
\ No newline at end of file
+no_loops(x) = true
diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl
index 6916596b7a..bbd1800e00 100644
--- a/src/runner/PlutoRunner.jl
+++ b/src/runner/PlutoRunner.jl
@@ -273,7 +273,7 @@ function try_macroexpand(mod::Module, notebook_id::UUID, cell_id::UUID, expr; ca
pop!(cell_expanded_exprs, cell_id, nothing)
# Remove toplevel block, as that screws with the computer and everything
- expr_not_toplevel = if expr.head == :toplevel || expr.head == :block
+ expr_not_toplevel = if Meta.isexpr(expr, (:toplevel, :block))
Expr(:block, expr.args...)
else
@warn "try_macroexpand expression not :toplevel or :block" expr
@@ -1030,7 +1030,177 @@ 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)
+# @codemirror/lint has only three levels
+function convert_julia_syntax_level(level)
+ level == :error ? "error" :
+ level == :warning ? "warning" : "info"
+end
+
+"""
+ map_byte_range_to_utf16_codepoints(s::String, start_byte::Int, end_byte::Int)::Tuple{Int,Int}
+
+Taken from `Base.transcode(::Type{UInt16}, src::Vector{UInt8})`
+but without line constraints. It also does not support invalid
+UTF-8 encoding which `String` should never be anyway.
+
+This maps the given raw byte range `(start_byte, end_byte)` range to UTF-16 codepoints indices.
+
+The resulting range can then be used by code-mirror on the frontend, quoting from the code-mirror docs:
+
+> Character positions are counted from zero, and count each line break and UTF-16 code unit as one unit.
+
+Examples:
+```julia
+ 123
+ vv
+julia> map_byte_range_to_utf16_codepoints("abc", 2, 3)
+(2, 3)
+
+ 1122
+ v v
+julia> map_byte_range_to_utf16_codepoints("🍕🍕", 1, 8)
+(1, 4)
+
+ 11233
+ v v
+julia> map_byte_range_to_utf16_codepoints("🍕c🍕", 1, 5)
+(1, 3)
+```
+"""
+function map_byte_range_to_utf16_codepoints(s, start_byte, end_byte)
+ invalid_utf8() = error("invalid UTF-8 string")
+ codeunit(s) == UInt8 || invalid_utf8()
+
+ i, n = 1, ncodeunits(s)
+ u16 = 0
+
+ from, to = -1, -1
+ a = codeunit(s, 1)
+ while true
+ if i == start_byte
+ from = u16
+ end
+ if i == end_byte
+ to = u16
+ break
+ end
+ if i < n && -64 <= a % Int8 <= -12 # multi-byte character
+ i += 1
+ b = codeunit(s, i)
+ if -64 <= (b % Int8) || a == 0xf4 && 0x8f < b
+ # invalid UTF-8 (non-continuation of too-high code point)
+ invalid_utf8()
+ elseif a < 0xe0 # 2-byte UTF-8
+ if i == start_byte
+ from = u16
+ end
+ if i == end_byte
+ to = u16
+ break
+ end
+ elseif i < n # 3/4-byte character
+ i += 1
+ c = codeunit(s, i)
+ if -64 <= (c % Int8) # invalid UTF-8 (non-continuation)
+ invalid_utf8()
+ elseif a < 0xf0 # 3-byte UTF-8
+ if i == start_byte
+ from = u16
+ end
+ if i == end_byte
+ to = u16
+ break
+ end
+ elseif i < n
+ i += 1
+ d = codeunit(s, i)
+ if -64 <= (d % Int8) # invalid UTF-8 (non-continuation)
+ invalid_utf8()
+ elseif a == 0xf0 && b < 0x90 # overlong encoding
+ invalid_utf8()
+ else # 4-byte UTF-8 && 2 codeunits UTF-16
+ u16 += 1
+ if i == start_byte
+ from = u16
+ end
+ if i == end_byte
+ to = u16
+ break
+ end
+ end
+ else # too short
+ invalid_utf8()
+ end
+ else # too short
+ invalid_utf8()
+ end
+ else
+ # ASCII or invalid UTF-8 (continuation byte or too-high code point)
+ end
+ u16 += 1
+ if i >= n
+ break
+ end
+ i += 1
+ a = codeunit(s, i)
+ end
+
+ if from == -1
+ from = u16
+ end
+ if to == -1
+ to = u16
+ end
+
+ return (from, to)
+end
+
+function convert_diagnostic_to_dict(source, diag)
+ code = source.code
+
+ # JuliaSyntax uses `last_byte < first_byte` to signal an empty range.
+ # https://github.com/JuliaLang/JuliaSyntax.jl/blob/97e2825c68e770a3f56f0ec247deda1a8588070c/src/diagnostics.jl#L67-L75
+ # it references the byte range as such: `source[first_byte:last_byte]` whereas codemirror
+ # is non inclusive, therefore we move the `last_byte` to the next valid character in the string,
+ # an empty range then becomes `from == to`, also JuliaSyntax is one based whereas code-mirror is zero-based
+ # but this is handled in `map_byte_range_to_utf16_codepoints` with `u16 = 0` initially.
+ first_byte = min(diag.first_byte, lastindex(code) + 1)
+ last_byte = min(nextind(code, diag.last_byte), lastindex(code) + 1)
+
+ from, to = map_byte_range_to_utf16_codepoints(code, first_byte, last_byte)
+
+ Dict(:from => from,
+ :to => to,
+ :message => diag.message,
+ :source => "JuliaSyntax.jl",
+ :line => first(Base.JuliaSyntax.source_location(source, diag.first_byte)),
+ :severity => convert_julia_syntax_level(diag.level))
+end
+
+function convert_parse_error_to_dict(ex)
+ Dict(
+ :source => ex.source.code,
+ :diagnostics => [
+ convert_diagnostic_to_dict(ex.source, diag)
+ for diag in ex.diagnostics
+ ]
+ )
+end
+
+function throw_syntax_error(@nospecialize(syntax_err))
+ syntax_err isa String && (syntax_err = "syntax: $syntax_err")
+ syntax_err isa Exception || (syntax_err = ErrorException(syntax_err))
+ throw(syntax_err)
+end
+
+const has_julia_syntax = isdefined(Base, :JuliaSyntax) && fieldcount(Base.Meta.ParseError) == 2
+
function format_output(val::CapturedException; context=default_iocontext)
+ if has_julia_syntax && val.ex isa Base.Meta.ParseError && val.ex.detail isa Base.JuliaSyntax.ParseError
+ dict = convert_parse_error_to_dict(val.ex.detail)
+ return dict, MIME"application/vnd.pluto.parseerror+object"()
+ end
+
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]
diff --git a/test/Dynamic.jl b/test/Dynamic.jl
index 669017f640..56496f0d2d 100644
--- a/test/Dynamic.jl
+++ b/test/Dynamic.jl
@@ -156,7 +156,7 @@ end
let
doc_output = Pluto.PlutoRunner.doc_fetcher("sor", Main)[1]
@test occursin("Similar results:", doc_output)
- @test occursin("sortperm", doc_output)
+ @test occursin("sort", doc_output)
end
@test occursin("\\div", Pluto.PlutoRunner.doc_fetcher("÷", Main)[1])
diff --git a/test/ExpressionExplorer.jl b/test/ExpressionExplorer.jl
index afe010c1ff..5ab13b83a9 100644
--- a/test/ExpressionExplorer.jl
+++ b/test/ExpressionExplorer.jl
@@ -1,4 +1,5 @@
using Test
+import Pluto: PlutoRunner
#=
`@test_broken` means that the test doesn't pass right now, but we want it to pass. Feel free to try to fix it and open a PR!
@@ -795,3 +796,16 @@ Some of these @test_broken lines are commented out to prevent printing to the te
@test :Date ∈ rn.references
end
end
+
+@testset "UTF-8 to Codemirror UTF-16 byte mapping" begin
+ # range ends are non inclusives
+ tests = [
+ (" aaaa", (2, 4), (1, 3)), # cm is zero based
+ (" 🍕🍕", (2, 6), (1, 3)), # a 🍕 is two UTF16 codeunits
+ (" 🍕🍕", (6, 10), (3, 5)), # a 🍕 is two UTF16 codeunits
+ ]
+ for (s, (start_byte, end_byte), (from, to)) in tests
+ @show s
+ @test PlutoRunner.map_byte_range_to_utf16_codepoints(s, start_byte, end_byte) == (from, to)
+ end
+end
diff --git a/test/React.jl b/test/React.jl
index 191540c53a..0921a4ce0d 100644
--- a/test/React.jl
+++ b/test/React.jl
@@ -1752,4 +1752,28 @@ import Distributed
update_run!(🍭, notebook, notebook.cells)
@test all(noerror, notebook.cells)
end
+
+ @testset "ParseError messages" begin
+ notebook = Notebook(Cell.([
+ "begin",
+ "\n\nend",
+ ]))
+ update_run!(🍭, notebook, notebook.cells)
+ @test Pluto.is_just_text(notebook.topology, notebook.cells[1])
+ @test Pluto.is_just_text(notebook.topology, notebook.cells[2])
+ @static if VERSION >= v"1.10.0-DEV.1548" # ~JuliaSyntax PR Pluto.jl#2526 julia#46372
+ @test haskey(notebook.cells[1].output.body, :source)
+ @test haskey(notebook.cells[1].output.body, :diagnostics)
+
+ @test haskey(notebook.cells[2].output.body, :source)
+ @test haskey(notebook.cells[2].output.body, :diagnostics)
+ else
+ @test !occursinerror("(incomplete ", notebook.cells[1])
+ @test !occursinerror("(incomplete ", notebook.cells[2])
+
+ @show notebook.cells[1].output.body
+ @test startswith(notebook.cells[1].output.body[:msg], "syntax:")
+ @test startswith(notebook.cells[2].output.body[:msg], "syntax:")
+ end
+ end
end
diff --git a/test/packages/Basic.jl b/test/packages/Basic.jl
index 773be1ec71..7cc83ee9b1 100644
--- a/test/packages/Basic.jl
+++ b/test/packages/Basic.jl
@@ -648,7 +648,25 @@ import Distributed
WorkspaceManager.unmake_workspace((🍭, notebook))
end
-
+
+ @testset "PlutoRunner Syntax Error" begin
+ notebook = Notebook([
+ Cell("1 +"),
+ Cell("PlutoRunner.throw_syntax_error"),
+ Cell("PlutoRunner.throw_syntax_error(1)"),
+ ])
+
+ update_run!(🍭, notebook, notebook.cells)
+
+ @test notebook.cells[1].errored
+ @test noerror(notebook.cells[2])
+ @test notebook.cells[3].errored
+
+ @test Pluto.is_just_text(notebook.topology, notebook.cells[1])
+ @test !Pluto.is_just_text(notebook.topology, notebook.cells[2]) # Not a syntax error form
+ @test Pluto.is_just_text(notebook.topology, notebook.cells[3])
+ end
+
@testset "Precompilation" begin
compilation_dir = joinpath(DEPOT_PATH[1], "compiled", "v$(VERSION.major).$(VERSION.minor)")
@assert isdir(compilation_dir)
From 83ec7c59661d672caa7e8be63049c9215bea14cf Mon Sep 17 00:00:00 2001
From: Alberto Mengali
Date: Thu, 24 Aug 2023 20:02:48 +0200
Subject: [PATCH 29/52] switch any-point:coarse to pointer:coarse (#2631)
The current CSS keeps the hide cell buttons always on when Pluto is opened on laptops which also have a touchscreen.
Using pointer instead of any-pointer should only make the button always on only on devices that have the touchscreen as primary.
---
frontend/editor.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/editor.css b/frontend/editor.css
index 73964d87d5..8a08894962 100644
--- a/frontend/editor.css
+++ b/frontend/editor.css
@@ -1520,7 +1520,7 @@ pluto-input > button > span {
}
}
-@media screen and (any-pointer: coarse) {
+@media screen and (pointer: coarse) {
pluto-cell > button,
pluto-cell > pluto-runarea {
opacity: 0;
From 137420c7c8a7187a426503f2cf957c29086c9868 Mon Sep 17 00:00:00 2001
From: jbrea
Date: Sun, 27 Aug 2023 16:51:17 +0200
Subject: [PATCH 30/52] fix EmbeddableDisplay (#2632)
* fix EmbeddableDisplay
* pass cell_id to logs
* Revert "pass cell_id to logs"
This reverts commit 7829d4c0ff1777e7df3dc787d42d0130bf9ca487.
* Add test for `embed_display`
---------
Co-authored-by: Paul
---
src/runner/PlutoRunner.jl | 2 +-
test/RichOutput.jl | 15 +++++++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/src/runner/PlutoRunner.jl b/src/runner/PlutoRunner.jl
index bbd1800e00..f1e67a5236 100644
--- a/src/runner/PlutoRunner.jl
+++ b/src/runner/PlutoRunner.jl
@@ -2382,7 +2382,7 @@ function Base.show(io::IO, m::MIME"text/html", e::EmbeddableDisplay)
// see https://plutocon2021-demos.netlify.app/fonsp%20%E2%80%94%20javascript%20inside%20pluto to learn about the techniques used in this script
- const body = $(publish_to_js(body, e.script_id));
+ const body = $(PublishedToJavascript(body));
const mime = "$(string(mime))";
const create_new = this == null || this._mime !== mime;
diff --git a/test/RichOutput.jl b/test/RichOutput.jl
index 1132472112..69e1eccff9 100644
--- a/test/RichOutput.jl
+++ b/test/RichOutput.jl
@@ -229,6 +229,21 @@ import Pluto: update_run!, WorkspaceManager, ClientSession, ServerSession, Noteb
end
end
+ @testset "embed_display" begin
+ 🍭.options.evaluation.workspace_use_distributed = false
+ notebook = Notebook([
+ Cell("x = randn(10)"),
+ Cell(raw"md\"x = $(embed_display(x))\"")
+ ])
+ update_run!(🍭, notebook, notebook.cells)
+
+ @test notebook.cells[1] |> noerror
+ @test notebook.cells[2] |> noerror
+
+ @test notebook.cells[2].output.body isa String
+ @test occursin("getPublishedObject", notebook.cells[2].output.body)
+ end
+
@testset "Table viewer" begin
🍭.options.evaluation.workspace_use_distributed = true
notebook = Notebook([
From fb1f1a5dc4e7ae35f6611c7f09203b87c57868ce Mon Sep 17 00:00:00 2001
From: Paul Berg
Date: Tue, 29 Aug 2023 23:28:46 +0200
Subject: [PATCH 31/52] Limit prefix length (#2636)
---
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 f1e67a5236..030de8cd48 100644
--- a/src/runner/PlutoRunner.jl
+++ b/src/runner/PlutoRunner.jl
@@ -1422,7 +1422,7 @@ function array_prefix(@nospecialize(x::Vector{<:Any}))
end
function array_prefix(@nospecialize(x))
- original = sprint(Base.showarg, x, false)
+ original = sprint(Base.showarg, x, false; context=:limit => true)
string(lstrip(original, ':'), ": ")::String
end
From 17d105b60282038f2194862109fab2afc5730a13 Mon Sep 17 00:00:00 2001
From: Vlad Flore
Date: Sun, 3 Sep 2023 17:29:24 +0200
Subject: [PATCH 32/52] change selector so that CSS applies correctly (#2637)
---
frontend/editor.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/editor.css b/frontend/editor.css
index 8a08894962..6cf6bf7617 100644
--- a/frontend/editor.css
+++ b/frontend/editor.css
@@ -2665,7 +2665,7 @@ footer button {
font-size: 0.75rem;
}
-#info {
+footer #info {
max-width: 9400px;
margin: 0 auto;
padding: 1rem;
From 5b96b85f1a2500dcefb0a739399f77edf5ae78d6 Mon Sep 17 00:00:00 2001
From: Fons van der Plas
Date: Wed, 6 Sep 2023 22:36:00 +0200
Subject: [PATCH 33/52] Warn when including PasswordField content in an html
export
---
frontend/components/ExportBanner.js | 66 +++++++++++++++++++----------
1 file changed, 43 insertions(+), 23 deletions(-)
diff --git a/frontend/components/ExportBanner.js b/frontend/components/ExportBanner.js
index 0c1bac5383..2188763d9b 100644
--- a/frontend/components/ExportBanner.js
+++ b/frontend/components/ExportBanner.js
@@ -26,8 +26,23 @@ const Square = ({ fill }) => html`
`
-//@ts-ignore
-window.enable_secret_pluto_recording = true
+export const WarnForVisisblePasswords = () => {
+ if (
+ Array.from(document.querySelectorAll("bond")).some((bond_el) =>
+ Array.from(bond_el.querySelectorAll(`input[type="password"]`)).some((input) => {
+ // @ts-ignore
+ if (input?.value !== "") {
+ input.scrollIntoView()
+ return true
+ }
+ })
+ )
+ ) {
+ alert(
+ "Warning: this notebook includes a password input with something typed in it. The contents of this password field will be included in the exported file in an unsafe way. \n\nClear the password field and export again to avoid this problem."
+ )
+ }
+}
export const ExportBanner = ({ notebook_id, onClose, notebookfile_url, notebookexport_url, start_recording }) => {
// @ts-ignore
@@ -49,7 +64,16 @@ export const ExportBanner = ({ notebook_id, onClose, notebookfile_url, notebooke
<${Triangle} fill="#a270ba" /> Notebook fileDownload a copy of the .jl script.
- exportNotebook(e, 1)}>
+ {
+ WarnForVisisblePasswords()
+ exportNotebook(e, 1)
+ }}
+ >
<${Square} fill="#E86F51" /> Static HTMLAn .html file for your web page, or to share online.
@@ -57,26 +81,22 @@ export const ExportBanner = ({ notebook_id, onClose, notebookfile_url, notebooke
<${Square} fill="#619b3d" /> PDFA static .pdf file for print or email.
- ${
- //@ts-ignore
- window.enable_secret_pluto_recording
- ? html`
-