feat(editor): Improve UX for brace completion from selection (#5024)

 Improve UX for brace completion from selection
This commit is contained in:
Iván Ovejero 2022-12-27 09:21:32 +01:00 committed by GitHub
parent afc529799d
commit 52077e2c45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 23 deletions

View file

@ -39,15 +39,32 @@ export default mixins(expressionManager, workflowHelpers).extend({
}, },
watch: { watch: {
value(newValue) { value(newValue) {
const payload: Record<string, unknown> = {
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 { try {
this.editor?.dispatch({ this.editor?.dispatch(payload);
changes: {
from: 0,
to: this.editor.state.doc.length,
insert: newValue,
},
selection: { anchor: this.cursorPosition, head: this.cursorPosition },
});
} catch (_) { } catch (_) {
// ignore out-of-range selection error on drop // ignore out-of-range selection error on drop
} }

View file

@ -23,19 +23,18 @@ const inputHandler = EditorView.inputHandler.of((view, from, to, insert) => {
view.dispatch(transaction); view.dispatch(transaction);
/** /**
* Customizations to inject whitespace and braces * Customizations to inject whitespace and braces for setup and completion
* for resolvable setup and completion
*/ */
const cursor = view.state.selection.main.head; 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 - 2, cursor) === '{{' &&
view.state.sliceDoc(cursor, cursor + 1) === '}'; view.state.sliceDoc(cursor, cursor + 1) === '}';
if (isSecondBraceForNewExpression) { if (isBraceCompletion) {
view.dispatch({ view.dispatch({
changes: { from: cursor, to: cursor + 2, insert: ' }' }, changes: { from: cursor, to: cursor + 2, insert: ' }' },
selection: { anchor: cursor + 1 }, selection: { anchor: cursor + 1 },
@ -44,28 +43,30 @@ const inputHandler = EditorView.inputHandler.of((view, from, to, insert) => {
return true; 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 - 1, cursor) === '{' &&
view.state.sliceDoc(cursor, cursor + 1) === '}'; view.state.sliceDoc(cursor, cursor + 1) === '}';
if (isFirstBraceForNewExpression) { if (isBraceSetup) {
view.dispatch({ changes: { from: cursor, insert: ' ' } }); view.dispatch({ changes: { from: cursor, insert: ' ' } });
return true; 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 [range] = view.state.selection.ranges;
const openMarkerIndex = doc.lastIndexOf('{', cursor);
const closeMarkerIndex = doc.indexOf('}}', cursor);
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( view.dispatch(
{ changes: { from: openMarkerIndex + 1, insert: ' ' } }, { changes: { from: range.from, insert: ' ' } },
{ changes: { from: closeMarkerIndex, insert: ' ' } }, { changes: { from: range.to, insert: ' ' }, selection: { anchor: range.to, head: range.to } },
); );
return true; return true;