Commit 6ac53a13 by David Committed by GitHub

Editor: Ignore closing brace when it was added by editor (#21172)

* Editor: Ignore closing brace when it was added by editor

- brace completion gets annoying if the user still types closing brace
- this change marks automatically added closing braces and when the user
types a closing brace at that position, it will be overridden instead of
added

* Fix label suggestions

* Correct brace behavior, but broken completion

* Rewrite auto-match detection with annotations
parent 74924c82
......@@ -18,7 +18,7 @@ describe('braces', () => {
const value = Plain.deserialize('');
const editor = shallow<Editor>(<Editor value={value} />);
const event = new window.KeyboardEvent('keydown', { key: '(' });
handler(event as Event, editor.instance() as any, nextMock);
expect(handler(event as Event, editor.instance() as any, nextMock)).toBeTruthy();
expect(Plain.serialize(editor.instance().value)).toEqual('()');
});
......@@ -37,4 +37,24 @@ describe('braces', () => {
const handled = handler(event as Event, editor.instance().moveForward(5) as any, nextMock);
expect(handled).toBeFalsy();
});
it('overrides an automatically inserted brace', () => {
const value = Plain.deserialize('');
const editor = shallow<Editor>(<Editor value={value} />);
const opening = new window.KeyboardEvent('keydown', { key: '(' });
expect(handler(opening as Event, editor.instance() as any, nextMock)).toBeTruthy();
const closing = new window.KeyboardEvent('keydown', { key: ')' });
expect(handler(closing as Event, editor.instance() as any, nextMock)).toBeTruthy();
expect(Plain.serialize(editor.instance().value)).toEqual('()');
});
it.skip('does not override manually inserted braces', () => {
const value = Plain.deserialize('');
const editor = shallow<Editor>(<Editor value={value} />);
const event1 = new window.KeyboardEvent('keydown', { key: ')' });
expect(handler(event1 as Event, editor.instance() as any, nextMock)).toBeFalsy();
const event2 = new window.KeyboardEvent('keydown', { key: ')' });
expect(handler(event2 as Event, editor.instance().moveBackward(1) as any, nextMock)).toBeFalsy();
expect(Plain.serialize(editor.instance().value)).toEqual('))');
});
});
import { Plugin } from '@grafana/slate-react';
import { Editor as CoreEditor } from 'slate';
import { Editor as CoreEditor, Annotation } from 'slate';
const BRACES: any = {
'[': ']',
......@@ -7,6 +7,8 @@ const BRACES: any = {
'(': ')',
};
const MATCH_MARK = 'brace_match';
export function BracesPlugin(): Plugin {
return {
onKeyDown(event: Event, editor: CoreEditor, next: Function) {
......@@ -17,7 +19,6 @@ export function BracesPlugin(): Plugin {
case '(':
case '{':
case '[': {
keyEvent.preventDefault();
const {
start: { offset: startOffset, key: startKey },
end: { offset: endOffset, key: endKey },
......@@ -27,21 +28,67 @@ export function BracesPlugin(): Plugin {
// If text is selected, wrap selected text in parens
if (value.selection.isExpanded) {
keyEvent.preventDefault();
editor
.insertTextByKey(startKey, startOffset, keyEvent.key)
.insertTextByKey(endKey, endOffset + 1, BRACES[keyEvent.key])
.moveEndBackward(1);
return true;
} else if (
// Insert matching brace when there is no input after caret
focusOffset === text.length ||
text[focusOffset] === ' ' ||
Object.values(BRACES).includes(text[focusOffset])
) {
editor.insertText(`${keyEvent.key}${BRACES[keyEvent.key]}`).moveBackward(1);
} else {
editor.insertText(keyEvent.key);
keyEvent.preventDefault();
const complement = BRACES[keyEvent.key];
const matchAnnotation = {
key: `${MATCH_MARK}-${Date.now()}`,
type: `${MATCH_MARK}-${complement}`,
anchor: {
key: startKey,
offset: startOffset,
object: 'point',
},
focus: {
key: endKey,
offset: endOffset + 1,
object: 'point',
},
object: 'annotation',
} as Annotation;
editor
.insertText(keyEvent.key)
.insertText(complement)
.addAnnotation(matchAnnotation)
.moveBackward(1);
return true;
}
break;
}
return true;
case ')':
case '}':
case ']': {
const text = value.anchorText.text;
const offset = value.selection.anchor.offset;
const nextChar = text[offset];
// Handle closing brace when it's already the next character
const complement = keyEvent.key;
const annotationType = `${MATCH_MARK}-${complement}`;
const annotation = value.annotations.find(
a => a?.type === annotationType && a.anchor.key === value.anchorText.key
);
if (annotation && nextChar === complement && !value.selection.isExpanded) {
keyEvent.preventDefault();
editor
.moveFocusForward(1)
.removeAnnotation(annotation)
.moveAnchorForward(1);
return true;
}
break;
}
case 'Backspace': {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment