Fix bug when pressing C-a in an empty block and then pasting text

Before this fix it would cause part of the block separator to be overwritten causing the block separation to break.
This commit is contained in:
Jonatan Heyman 2023-12-07 19:07:35 +01:00
parent 08a28d037e
commit a5068c0913
6 changed files with 115 additions and 39 deletions

View File

@ -8,6 +8,7 @@ import { IterMode } from "@lezer/common";
import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js";
import { SelectionChangeEvent } from "../event.js"
import { mathBlock } from "./math.js"
import { emptyBlockSelected } from "./select-all.js";
// tracks the size of the first delimiter
@ -325,5 +326,6 @@ export const noteBlockExtension = (editor) => {
preventSelectionBeforeFirstBlock,
emitCursorChange(editor),
mathBlock,
emptyBlockSelected,
]
}

View File

@ -1,13 +1,10 @@
import { EditorSelection } from "@codemirror/state"
import {
selectAll as defaultSelectAll,
moveLineUp as defaultMoveLineUp,
} from "@codemirror/commands"
import { heynoteEvent, LANGUAGE_CHANGE, CURRENCIES_LOADED } from "../annotation.js";
import { blockState, getActiveNoteBlock, getNoteBlockFromPos } from "./block"
import { moveLineDown, moveLineUp } from "./move-lines.js";
import { selectAll } from "./select-all.js";
export { moveLineDown, moveLineUp }
export { moveLineDown, moveLineUp, selectAll }
export const insertNewBlockAtCursor = ({ state, dispatch }) => {
@ -50,37 +47,6 @@ export const addNewBlockAfterCurrent = ({ state, dispatch }) => {
return true;
}
export const selectAll = ({ state, dispatch }) => {
const range = state.selection.asSingle().ranges[0]
const block = getActiveNoteBlock(state)
// handle empty blocks separately
if (block.content.from === block.content.to) {
// check if C-a has already been pressed
if (range.from === block.content.from-1 && range.to === block.content.to) {
return defaultSelectAll({state, dispatch})
}
dispatch(state.update({
selection: {anchor: block.content.from-1, head: block.content.to},
userEvent: "select"
}))
return true
}
// check if all the text of the note is already selected, in which case we want to select all the text of the whole document
if (range.from === block.content.from && range.to === block.content.to) {
return defaultSelectAll({state, dispatch})
}
dispatch(state.update({
selection: {anchor: block.content.from, head: block.content.to},
userEvent: "select"
}))
return true
}
export function changeLanguageTo(state, dispatch, block, language, auto) {
if (state.readOnly)
return false

View File

@ -0,0 +1,102 @@
import { ViewPlugin, Decoration } from "@codemirror/view"
import { StateField, StateEffect, RangeSetBuilder } from "@codemirror/state"
import { selectAll as defaultSelectAll } from "@codemirror/commands"
import { getActiveNoteBlock } from "./block"
/**
* When the user presses C-a, we want to first select the whole block. But if the whole block is already selected,
* we want to instead select the whole document. This doesn't work for empty block, since the whole block is already
* selected (since it's empty). Therefore we use a StateField to keep track of whether the empty block is selected,
* and add a manual line decoration to visually indicate that the empty block is selected.
*/
export const emptyBlockSelected = StateField.define({
create: () => {
return null
},
update(value, tr) {
if (tr.selection) {
// if selection changes, reset the state
return null
} else {
for (let e of tr.effects) {
if (e.is(setEmptyBlockSelected)) {
// toggle the state to true
return e.value
}
}
}
},
provide() {
return ViewPlugin.fromClass(class {
constructor(view) {
this.decorations = emptyBlockSelectedDecorations(view)
}
update(update) {
this.decorations = emptyBlockSelectedDecorations(update.view)
}
}, {
decorations: v => v.decorations
})
}
})
/**
* Effect that can be dispatched to set the empty block selected state
*/
const setEmptyBlockSelected = StateEffect.define()
const decoration = Decoration.line({
attributes: {class: "heynote-empty-block-selected"}
})
function emptyBlockSelectedDecorations(view) {
const selectionPos = view.state.field(emptyBlockSelected)
const builder = new RangeSetBuilder()
if (selectionPos) {
const line = view.state.doc.lineAt(selectionPos)
builder.add(line.from, line.from, decoration)
}
return builder.finish()
}
export const selectAll = ({ state, dispatch }) => {
const range = state.selection.asSingle().ranges[0]
const block = getActiveNoteBlock(state)
// handle empty blocks separately
if (block.content.from === block.content.to) {
// check if C-a has already been pressed,
if (state.field(emptyBlockSelected)) {
// if the active block is already marked as selected we want to select the whole buffer
return defaultSelectAll({state, dispatch})
} else if (range.empty) {
// if the empty block is not selected mark it as selected
// the reason we check for range.empty is if there is a an empty block at the end of the document
// and the users presses C-a twice so that the whole buffer gets selected, the active block will
// still be empty but we don't want to mark it as selected
dispatch({
effects: setEmptyBlockSelected.of(block.content.from)
})
}
return true
}
// check if all the text of the note is already selected, in which case we want to select all the text of the whole document
if (range.from === block.content.from && range.to === block.content.to) {
return defaultSelectAll({state, dispatch})
}
dispatch(state.update({
selection: {anchor: block.content.from, head: block.content.to},
userEvent: "select"
}))
return true
}

View File

@ -122,5 +122,5 @@ export const heynoteBase = EditorView.theme({
},
'.heynote-link': {
textDecoration: "underline",
}
},
})

View File

@ -29,8 +29,8 @@ const highlightBackground = 'rgba(255,255,255,0.04)';
const lineNumberColor = 'rgba(255,255,255, 0.15)';
const commentColor = '#888d97';
const matchingBracket = 'rgba(255,255,255,0.1)';
const selection = "#0865a9";
const selectionBlur = "#225377";
const selection = "#0865a9aa";
const selectionBlur = "#225377aa";
const darkTheme = EditorView.theme({
@ -48,6 +48,9 @@ const darkTheme = EditorView.theme({
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
backgroundColor: selection,
},
'.cm-activeLine.heynote-empty-block-selected': {
"background-color": selection,
},
'.cm-panels': {
backgroundColor: "#474747",
color: "#9c9c9c",

View File

@ -49,6 +49,9 @@ const lightTheme = EditorView.theme({
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
background: selection,
},
'.cm-activeLine.heynote-empty-block-selected': {
"background-color": selection,
},
".heynote-blocks-layer .block-even": {
background: "#ffffff",