diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue index 514c112aaeea2..0c24d16bbadff 100644 --- a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue +++ b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorInput.vue @@ -39,15 +39,32 @@ export default mixins(expressionManager, workflowHelpers).extend({ }, watch: { value(newValue) { + const payload: Record = { + changes: { + from: 0, + to: this.editor?.state.doc.length, + insert: newValue, + }, + selection: { anchor: this.cursorPosition, head: this.cursorPosition }, + }; + + /** + * If completion from selection, preserve selection. + */ + if (this.editor) { + const [range] = this.editor.state.selection.ranges; + + const isBraceAutoinsertion = + this.editor.state.sliceDoc(range.from - 1, range.from) === '{' && + this.editor.state.sliceDoc(range.to, range.to + 1) === '}'; + + if (isBraceAutoinsertion) { + payload.selection = { anchor: range.from, head: range.to }; + } + } + try { - this.editor?.dispatch({ - changes: { - from: 0, - to: this.editor.state.doc.length, - insert: newValue, - }, - selection: { anchor: this.cursorPosition, head: this.cursorPosition }, - }); + this.editor?.dispatch(payload); } catch (_) { // ignore out-of-range selection error on drop } diff --git a/packages/editor-ui/src/plugins/codemirror/doubleBraceHandler.ts b/packages/editor-ui/src/plugins/codemirror/doubleBraceHandler.ts index fba5a9ee08b46..631da62d02e01 100644 --- a/packages/editor-ui/src/plugins/codemirror/doubleBraceHandler.ts +++ b/packages/editor-ui/src/plugins/codemirror/doubleBraceHandler.ts @@ -23,19 +23,18 @@ const inputHandler = EditorView.inputHandler.of((view, from, to, insert) => { view.dispatch(transaction); /** - * Customizations to inject whitespace and braces - * for resolvable setup and completion + * Customizations to inject whitespace and braces for setup and completion */ const cursor = view.state.selection.main.head; - // inject whitespace and second brace on completion: {| } -> {{ | }} + // inject whitespace and second brace for brace completion: {| } -> {{ | }} - const isSecondBraceForNewExpression = + const isBraceCompletion = view.state.sliceDoc(cursor - 2, cursor) === '{{' && view.state.sliceDoc(cursor, cursor + 1) === '}'; - if (isSecondBraceForNewExpression) { + if (isBraceCompletion) { view.dispatch({ changes: { from: cursor, to: cursor + 2, insert: ' }' }, selection: { anchor: cursor + 1 }, @@ -44,28 +43,30 @@ const inputHandler = EditorView.inputHandler.of((view, from, to, insert) => { return true; } - // inject whitespace on setup: empty -> {| } + // inject whitespace for brace setup: empty -> {| } - const isFirstBraceForNewExpression = + const isBraceSetup = view.state.sliceDoc(cursor - 1, cursor) === '{' && view.state.sliceDoc(cursor, cursor + 1) === '}'; - if (isFirstBraceForNewExpression) { + if (isBraceSetup) { view.dispatch({ changes: { from: cursor, insert: ' ' } }); return true; } - // when selected, surround with whitespaces on completion: {{abc}} -> {{ abc }} + // inject whitespace for brace completion from selection: {{abc|}} -> {{ abc| }} - const doc = view.state.doc.toString(); - const openMarkerIndex = doc.lastIndexOf('{', cursor); - const closeMarkerIndex = doc.indexOf('}}', cursor); + const [range] = view.state.selection.ranges; - if (openMarkerIndex !== -1 && closeMarkerIndex !== -1) { + const isBraceCompletionFromSelection = + view.state.sliceDoc(range.from - 2, range.from) === '{{' && + view.state.sliceDoc(range.to, range.to + 2) === '}}'; + + if (isBraceCompletionFromSelection) { view.dispatch( - { changes: { from: openMarkerIndex + 1, insert: ' ' } }, - { changes: { from: closeMarkerIndex, insert: ' ' } }, + { changes: { from: range.from, insert: ' ' } }, + { changes: { from: range.to, insert: ' ' }, selection: { anchor: range.to, head: range.to } }, ); return true;