Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update codemirror and support try-catch-else #2673

Merged
merged 6 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading