Skip to content

Commit

Permalink
Merge branch 'main' into slider-server-in-pluto-export-json
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp committed Oct 30, 2023
2 parents 99d2e9b + 42c5258 commit f693e36
Show file tree
Hide file tree
Showing 27 changed files with 2,328 additions and 771 deletions.
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ name = "Pluto"
uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781"
license = "MIT"
authors = ["Fons van der Plas <[email protected]>"]
version = "0.19.29"
version = "0.19.30"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Configurations = "5218b696-f38b-4ac9-8b61-a12ec717816d"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
FuzzyCompletions = "fb4132e2-a121-4a70-b8a1-d5b831dcdcc2"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Expand Down
496 changes: 205 additions & 291 deletions frontend-bundler/package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions frontend/binder.css
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,17 @@ body.wiggle_binder .edit_or_run > button {
.binder_help_text {
--width: min(85vw, 570px);
position: fixed;
top: 5rem;
max-height: calc(100vh - 4rem);
overflow: auto;
width: var(--width);
padding: 16px;
border-radius: 8px;
left: calc(50vw - var(--width) / 2);
background-color: white;
color: black;
color-scheme: light;
box-shadow: 0px 0px 0px 100vmax #0000004a;
font-family: var(--sans-serif-font-stack);
border: 0;
}
.binder_help_text a {
color: black;
Expand Down
41 changes: 41 additions & 0 deletions frontend/common/useDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//@ts-ignore
import dialogPolyfill from "https://cdn.jsdelivr.net/npm/[email protected]/dist/dialog-polyfill.esm.min.js"

import { useEffect, useLayoutEffect, useRef } from "../imports/Preact.js"

/**
* @returns {[import("../imports/Preact.js").Ref<HTMLDialogElement?>, () => void, () => void, () => void]}
*/
export const useDialog = ({ light_dismiss = false } = {}) => {
const dialog_ref = useRef(/** @type {HTMLDialogElement?} */ (null))

useLayoutEffect(() => {
if (dialog_ref.current != null) dialogPolyfill.registerDialog(dialog_ref.current)
}, [dialog_ref.current])

//@ts-ignore
const open = () => dialog_ref.current.showModal()
//@ts-ignore
const close = () => dialog_ref.current.close()
//@ts-ignore
const toggle = () => (dialog_ref.current.open ? dialog_ref.current?.close() : dialog_ref.current?.showModal())

useEffect(() => {
if (light_dismiss) {
const handleclick = (e) => {
if (dialog_ref.current?.open && dialog_ref.current.contains(e.target)) {
close()
// Avoid activating whatever was below
e.stopPropagation()
e.preventDefault()
}
}
document.body.addEventListener("click", handleclick)
return () => {
document.body.removeEventListener("click", handleclick)
}
}
}, [dialog_ref.current])

return [dialog_ref, open, close, toggle]
}
1 change: 1 addition & 0 deletions frontend/components/CellInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { utf8index_to_ut16index } from "../common/UnicodeTools.js"
import { PlutoActionsContext } from "../common/PlutoContext.js"
import { get_selected_doc_from_state } from "./CellInput/LiveDocsFromCursor.js"
import { go_to_definition_plugin, GlobalDefinitionsFacet } from "./CellInput/go_to_definition_plugin.js"
// import { debug_syntax_plugin } from "./CellInput/debug_syntax_plugin.js"

import {
EditorState,
Expand Down
12 changes: 11 additions & 1 deletion frontend/components/CellInput/LiveDocsFromCursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ export let get_selected_doc_from_state = (/** @type {EditorState} */ state, verb
// We're inside a `... = ...` inside the struct
} else if (parents.includes("TypedExpression") && parents.indexOf("TypedExpression") < index_of_struct_in_parents) {
// We're inside a `x::X` inside the struct
} else if (parents.includes("SubtypedExpression") && parents.indexOf("SubtypedExpression") < index_of_struct_in_parents) {
// We're inside `Real` in `struct MyNumber<:Real`
while (parent?.name !== "SubtypedExpression") {
parent = parent.parent
}
const type_node = parent.lastChild
if (type_node.from <= cursor.from && type_node.to >= cursor.to) {
return state.doc.sliceString(type_node.from, type_node.to)
}
} else if (cursor.name === "struct" || cursor.name === "mutable") {
cursor.parent()
cursor.firstChild()
Expand Down Expand Up @@ -235,7 +244,8 @@ export let get_selected_doc_from_state = (/** @type {EditorState} */ state, verb
if (
cursor.name === "Identifier" &&
parent.name === "ArgumentList" &&
(parent.parent.name === "FunctionAssignmentExpression" || parent.parent.name === "FunctionDefinition")
(parent.parent.parent.name === "FunctionAssignmentExpression" ||
parent.parent.name === "FunctionDefinition")
) {
continue
}
Expand Down
67 changes: 46 additions & 21 deletions frontend/components/CellInput/block_matcher_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ import { Decoration } from "../../imports/CodemirrorPlutoSetup.js"
* Also it doesn't do non-matching now, there is just matching or nothing.
*/

function match_try_node(node) {
let try_node = node.parent.firstChild
let possibly_end = node.parent.lastChild
let did_match = possibly_end.name === "end"
if (!did_match) return null

let catch_node = node.parent.getChild("CatchClause")?.firstChild
let else_node = node.parent.getChild("TryElseClause")?.firstChild
let finally_node = node.parent.getChild("FinallyClause")?.firstChild

return [
{ from: try_node.from, to: try_node.to },
catch_node && { from: catch_node.from, to: catch_node.to },
else_node && { from: else_node.from, to: else_node.to },
finally_node && { from: finally_node.from, to: finally_node.to },
{ from: possibly_end.from, to: possibly_end.to },
].filter((x) => x != null)

}

function match_block(node) {
if (node.name === "end") {
if (node.parent.name === "IfStatement") {
Expand Down Expand Up @@ -154,36 +174,24 @@ function match_block(node) {
]
}

if (node.name === "try" || node.name === "catch" || node.name === "finally") {
if (node.name === "catch") node = node.parent
if (node.name === "finally") node = node.parent

let try_node = node.parent.firstChild
let possibly_end = node.parent.lastChild
let did_match = possibly_end.name === "end"
if (!did_match) return null

let catch_node = node.parent.getChild("CatchClause")?.firstChild
let finally_node = node.parent.getChild("FinallyClause")?.firstChild

return [
{ from: try_node.from, to: try_node.to },
catch_node && { from: catch_node.from, to: catch_node.to },
finally_node && { from: finally_node.from, to: finally_node.to },
{ from: possibly_end.from, to: possibly_end.to },
].filter((x) => x != null)
}

if (node.name === "if" || node.name === "else" || node.name === "elseif") {
if (node.name === "if") node = node.parent
if (node.name === "else") node = node.parent
let iselse = false
if (node.name === "else") {
node = node.parent
iselse = true
}
if (node.name === "elseif") node = node.parent.parent

let try_node = node.parent.firstChild
let possibly_end = node.parent.lastChild
let did_match = possibly_end.name === "end"
if (!did_match) return null

if (iselse && try_node.name === "try") {
return match_try_node(node) // try catch else finally end
}

let decorations = []
decorations.push({ from: try_node.from, to: try_node.to })
for (let elseif_clause_node of node.parent.getChildren("ElseifClause")) {
Expand All @@ -199,6 +207,23 @@ function match_block(node) {
return decorations
}

if (node.name === "try"
|| node.name === "catch"
|| node.name === "finally"
|| node.name === "else") {

if (node.name === "catch") node = node.parent
if (node.name === "finally") node = node.parent
if (node.name === "else") node = node.parent

let possibly_end = node.parent.lastChild
let did_match = possibly_end.name === "end"
if (!did_match) return null

return match_try_node(node)
}


return null
}

Expand Down
18 changes: 15 additions & 3 deletions frontend/components/CellInput/pluto_autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ import { open_bottom_right_panel } from "../BottomRightPanel.js"

let { autocompletion, completionKeymap, completionStatus, acceptCompletion } = autocomplete

// Option.source is now the source, we find to find the corresponding ActiveResult
// https://github.com/codemirror/autocomplete/commit/6d9f24115e9357dc31bc265cd3da7ce2287fdcbd
const getActiveResult = (view, source) =>
view.state.field(completionState).active.find(a => a.source == source)

// These should be imported from @codemirror/autocomplete, but they are not exported.
let completionState = autocompletion()[0]
let applyCompletion = (/** @type {EditorView} */ view, option) => {
let apply = option.completion.apply || option.completion.label
let result = option.source
let result = getActiveResult(view, option.source)
if (!result?.from) return
if (typeof apply == "string") {
view.dispatch({
changes: { from: result.from, to: result.to, insert: apply },
Expand Down Expand Up @@ -127,13 +133,19 @@ let update_docs_from_autocomplete_selection = (on_update_doc_query) => {
let text_to_apply = selected_option.completion.apply ?? selected_option.completion.label
if (typeof text_to_apply !== "string") return

const active_result = getActiveResult(update.view, selected_option.source)
if (!active_result?.from) return // not an ActiveResult instance

const from = active_result.from,
to = Math.min(active_result.to, update.state.doc.length)

// Apply completion to state, which will yield us a `Transaction`.
// 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: Math.min(selected_option.source.to, update.state.doc.length),
from,
to,
insert: text_to_apply,
},
})
Expand Down
8 changes: 7 additions & 1 deletion frontend/components/CellOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,14 @@ const execute_scripttags = async ({ root_node, script_nodes, previous_results_ma

if (script_el == undefined) {
script_el = document.createElement("script")
script_el.referrerPolicy = node.referrerPolicy
script_el.crossOrigin = node.crossOrigin
script_el.integrity = node.integrity
script_el.noModule = node.noModule
script_el.nonce = node.nonce
script_el.type = node.type
script_el.src = node.src
script_el.type = node.type === "module" ? "module" : "text/javascript"
// Not copying defer or async because this script is not included in the initial HTML document, so it has no effect.
// @ts-ignore
script_el.pluto_is_loading_me = true
}
Expand Down
43 changes: 12 additions & 31 deletions frontend/components/EditOrRunButton.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import _ from "../imports/lodash.js"
import { BackendLaunchPhase } from "../common/Binder.js"
import { html, useEffect, useState, useRef } from "../imports/Preact.js"
import { html, useEffect, useState, useRef, useLayoutEffect } from "../imports/Preact.js"

import { has_ctrl_or_cmd_pressed } from "../common/KeyboardShortcuts.js"
import { useDialog } from "../common/useDialog.js"

export const RunLocalButton = ({ show, start_local }) => {
//@ts-ignore
Expand Down Expand Up @@ -30,36 +33,14 @@ export const RunLocalButton = ({ show, start_local }) => {
* }} props
* */
export const BinderButton = ({ offer_binder, start_binder, notebookfile, notebook }) => {
const [popupOpen, setPopupOpen] = useState(false)
const [dialog_ref, openModal, closeModal, toggleModal] = useDialog({ light_dismiss: true })

const [showCopyPopup, setShowCopyPopup] = useState(false)
const notebookfile_ref = useRef("")
notebookfile_ref.current = notebookfile ?? ""

//@ts-ignore
window.open_edit_or_run_popup = () => {
setPopupOpen(true)
}

useEffect(() => {
const handlekeyup = (e) => {
e.key === "Escape" && setPopupOpen(false)
}
const handleclick = (e) => {
if (popupOpen && !e.target?.closest(".binder_help_text")) {
setPopupOpen(false)
// Avoid activating whatever was below
e.stopPropagation()
e.preventDefault()
}
}
document.body.addEventListener("keyup", handlekeyup)
document.body.addEventListener("click", handleclick)

return () => {
document.body.removeEventListener("keyup", handlekeyup)
document.body.removeEventListener("click", handleclick)
}
}, [popupOpen])
window.open_edit_or_run_popup = openModal

useEffect(() => {
//@ts-ignore
Expand All @@ -73,19 +54,19 @@ export const BinderButton = ({ offer_binder, start_binder, notebookfile, noteboo

const recommend_download = notebookfile_ref.current.startsWith("data:")
const runtime_str = expected_runtime_str(notebook)

return html`<div class="edit_or_run">
<button
onClick=${(e) => {
toggleModal()
e.stopPropagation()
e.preventDefault()
setPopupOpen(!popupOpen)
}}
>
<b>Edit</b> or <b>run</b> this notebook
</button>
${popupOpen &&
html`<div class="binder_help_text">
<span onClick=${() => setPopupOpen(false)} class="close"></span>
<dialog ref=${dialog_ref} class="binder_help_text">
<span onClick=${closeModal} class="close"></span>
${offer_binder
? html`
<p style="text-align: center;">
Expand Down Expand Up @@ -162,7 +143,7 @@ export const BinderButton = ({ offer_binder, start_binder, notebookfile, noteboo
`}
</li>
</ol>
</div>`}
</dialog>
</div>`
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1510,9 +1510,6 @@ patch: ${JSON.stringify(
/>`)
}
<div class="flex_grow_2"></div>
<button class="toggle_export" title="Export..." onClick=${() => {
this.setState({ export_menu_open: !export_menu_open })
}}><span></span></button>
<div id="process_status">${
status.binder && status.loading
? "Loading binder..."
Expand All @@ -1532,6 +1529,9 @@ patch: ${JSON.stringify(
? html`${restart_button("Run notebook code", true)}`
: null
}</div>
<button class="toggle_export" title="Export..." onClick=${() => {
this.setState({ export_menu_open: !export_menu_open })
}}><span></span></button>
</nav>
</header>
Expand Down
11 changes: 2 additions & 9 deletions frontend/components/FrontmatterInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "https://cdn.jsdelivr.net/gh/fonsp/[email protected]/lib/rebel-tag-in
//@ts-ignore
import dialogPolyfill from "https://cdn.jsdelivr.net/npm/[email protected]/dist/dialog-polyfill.esm.min.js"
import immer from "../imports/immer.js"
import { useDialog } from "../common/useDialog.js"

/**
* @param {{
Expand All @@ -32,15 +33,7 @@ export const FrontMatterInput = ({ remote_frontmatter, set_remote_frontmatter })
})
)

const dialog_ref = useRef(/** @type {HTMLDialogElement?} */ (null))
useLayoutEffect(() => {
dialogPolyfill.registerDialog(dialog_ref.current)
})

//@ts-ignore
const open = () => dialog_ref.current.showModal()
//@ts-ignore
const close = () => dialog_ref.current.close()
const [dialog_ref, open, close, _toggle] = useDialog({ light_dismiss: false })

const cancel = () => {
set_frontmatter(remote_frontmatter ?? {})
Expand Down
Loading

0 comments on commit f693e36

Please sign in to comment.