From 21a4e0b6b34cb75be569e68f389a85b423c81d40 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 15 Jan 2024 10:02:38 +0100 Subject: [PATCH] [visual] Skip moving cursor inside argument if there is no decoration (#16365) * [visual] Skip moving cursor inside argument if there is no decoration * Refactor skipAtomicRanges function * rangeSet outside the loop, continue rather than return * use rangeSet.between * prettier GitOrigin-RevId: 85ef817e09ea7eb854cec43cb7866f61b4bfbc21 --- .../extensions/visual/atomic-decorations.ts | 2 + .../visual/select-decorated-argument.ts | 65 +++++++++++++------ .../source-editor/extensions/visual/visual.ts | 2 - 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts b/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts index f9599fd74b..c81901e52d 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts @@ -67,6 +67,7 @@ import { TableRenderingErrorWidget } from './visual-widgets/table-rendering-erro import { GraphicsWidget } from './visual-widgets/graphics' import { InlineGraphicsWidget } from './visual-widgets/inline-graphics' import { PreviewPath } from '../../../../../../types/preview-path' +import { selectDecoratedArgument } from './select-decorated-argument' import { generateTable, ParsedTableData, @@ -1278,6 +1279,7 @@ export const atomicDecorations = (options: Options) => { } }), skipPreambleWithCursor(field), + selectDecoratedArgument(field), ] }, }), diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/select-decorated-argument.ts b/services/web/frontend/js/features/source-editor/extensions/visual/select-decorated-argument.ts index bd5b492278..bc1e788ac1 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/select-decorated-argument.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/select-decorated-argument.ts @@ -1,4 +1,9 @@ -import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state' +import { + EditorSelection, + EditorState, + SelectionRange, + StateField, +} from '@codemirror/state' import { syntaxTree } from '@codemirror/language' import { Tree } from '@lezer/common' import { @@ -6,6 +11,7 @@ import { descendantsOfNodeWithType, } from '../../utils/tree-operations/ancestors' import { getMousedownSelection, selectionIntersects } from './selection' +import { DecorationSet } from '@codemirror/view' /** * A custom extension that updates the selection in a transaction if the mouse pointer was used @@ -13,28 +19,47 @@ import { getMousedownSelection, selectionIntersects } from './selection' * or to drag a range across the whole range of an argument (the selection is placed inside the braces), * when the selection was not already inside the command. */ -export const selectDecoratedArgument = EditorState.transactionFilter.of(tr => { - if (tr.selection && tr.isUserEvent('select.pointer')) { - const tree = syntaxTree(tr.state) - let selection = tr.selection - const mousedownSelection = getMousedownSelection(tr.state) - let replaced = false - for (const [index, range] of selection.ranges.entries()) { - const replacementRange = - selectArgument(tree, range, mousedownSelection, 1) || - selectArgument(tree, range, mousedownSelection, -1) - if (replacementRange) { - selection = selection.replaceRange(replacementRange, index) - replaced = true +export const selectDecoratedArgument = ( + field: StateField<{ decorations: DecorationSet }> +) => + EditorState.transactionFilter.of(tr => { + if (tr.selection && tr.isUserEvent('select.pointer')) { + const tree = syntaxTree(tr.state) + let selection = tr.selection + const mousedownSelection = getMousedownSelection(tr.state) + let replaced = false + const rangeSet = tr.state.field(field, false)?.decorations + for (const [index, range] of selection.ranges.entries()) { + if (rangeSet) { + let isAtomicRange = false + rangeSet.between(range.anchor, range.anchor, (_from, to) => { + if (to > range.anchor) { + isAtomicRange = true + return false + } + }) + + if (isAtomicRange === false) { + // skip since decoration is not covering the selection + continue + } + } + + const replacementRange = + selectArgument(tree, range, mousedownSelection, 1) || + selectArgument(tree, range, mousedownSelection, -1) + if (replacementRange) { + selection = selection.replaceRange(replacementRange, index) + replaced = true + } + } + if (replaced) { + return [tr, { selection }] } } - if (replaced) { - return [tr, { selection }] - } - } - return tr -}) + return tr + }) const selectArgument = ( tree: Tree, diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/visual.ts b/services/web/frontend/js/features/source-editor/extensions/visual/visual.ts index 94769f2e86..b59410ccb2 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/visual.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/visual.ts @@ -16,7 +16,6 @@ import { forceParsing, syntaxTree } from '@codemirror/language' import { hasLanguageLoadedEffect } from '../language' import { restoreScrollPosition } from '../scroll-position' import { listItemMarker } from './list-item-marker' -import { selectDecoratedArgument } from './select-decorated-argument' import { pasteHtml } from './paste-html' import { commandTooltip } from '../command-tooltip' import { tableGeneratorTheme } from './table-generator' @@ -193,7 +192,6 @@ const extension = (options: Options) => [ visualKeymap, commandTooltip, scrollJumpAdjuster, - selectDecoratedArgument, showContentWhenParsed, pasteHtml, tableGeneratorTheme,