diff --git a/src/editor-model/commands-delete.ts b/src/editor-model/commands-delete.ts index f04ea8dd2..9547652e1 100644 --- a/src/editor-model/commands-delete.ts +++ b/src/editor-model/commands-delete.ts @@ -1,7 +1,7 @@ import { register } from '../editor/commands'; import type { _Model } from './model-private'; import { deleteForward, deleteBackward, deleteRange } from './delete'; -import { wordBoundaryOffset } from './commands'; +import { skip } from './commands'; register( { @@ -12,32 +12,41 @@ register( deleteBackward: (model: _Model): boolean => deleteBackward(model), deleteNextWord: (model: _Model): boolean => model.contentWillChange({ inputType: 'deleteWordForward' }) && - deleteRange( - model, - [model.anchor, wordBoundaryOffset(model, model.position, 'forward')], - 'deleteWordForward' - ), + skip(model, 'forward', { delete: true }), deletePreviousWord: (model: _Model): boolean => model.contentWillChange({ inputType: 'deleteWordBackward' }) && - deleteRange( - model, - [model.anchor, wordBoundaryOffset(model, model.position, 'backward')], - 'deleteWordBackward' - ), - deleteToGroupStart: (model: _Model): boolean => - model.contentWillChange({ inputType: 'deleteSoftLineBackward' }) && - deleteRange( - model, - [model.anchor, model.offsetOf(model.at(model.position).firstSibling)], - 'deleteSoftLineBackward' - ), - deleteToGroupEnd: (model: _Model): boolean => - model.contentWillChange({ inputType: 'deleteSoftLineForward' }) && - deleteRange( - model, - [model.anchor, model.offsetOf(model.at(model.position).lastSibling)], - 'deleteSoftLineForward' - ), + skip(model, 'backward', { delete: true }), + deleteToGroupStart: (model: _Model): boolean => { + if (!model.contentWillChange({ inputType: 'deleteSoftLineBackward' })) + return false; + const pos = model.offsetOf(model.at(model.position).firstSibling); + if (pos === model.position) { + model.announce('plonk'); + return false; + } + + model.deferNotifications( + { content: true, selection: true, type: 'deleteSoftLineBackward' }, + () => model.deleteAtoms([model.anchor, pos]) + ); + model.position = pos; + return true; + }, + deleteToGroupEnd: (model: _Model): boolean => { + if (!model.contentWillChange({ inputType: 'deleteSoftLineForward' })) + return false; + const pos = model.offsetOf(model.at(model.position).lastSibling); + if (pos === model.position) { + model.announce('plonk'); + return false; + } + + model.deferNotifications( + { content: true, selection: true, type: 'deleteSoftLineForward' }, + () => model.deleteAtoms([model.anchor, pos]) + ); + return true; + }, deleteToMathFieldStart: (model: _Model): boolean => model.contentWillChange({ inputType: 'deleteHardLineBackward' }) && deleteRange(model, [model.anchor, 0], 'deleteHardLineBackward'), diff --git a/src/editor-model/commands.ts b/src/editor-model/commands.ts index 41c6485dc..134329172 100644 --- a/src/editor-model/commands.ts +++ b/src/editor-model/commands.ts @@ -9,6 +9,7 @@ import { getCommandSuggestionRange } from '../editor-mathfield/mode-editor-latex import { PromptAtom } from '../atoms/prompt'; import { getLocalDOMRect } from 'editor-mathfield/utils'; import { _Mathfield } from 'editor-mathfield/mathfield-private'; +import { deleteRange } from './delete'; /* * Calculates the offset of the "next word". @@ -116,12 +117,13 @@ export function wordBoundaryOffset( * than the current focus. * If `extend` is true, the selection will be extended. Otherwise, it is * collapsed, then moved. + * If `delete` is true, the skipped range is removed. * @todo array */ export function skip( model: _Model, direction: 'forward' | 'backward', - options?: { extend: boolean } + options?: { extend?: boolean; delete?: boolean } ): boolean { const previousPosition = model.position; @@ -274,16 +276,26 @@ export function skip( model.announce('plonk'); return false; } + model.announce('move', previousPosition); } else { if (offset === model.position) { model.announce('plonk'); return false; } - model.position = offset; + if (options?.delete ?? false) { + if (direction === 'forward') + deleteRange(model, [previousPosition, offset], 'deleteWordForward'); + else { + deleteRange(model, [previousPosition, offset], 'deleteWordBackward'); + model.position = offset; + } + } else { + model.position = offset; + model.announce('move', previousPosition); + } } - model.announce('move', previousPosition); model.mathfield.stopCoalescingUndo(); return true; } diff --git a/src/editor/keybindings-definitions.ts b/src/editor/keybindings-definitions.ts index 2fde1f225..8f6e09a40 100644 --- a/src/editor/keybindings-definitions.ts +++ b/src/editor/keybindings-definitions.ts @@ -13,10 +13,17 @@ export const DEFAULT_KEYBINDINGS: Keybinding[] = [ { key: 'shift+[ArrowDown]', command: 'extendSelectionDownward' }, { key: '[Backspace]', command: 'deleteBackward' }, - { key: 'alt+[Delete]', command: 'deleteBackward' }, { key: '[Delete]', command: 'deleteForward' }, - { key: 'alt+[Backspace]', command: 'deleteForward' }, + { key: 'shift+[Backspace]', command: 'deleteForward' }, + + { key: 'alt+[Backspace]', command: 'deletePreviousWord' }, + { key: 'alt+[Delete]', command: 'deleteNextWord' }, + + { key: 'ctrl+[Backspace]', command: 'deleteToGroupStart' }, + + { key: 'ctrl+[Delete]', command: 'deleteToGroupEnd' }, + { key: 'ctrl+shift+[Backspace]', command: 'deleteToGroupEnd' }, { key: 'alt+[ArrowLeft]', command: 'moveToPreviousWord' }, { key: 'alt+[ArrowRight]', command: 'moveToNextWord' }, @@ -336,16 +343,6 @@ export const DEFAULT_KEYBINDINGS: Keybinding[] = [ ifMode: 'math', command: 'addRowBefore', }, - { - key: 'ctrl+[Backspace]', - ifMode: 'math', - command: 'removeRow', - }, - { - key: 'cmd+[Backspace]', - ifMode: 'math', - command: 'removeRow', - }, // { // key: 'ctrl+[Comma]', @@ -384,6 +381,16 @@ export const DEFAULT_KEYBINDINGS: Keybinding[] = [ ifMode: 'math', command: 'removeColumn', }, + { + key: 'shift+[Delete]', + ifMode: 'math', + command: 'removeRow', + }, + { + key: 'shift+alt+[Backspace]', + ifMode: 'math', + command: 'removeRow', + }, { key: 'alt+[Digit5]',