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

FIX: Ctrl+Shift+K messing up block syntax #114

Closed
wants to merge 3 commits into from
Closed
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
38 changes: 28 additions & 10 deletions src/editor/block/block.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ViewPlugin, EditorView, Decoration, WidgetType, lineNumbers } from "@codemirror/view"
import { layer, RectangleMarker } from "@codemirror/view"
import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet} from "@codemirror/state";
import { EditorState, RangeSetBuilder, StateField, Facet , StateEffect, RangeSet, MapMode} from "@codemirror/state";
import { syntaxTree, ensureSyntaxTree } from "@codemirror/language"
import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js"
import { IterMode } from "@lezer/common";
Expand Down Expand Up @@ -229,20 +229,33 @@ const blockLayer = layer({
const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr) => {
//console.log("change filter!", tr)
const protect = []
if (!tr.annotations.some(a => a.type === heynoteEvent) && firstBlockDelimiterSize) {
if (tr.annotation(heynoteEvent) === undefined && firstBlockDelimiterSize) {
protect.push(0, firstBlockDelimiterSize)
}
// if the transaction is a search and replace, we want to protect all block delimiters
if (tr.annotations.some(a => a.value === "input.replace" || a.value === "input.replace.all")) {
// `isUserEvent` grabs any annotation that is equal to the specified or more specific too
// in this case, it returns true to "input.replace" and "input.replace.all".
if (tr.isUserEvent("input.replace")) {
const blocks = tr.startState.facet(blockState)
blocks.forEach(block => {
protect.push(block.delimiter.from, block.delimiter.to)
})
//console.log("protected ranges:", protect)
}
if (protect.length > 0) {
return protect
return protect;
})

/**
* Fix for the default Codemirror keymap "Ctrl-Shift-K", which deletes
* lines, but breaks HeynoteBlock delimiter syntax when deleting entire blocks
*/
const preventBlockDelimiterDeletion = EditorState.changeFilter.of((tr)=>{
// Regular line deletes dont actually trigger this
if(tr.isUserEvent("delete.line")){
let activeBlock = getActiveNoteBlock(tr.startState);
return [activeBlock.delimiter.from, activeBlock.delimiter.to]
}
return true;
})

/**
Expand All @@ -255,12 +268,16 @@ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) =
tr?.selection?.ranges.forEach(range => {
// change the selection to after the first block if the transaction sets the selection before the first block
if (range && range.from < firstBlockDelimiterSize) {
range.from = firstBlockDelimiterSize
//console.log("changing the from selection to", markerSize)
// `range.to` & `range.from` should be read-only properties, so we need a new range from the old one:
//range.from = firstBlockDelimiterSize
tr.selection.replaceRange(Object.assign(range, {from:firstBlockDelimiterSize}))
//console.log("changing the from selection to", tr.selection)
}
if (range && range.to < firstBlockDelimiterSize) {
range.to = firstBlockDelimiterSize
//console.log("changing the from selection to", markerSize)
// `range.to` & `range.from` should be read-only properties, so we need a new range from the old one:
//range.to = firstBlockDelimiterSize
tr.selection.replaceRange(Object.assign(range, {to:firstBlockDelimiterSize}))
//console.log("changing the from selection to", tr.selection)
}
})
return tr
Expand Down Expand Up @@ -311,7 +328,7 @@ const emitCursorChange = (editor) => ViewPlugin.fromClass(
update(update) {
// if the selection changed or the language changed (can happen without selection change),
// emit a selection change event
const langChange = update.transactions.some(tr => tr.annotations.some(a => a.value == LANGUAGE_CHANGE))
const langChange = update.transactions.some(tr => tr.isUserEvent(LANGUAGE_CHANGE))
if (update.selectionSet || langChange) {
const cursorLine = getBlockLineFromPos(update.state, update.state.selection.main.head)

Expand Down Expand Up @@ -344,5 +361,6 @@ export const noteBlockExtension = (editor) => {
emitCursorChange(editor),
mathBlock,
emptyBlockSelected,
preventBlockDelimiterDeletion
]
}
9 changes: 8 additions & 1 deletion src/editor/copy-paste.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import { setEmacsMarkMode } from "./emacs.js"
const languageTokensMatcher = LANGUAGES.map(l => l.token).join("|")
const blockSeparatorRegex = new RegExp(`\\n∞∞∞(${languageTokensMatcher})(-a)?\\n`, "g")


/**
* Given a `EditorState`, returns a object containing
* the current selection content as a `string`, alongside a array of ranges.
* @param {EditorState} state The state object of the editor.
* @returns {Object} object with:<br><br>
* - `text`: the selection content
* - `ranges`: a array of selection ranges
*/
function copiedRange(state) {
let content = [], ranges = []
for (let range of state.selection.ranges) if (!range.empty) {
Expand Down
7 changes: 6 additions & 1 deletion src/editor/lang-heynote/nested-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import { LANGUAGES } from "../languages.js"

const languageMapping = Object.fromEntries(LANGUAGES.map(l => [l.token, l.parser]))


/**
* Creates a parse wrapper that, after parsing the HeyNote language syntax
* (such as language tokens `NoteDelimiter` and content `NoteContent`) applies
* another parser to the data, attaching the resulting nodes to the syntax tree, with all their props.
* @returns {ParseWrapper} a custom Heynote `ParseWrapper`.
*/
export function configureNesting() {
return parseMixed((node, input) => {
let id = node.type.id
Expand Down
Loading