braces.ts 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. const BRACES = {
  2. '[': ']',
  3. '{': '}',
  4. '(': ')',
  5. };
  6. const NON_SELECTOR_SPACE_REGEXP = / (?![^}]+})/;
  7. export default function BracesPlugin() {
  8. return {
  9. onKeyDown(event, change) {
  10. const { value } = change;
  11. if (!value.isCollapsed) {
  12. return undefined;
  13. }
  14. switch (event.key) {
  15. case '{':
  16. case '[': {
  17. event.preventDefault();
  18. // Insert matching braces
  19. change
  20. .insertText(`${event.key}${BRACES[event.key]}`)
  21. .move(-1)
  22. .focus();
  23. return true;
  24. }
  25. case '(': {
  26. event.preventDefault();
  27. const text = value.anchorText.text;
  28. const offset = value.anchorOffset;
  29. const delimiterIndex = text.slice(offset).search(NON_SELECTOR_SPACE_REGEXP);
  30. const length = delimiterIndex > -1 ? delimiterIndex + offset : text.length;
  31. const forward = length - offset;
  32. // Insert matching braces
  33. change
  34. .insertText(event.key)
  35. .move(forward)
  36. .insertText(BRACES[event.key])
  37. .move(-1 - forward)
  38. .focus();
  39. return true;
  40. }
  41. case 'Backspace': {
  42. const text = value.anchorText.text;
  43. const offset = value.anchorOffset;
  44. const previousChar = text[offset - 1];
  45. const nextChar = text[offset];
  46. if (BRACES[previousChar] && BRACES[previousChar] === nextChar) {
  47. event.preventDefault();
  48. // Remove closing brace if directly following
  49. change
  50. .deleteBackward()
  51. .deleteForward()
  52. .focus();
  53. return true;
  54. }
  55. }
  56. default: {
  57. break;
  58. }
  59. }
  60. return undefined;
  61. },
  62. };
  63. }