Skip to content

Commit

Permalink
Update codemirror and support try-catch-else (#2673)
Browse files Browse the repository at this point in the history
* update codemirror

* support try-catch-else in ExpressionExplorer

* Update ExpressionExplorer.jl

* oops

* Update ExpressionExplorer.jl

* Update ExpressionExplorer.jl
  • Loading branch information
Pangoraw authored Oct 23, 2023
1 parent 487755a commit a5feb55
Show file tree
Hide file tree
Showing 8 changed files with 1,767 additions and 242 deletions.
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

0 comments on commit a5feb55

Please sign in to comment.