@neo4j-cypher/react-codemirror 2.0.0-next.3 → 2.0.0-next.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +257 -0
  2. package/README.md +3 -2
  3. package/dist/{types → src}/CypherEditor.d.ts +81 -3
  4. package/dist/src/CypherEditor.js +336 -0
  5. package/dist/src/CypherEditor.js.map +1 -0
  6. package/dist/src/CypherEditor.test.js +154 -0
  7. package/dist/src/CypherEditor.test.js.map +1 -0
  8. package/dist/src/constants.d.ts +1 -0
  9. package/dist/src/constants.js +2 -0
  10. package/dist/src/constants.js.map +1 -0
  11. package/dist/src/e2e_tests/autoCompletion.spec.js +332 -0
  12. package/dist/src/e2e_tests/autoCompletion.spec.js.map +1 -0
  13. package/dist/src/e2e_tests/configuration.spec.js +83 -0
  14. package/dist/src/e2e_tests/configuration.spec.js.map +1 -0
  15. package/dist/src/e2e_tests/debounce.spec.js +66 -0
  16. package/dist/src/e2e_tests/debounce.spec.js.map +1 -0
  17. package/dist/{types/e2e_tests/e2e-utils.d.ts → src/e2e_tests/e2eUtils.d.ts} +2 -0
  18. package/dist/src/e2e_tests/e2eUtils.js +79 -0
  19. package/dist/src/e2e_tests/e2eUtils.js.map +1 -0
  20. package/dist/src/e2e_tests/extraKeybindings.spec.js +43 -0
  21. package/dist/src/e2e_tests/extraKeybindings.spec.js.map +1 -0
  22. package/dist/src/e2e_tests/historyNavigation.spec.js +227 -0
  23. package/dist/src/e2e_tests/historyNavigation.spec.js.map +1 -0
  24. package/dist/src/e2e_tests/performanceTest.spec.d.ts +6 -0
  25. package/dist/src/e2e_tests/performanceTest.spec.js +97 -0
  26. package/dist/src/e2e_tests/performanceTest.spec.js.map +1 -0
  27. package/dist/src/e2e_tests/sanityChecks.spec.js +53 -0
  28. package/dist/src/e2e_tests/sanityChecks.spec.js.map +1 -0
  29. package/dist/src/e2e_tests/signatureHelp.spec.js +228 -0
  30. package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -0
  31. package/dist/src/e2e_tests/snippets.spec.js +62 -0
  32. package/dist/src/e2e_tests/snippets.spec.js.map +1 -0
  33. package/dist/src/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
  34. package/dist/src/e2e_tests/syntaxHighlighting.spec.js +90 -0
  35. package/dist/src/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
  36. package/dist/src/e2e_tests/syntaxValidation.spec.d.ts +1 -0
  37. package/dist/src/e2e_tests/syntaxValidation.spec.js +126 -0
  38. package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -0
  39. package/dist/src/historyNavigation.js +163 -0
  40. package/dist/src/historyNavigation.js.map +1 -0
  41. package/dist/{types → src}/icons.d.ts +1 -1
  42. package/dist/src/icons.js +62 -0
  43. package/dist/src/icons.js.map +1 -0
  44. package/dist/src/index.d.ts +4 -0
  45. package/dist/src/index.js +5 -0
  46. package/dist/src/index.js.map +1 -0
  47. package/dist/src/lang-cypher/autocomplete.d.ts +6 -0
  48. package/dist/src/lang-cypher/autocomplete.js +113 -0
  49. package/dist/src/lang-cypher/autocomplete.js.map +1 -0
  50. package/dist/{types → src}/lang-cypher/constants.d.ts +11 -0
  51. package/dist/src/lang-cypher/constants.js +69 -0
  52. package/dist/src/lang-cypher/constants.js.map +1 -0
  53. package/dist/src/lang-cypher/contants.test.d.ts +1 -0
  54. package/dist/src/lang-cypher/contants.test.js +103 -0
  55. package/dist/src/lang-cypher/contants.test.js.map +1 -0
  56. package/dist/src/lang-cypher/createCypherTheme.js +183 -0
  57. package/dist/src/lang-cypher/createCypherTheme.js.map +1 -0
  58. package/dist/src/lang-cypher/langCypher.d.ts +13 -0
  59. package/dist/src/lang-cypher/langCypher.js +23 -0
  60. package/dist/src/lang-cypher/langCypher.js.map +1 -0
  61. package/dist/src/lang-cypher/lintWorker.mjs +2022 -0
  62. package/dist/src/lang-cypher/parser-adapter.d.ts +19 -0
  63. package/dist/src/lang-cypher/parser-adapter.js +113 -0
  64. package/dist/src/lang-cypher/parser-adapter.js.map +1 -0
  65. package/dist/src/lang-cypher/signatureHelp.d.ts +4 -0
  66. package/dist/src/lang-cypher/signatureHelp.js +109 -0
  67. package/dist/src/lang-cypher/signatureHelp.js.map +1 -0
  68. package/dist/{types/lang-cypher/syntax-validation.d.ts → src/lang-cypher/syntaxValidation.d.ts} +2 -1
  69. package/dist/src/lang-cypher/syntaxValidation.js +57 -0
  70. package/dist/src/lang-cypher/syntaxValidation.js.map +1 -0
  71. package/dist/src/lang-cypher/themeIcons.js +22 -0
  72. package/dist/src/lang-cypher/themeIcons.js.map +1 -0
  73. package/dist/src/lang-cypher/utils.d.ts +2 -0
  74. package/dist/src/lang-cypher/utils.js +10 -0
  75. package/dist/src/lang-cypher/utils.js.map +1 -0
  76. package/dist/src/ndlTokensCopy.d.ts +570 -0
  77. package/dist/src/ndlTokensCopy.js +571 -0
  78. package/dist/src/ndlTokensCopy.js.map +1 -0
  79. package/dist/src/ndlTokensCopy.test.d.ts +1 -0
  80. package/dist/src/ndlTokensCopy.test.js +12 -0
  81. package/dist/src/ndlTokensCopy.test.js.map +1 -0
  82. package/dist/src/neo4jSetup.d.ts +6 -0
  83. package/dist/src/neo4jSetup.js +120 -0
  84. package/dist/src/neo4jSetup.js.map +1 -0
  85. package/dist/src/richClipboardCopier.d.ts +4 -0
  86. package/dist/src/richClipboardCopier.js +78 -0
  87. package/dist/src/richClipboardCopier.js.map +1 -0
  88. package/dist/src/richClipboardCopier.test.d.ts +1 -0
  89. package/dist/src/richClipboardCopier.test.js +53 -0
  90. package/dist/src/richClipboardCopier.test.js.map +1 -0
  91. package/dist/{types → src}/themes.d.ts +1 -1
  92. package/dist/src/themes.js +93 -0
  93. package/dist/src/themes.js.map +1 -0
  94. package/dist/tsconfig.tsbuildinfo +1 -0
  95. package/package.json +43 -41
  96. package/src/CypherEditor.test.tsx +204 -0
  97. package/src/CypherEditor.tsx +316 -42
  98. package/src/constants.ts +1 -0
  99. package/src/e2e_tests/autoCompletion.spec.tsx +571 -0
  100. package/src/e2e_tests/configuration.spec.tsx +111 -0
  101. package/src/e2e_tests/debounce.spec.tsx +106 -0
  102. package/src/e2e_tests/{e2e-utils.ts → e2eUtils.ts} +41 -3
  103. package/src/e2e_tests/{extra-keybindings.spec.tsx → extraKeybindings.spec.tsx} +1 -3
  104. package/src/e2e_tests/{history-navigation.spec.tsx → historyNavigation.spec.tsx} +137 -18
  105. package/src/e2e_tests/performanceTest.spec.tsx +163 -0
  106. package/src/e2e_tests/{sanity-checks.spec.tsx → sanityChecks.spec.tsx} +7 -22
  107. package/src/e2e_tests/signatureHelp.spec.tsx +444 -0
  108. package/src/e2e_tests/snippets.spec.tsx +92 -0
  109. package/src/e2e_tests/{syntax-highlighting.spec.tsx → syntaxHighlighting.spec.tsx} +26 -24
  110. package/src/e2e_tests/syntaxValidation.spec.tsx +259 -0
  111. package/src/{history-navigation.ts → historyNavigation.ts} +1 -1
  112. package/src/icons.ts +3 -0
  113. package/src/index.ts +2 -2
  114. package/src/lang-cypher/autocomplete.ts +99 -18
  115. package/src/lang-cypher/constants.ts +27 -0
  116. package/src/lang-cypher/contants.test.ts +6 -2
  117. package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +45 -2
  118. package/src/lang-cypher/langCypher.ts +42 -0
  119. package/src/lang-cypher/lintWorker.mjs +2022 -0
  120. package/src/lang-cypher/parser-adapter.ts +145 -0
  121. package/src/lang-cypher/signatureHelp.ts +151 -0
  122. package/src/lang-cypher/syntaxValidation.ts +72 -0
  123. package/src/lang-cypher/utils.ts +9 -0
  124. package/src/{ndl-tokens-copy.test.ts → ndlTokensCopy.test.ts} +2 -1
  125. package/src/ndlTokensCopy.ts +570 -0
  126. package/src/{neo4j-setup.tsx → neo4jSetup.tsx} +78 -17
  127. package/src/richClipboardCopier.test.ts +65 -0
  128. package/src/richClipboardCopier.ts +99 -0
  129. package/src/themes.ts +45 -70
  130. package/src/viteEnv.d.ts +1 -0
  131. package/dist/cjs/index.cjs +0 -1440
  132. package/dist/cjs/index.cjs.map +0 -7
  133. package/dist/esm/index.mjs +0 -1463
  134. package/dist/esm/index.mjs.map +0 -7
  135. package/dist/types/e2e_tests/mock-data.d.ts +0 -3779
  136. package/dist/types/index.d.ts +0 -4
  137. package/dist/types/lang-cypher/ParserAdapter.d.ts +0 -14
  138. package/dist/types/lang-cypher/autocomplete.d.ts +0 -3
  139. package/dist/types/lang-cypher/lang-cypher.d.ts +0 -7
  140. package/dist/types/ndl-tokens-copy.d.ts +0 -379
  141. package/dist/types/neo4j-setup.d.ts +0 -2
  142. package/dist/types/tsconfig.tsbuildinfo +0 -1
  143. package/src/e2e_tests/auto-completion.spec.tsx +0 -232
  144. package/src/e2e_tests/mock-data.ts +0 -4310
  145. package/src/e2e_tests/performance-test.spec.tsx +0 -71
  146. package/src/e2e_tests/syntax-validation.spec.tsx +0 -156
  147. package/src/lang-cypher/ParserAdapter.ts +0 -92
  148. package/src/lang-cypher/lang-cypher.ts +0 -32
  149. package/src/lang-cypher/syntax-validation.ts +0 -24
  150. package/src/ndl-tokens-copy.ts +0 -379
  151. /package/dist/{types/e2e_tests/auto-completion.spec.d.ts → src/CypherEditor.test.d.ts} +0 -0
  152. /package/dist/{types/e2e_tests/extra-keybindings.spec.d.ts → src/e2e_tests/autoCompletion.spec.d.ts} +0 -0
  153. /package/dist/{types/e2e_tests/history-navigation.spec.d.ts → src/e2e_tests/configuration.spec.d.ts} +0 -0
  154. /package/dist/{types/e2e_tests/performance-test.spec.d.ts → src/e2e_tests/debounce.spec.d.ts} +0 -0
  155. /package/dist/{types/e2e_tests/sanity-checks.spec.d.ts → src/e2e_tests/extraKeybindings.spec.d.ts} +0 -0
  156. /package/dist/{types/e2e_tests/syntax-highlighting.spec.d.ts → src/e2e_tests/historyNavigation.spec.d.ts} +0 -0
  157. /package/dist/{types/e2e_tests/syntax-validation.spec.d.ts → src/e2e_tests/sanityChecks.spec.d.ts} +0 -0
  158. /package/dist/{types/lang-cypher/contants.test.d.ts → src/e2e_tests/signatureHelp.spec.d.ts} +0 -0
  159. /package/dist/{types/ndl-tokens-copy.test.d.ts → src/e2e_tests/snippets.spec.d.ts} +0 -0
  160. /package/dist/{types/history-navigation.d.ts → src/historyNavigation.d.ts} +0 -0
  161. /package/dist/{types/lang-cypher/create-cypher-theme.d.ts → src/lang-cypher/createCypherTheme.d.ts} +0 -0
  162. /package/dist/{types/lang-cypher/theme-icons.d.ts → src/lang-cypher/themeIcons.d.ts} +0 -0
  163. /package/src/lang-cypher/{theme-icons.ts → themeIcons.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { insertNewline } from '@codemirror/commands';
1
2
  import {
2
3
  Annotation,
3
4
  Compartment,
@@ -9,18 +10,24 @@ import {
9
10
  KeyBinding,
10
11
  keymap,
11
12
  lineNumbers,
13
+ placeholder,
12
14
  ViewUpdate,
13
15
  } from '@codemirror/view';
14
- import type { DbSchema } from '@neo4j-cypher/language-support';
16
+ import { formatQuery, type DbSchema } from '@neo4j-cypher/language-support';
17
+ import debounce from 'lodash.debounce';
15
18
  import { Component, createRef } from 'react';
19
+ import { DEBOUNCE_TIME } from './constants';
16
20
  import {
17
21
  replaceHistory,
18
22
  replMode as historyNavigation,
19
- } from './history-navigation';
20
- import { cypher, CypherConfig } from './lang-cypher/lang-cypher';
21
- import { basicNeo4jSetup } from './neo4j-setup';
23
+ } from './historyNavigation';
24
+ import { cypher, CypherConfig } from './lang-cypher/langCypher';
25
+ import { cleanupWorkers } from './lang-cypher/syntaxValidation';
26
+ import { basicNeo4jSetup } from './neo4jSetup';
22
27
  import { getThemeExtension } from './themes';
28
+ import { richClipboardCopier } from './richClipboardCopier';
23
29
 
30
+ type DomEventHandlers = Parameters<typeof EditorView.domEventHandlers>[0];
24
31
  export interface CypherEditorProps {
25
32
  /**
26
33
  * The prompt to show on single line editors
@@ -40,7 +47,14 @@ export interface CypherEditorProps {
40
47
  */
41
48
  onExecute?: (cmd: string) => void;
42
49
  /**
43
- * The editor history navigateable via up/down arrow keys. Order newest to oldest.
50
+ * If true, pressing enter will add a new line to the editor and cmd/ctrl + enter will execute the query.
51
+ * Otherwise pressing enter on a single line will execute the query.
52
+ *
53
+ * @default false
54
+ */
55
+ newLineOnEnter?: boolean;
56
+ /**
57
+ * The editor history navigable via up/down arrow keys. Order newest to oldest.
44
58
  * Add to this list with the `onExecute` callback for REPL style history.
45
59
  */
46
60
  history?: string[];
@@ -57,6 +71,10 @@ export interface CypherEditorProps {
57
71
  * @default false
58
72
  */
59
73
  autofocus?: boolean;
74
+ /**
75
+ * Where to place the cursor in the query. Cannot be enabled at the same time than autofocus
76
+ */
77
+ offset?: number;
60
78
  /**
61
79
  * Whether the editor should wrap lines.
62
80
  *
@@ -69,6 +87,20 @@ export interface CypherEditorProps {
69
87
  * @default true
70
88
  */
71
89
  lint?: boolean;
90
+ /**
91
+ * Whether the signature help tooltip should be shown below the text.
92
+ * If false, it will be shown above.
93
+ *
94
+ * @default true
95
+ */
96
+ showSignatureTooltipBelow?: boolean;
97
+ /**
98
+ * Internal feature flags for the editor. Don't use in production
99
+ *
100
+ */
101
+ featureFlags?: {
102
+ consoleCommands?: boolean;
103
+ };
72
104
  /**
73
105
  * The schema to use for autocompletion and linting.
74
106
  *
@@ -96,32 +128,158 @@ export interface CypherEditorProps {
96
128
  * @param {ViewUpdate} viewUpdate - the view update from codemirror
97
129
  */
98
130
  onChange?(value: string, viewUpdate: ViewUpdate): void;
131
+
132
+ /**
133
+ * Map of event handlers to add to the editor.
134
+ *
135
+ * Note that the props are compared by reference, meaning object defined inline
136
+ * will cause the editor to re-render (much like the style prop does in this example:
137
+ * <div style={{}} />
138
+ *
139
+ * Memoize the object if you want/need to avoid this.
140
+ *
141
+ * @example
142
+ * // listen to blur events
143
+ * <CypherEditor domEventHandlers={{blur: () => console.log("blur event fired")}} />
144
+ */
145
+ domEventHandlers?: DomEventHandlers;
146
+ /**
147
+ * Placeholder text to display when the editor is empty.
148
+ */
149
+ placeholder?: string;
150
+ /**
151
+ * Whether the editor should show line numbers.
152
+ *
153
+ * @default true
154
+ */
155
+ lineNumbers?: boolean;
156
+ /**
157
+ * Whether the editor is read-only.
158
+ *
159
+ * @default false
160
+ */
161
+ readonly?: boolean;
162
+
163
+ /**
164
+ * String value to assign to the aria-label attribute of the editor.
165
+ */
166
+ ariaLabel?: string;
167
+
168
+ /**
169
+ * Whether keybindings for inserting indents with the Tab key should be disabled.
170
+ *
171
+ * true will not create keybindings for inserting indents.
172
+ * false will create keybindings for inserting indents.
173
+ *
174
+ * @default false
175
+ */
176
+ moveFocusOnTab?: boolean;
99
177
  }
100
178
 
101
- const executeKeybinding = (onExecute?: (cmd: string) => void) =>
102
- onExecute
103
- ? [
104
- {
105
- key: 'Ctrl-Enter',
106
- mac: 'Mod-Enter',
107
- preventDefault: true,
108
- run: (view: EditorView) => {
109
- const doc = view.state.doc.toString();
110
- if (doc.trim() !== '') {
111
- onExecute(doc);
112
- }
179
+ const format = (view: EditorView): void => {
180
+ try {
181
+ const doc = view.state.doc.toString();
182
+ const { formattedQuery, newCursorPos } = formatQuery(doc, {
183
+ cursorPosition: view.state.selection.main.anchor,
184
+ });
185
+ view.dispatch({
186
+ changes: {
187
+ from: 0,
188
+ to: doc.length,
189
+ insert: formattedQuery,
190
+ },
191
+ selection: { anchor: newCursorPos },
192
+ });
193
+ } catch (error) {
194
+ // Formatting failed, likely because of a syntax error
195
+ }
196
+ };
197
+
198
+ const executeKeybinding = (
199
+ onExecute?: (cmd: string) => void,
200
+ newLineOnEnter?: boolean,
201
+ flush?: () => void,
202
+ ) => {
203
+ const keybindings: Record<string, KeyBinding> = {
204
+ 'Shift-Enter': {
205
+ key: 'Shift-Enter',
206
+ run: insertNewline,
207
+ },
208
+ 'Ctrl-Enter': {
209
+ key: 'Ctrl-Enter',
210
+ run: () => true,
211
+ },
212
+ Enter: {
213
+ key: 'Enter',
214
+ run: insertNewline,
215
+ },
216
+ };
217
+ if (onExecute) {
218
+ keybindings['Ctrl-Enter'] = {
219
+ key: 'Ctrl-Enter',
220
+ mac: 'Mod-Enter',
221
+ preventDefault: true,
222
+ run: (view: EditorView) => {
223
+ const doc = view.state.doc.toString();
224
+ if (doc.trim() !== '') {
225
+ flush?.();
226
+ onExecute(doc);
227
+ }
228
+
229
+ return true;
230
+ },
231
+ };
232
+
233
+ if (!newLineOnEnter) {
234
+ keybindings['Enter'] = {
235
+ key: 'Enter',
236
+ preventDefault: true,
237
+ run: (view: EditorView) => {
238
+ const doc = view.state.doc.toString();
239
+ if (doc.includes('\n')) {
240
+ // Returning false means the event will mark the event
241
+ // as not handled and the default behavior will be executed
242
+ return false;
243
+ }
244
+
245
+ if (doc.trim() !== '') {
246
+ flush?.();
247
+ onExecute(doc);
248
+ }
113
249
 
114
- return true;
115
- },
250
+ return true;
116
251
  },
117
- ]
118
- : [];
252
+ };
253
+ }
254
+ }
255
+
256
+ return Object.values(keybindings);
257
+ };
119
258
 
120
259
  const themeCompartment = new Compartment();
121
260
  const keyBindingCompartment = new Compartment();
261
+ const lineNumbersCompartment = new Compartment();
262
+ const readOnlyCompartment = new Compartment();
263
+ const placeholderCompartment = new Compartment();
264
+ const domEventHandlerCompartment = new Compartment();
265
+
266
+ const formatLineNumber =
267
+ (prompt?: string) => (a: number, state: EditorState) => {
268
+ if (state.doc.lines === 1 && prompt !== undefined) {
269
+ return prompt;
270
+ }
271
+
272
+ return a.toString();
273
+ };
274
+
275
+ type CypherEditorState = { cypherSupportEnabled: boolean };
122
276
 
123
277
  const ExternalEdit = Annotation.define<boolean>();
124
- export class CypherEditor extends Component<CypherEditorProps> {
278
+
279
+ export class CypherEditor extends Component<
280
+ CypherEditorProps,
281
+ CypherEditorState
282
+ > {
125
283
  /**
126
284
  * The codemirror editor container.
127
285
  */
@@ -136,6 +294,13 @@ export class CypherEditor extends Component<CypherEditorProps> {
136
294
  editorView: React.MutableRefObject<EditorView> = createRef();
137
295
  private schemaRef: React.MutableRefObject<CypherConfig> = createRef();
138
296
 
297
+ /**
298
+ * Format Cypher query
299
+ */
300
+ format() {
301
+ format(this.editorView.current);
302
+ }
303
+
139
304
  /**
140
305
  * Focus the editor
141
306
  */
@@ -158,13 +323,20 @@ export class CypherEditor extends Component<CypherEditorProps> {
158
323
  */
159
324
  setValueAndFocus(value = '') {
160
325
  const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
326
+ // Normalize line endings to LF that CM expects.
327
+ // Prevents issues with inserted values that contain CRLF line endings.
328
+ // https://codemirror.net/docs/ref/?utm_source=chatgpt.com#state.EditorState^lineSeparator
329
+ const normalizedValue = value.replace(/\r\n/g, '\n');
161
330
  this.editorView.current.dispatch({
162
331
  changes: {
163
332
  from: 0,
164
333
  to: currentCmValue.length,
165
- insert: value,
334
+ insert: normalizedValue,
335
+ },
336
+ selection: {
337
+ anchor: normalizedValue.length,
338
+ head: normalizedValue.length,
166
339
  },
167
- selection: { anchor: value.length, head: value.length },
168
340
  });
169
341
  this.editorView.current?.focus();
170
342
  }
@@ -174,11 +346,24 @@ export class CypherEditor extends Component<CypherEditorProps> {
174
346
  schema: {},
175
347
  overrideThemeBackgroundColor: false,
176
348
  lineWrap: false,
349
+ showSignatureTooltipBelow: true,
177
350
  extraKeybindings: [],
178
351
  history: [],
179
352
  theme: 'light',
353
+ lineNumbers: true,
354
+ newLineOnEnter: false,
355
+ moveFocusOnTab: false,
180
356
  };
181
357
 
358
+ private debouncedOnChange = this.props.onChange
359
+ ? debounce(
360
+ ((value, viewUpdate) => {
361
+ this.props.onChange(value, viewUpdate);
362
+ }) satisfies CypherEditorProps['onChange'],
363
+ DEBOUNCE_TIME,
364
+ )
365
+ : undefined;
366
+
182
367
  componentDidMount(): void {
183
368
  const {
184
369
  theme,
@@ -187,18 +372,34 @@ export class CypherEditor extends Component<CypherEditorProps> {
187
372
  overrideThemeBackgroundColor,
188
373
  schema,
189
374
  lint,
190
- onChange,
375
+ showSignatureTooltipBelow,
376
+ featureFlags,
191
377
  onExecute,
378
+ newLineOnEnter,
192
379
  } = this.props;
193
380
 
194
- this.schemaRef.current = { schema, lint };
381
+ this.schemaRef.current = {
382
+ schema,
383
+ lint,
384
+ showSignatureTooltipBelow,
385
+ featureFlags: {
386
+ consoleCommands: true,
387
+ ...featureFlags,
388
+ },
389
+ useLightVersion: false,
390
+ setUseLightVersion: (newVal) => {
391
+ if (this.schemaRef.current !== undefined) {
392
+ this.schemaRef.current.useLightVersion = newVal;
393
+ }
394
+ },
395
+ };
195
396
 
196
397
  const themeExtension = getThemeExtension(
197
398
  theme,
198
399
  overrideThemeBackgroundColor,
199
400
  );
200
401
 
201
- const changeListener = onChange
402
+ const changeListener = this.debouncedOnChange
202
403
  ? [
203
404
  EditorView.updateListener.of((upt: ViewUpdate) => {
204
405
  const wasUserEdit = !upt.transactions.some((tr) =>
@@ -208,7 +409,7 @@ export class CypherEditor extends Component<CypherEditorProps> {
208
409
  if (upt.docChanged && wasUserEdit) {
209
410
  const doc = upt.state.doc;
210
411
  const value = doc.toString();
211
- onChange(value, upt);
412
+ this.debouncedOnChange(value, upt);
212
413
  }
213
414
  }),
214
415
  ]
@@ -217,24 +418,40 @@ export class CypherEditor extends Component<CypherEditorProps> {
217
418
  this.editorState.current = EditorState.create({
218
419
  extensions: [
219
420
  keyBindingCompartment.of(
220
- keymap.of([...executeKeybinding(onExecute), ...extraKeybindings]),
421
+ keymap.of([
422
+ ...executeKeybinding(onExecute, newLineOnEnter, () =>
423
+ this.debouncedOnChange?.flush(),
424
+ ),
425
+ ...extraKeybindings,
426
+ ]),
221
427
  ),
428
+ richClipboardCopier,
222
429
  historyNavigation(this.props),
223
- basicNeo4jSetup(),
430
+ basicNeo4jSetup(this.props),
224
431
  themeCompartment.of(themeExtension),
225
432
  changeListener,
226
433
  cypher(this.schemaRef.current),
227
434
  lineWrap ? EditorView.lineWrapping : [],
228
435
 
229
- lineNumbers({
230
- formatNumber: (a, state) => {
231
- if (state.doc.lines === 1 && this.props.prompt !== undefined) {
232
- return this.props.prompt;
233
- }
234
-
235
- return a.toString();
236
- },
237
- }),
436
+ lineNumbersCompartment.of(
437
+ this.props.lineNumbers
438
+ ? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
439
+ : [],
440
+ ),
441
+ readOnlyCompartment.of(EditorState.readOnly.of(this.props.readonly)),
442
+ placeholderCompartment.of(
443
+ this.props.placeholder ? placeholder(this.props.placeholder) : [],
444
+ ),
445
+ domEventHandlerCompartment.of(
446
+ this.props.domEventHandlers
447
+ ? EditorView.domEventHandlers(this.props.domEventHandlers)
448
+ : [],
449
+ ),
450
+ this.props.ariaLabel
451
+ ? EditorView.contentAttributes.of({
452
+ 'aria-label': this.props.ariaLabel,
453
+ })
454
+ : [],
238
455
  ],
239
456
  doc: this.props.value,
240
457
  });
@@ -249,6 +466,8 @@ export class CypherEditor extends Component<CypherEditorProps> {
249
466
  if (this.props.value) {
250
467
  this.updateCursorPosition(this.props.value.length);
251
468
  }
469
+ } else if (this.props.offset) {
470
+ this.updateCursorPosition(this.props.offset);
252
471
  }
253
472
  }
254
473
 
@@ -260,7 +479,11 @@ export class CypherEditor extends Component<CypherEditorProps> {
260
479
  // Handle externally set value
261
480
  const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
262
481
 
263
- if (this.props.value !== undefined && currentCmValue !== this.props.value) {
482
+ if (
483
+ this.props.value !== undefined && // If the component becomes uncontolled, we just leave the value as is
484
+ this.props.value !== prevProps.value && // The value prop has changed, we need to update the editor
485
+ this.props.value !== currentCmValue // No need to dispatch an update if the value is the same
486
+ ) {
264
487
  this.editorView.current.dispatch({
265
488
  changes: {
266
489
  from: 0,
@@ -288,6 +511,35 @@ export class CypherEditor extends Component<CypherEditorProps> {
288
511
  });
289
512
  }
290
513
 
514
+ if (
515
+ prevProps.lineNumbers !== this.props.lineNumbers ||
516
+ prevProps.prompt !== this.props.prompt
517
+ ) {
518
+ this.editorView.current.dispatch({
519
+ effects: lineNumbersCompartment.reconfigure(
520
+ this.props.lineNumbers
521
+ ? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
522
+ : [],
523
+ ),
524
+ });
525
+ }
526
+
527
+ if (prevProps.readonly !== this.props.readonly) {
528
+ this.editorView.current.dispatch({
529
+ effects: readOnlyCompartment.reconfigure(
530
+ EditorState.readOnly.of(this.props.readonly),
531
+ ),
532
+ });
533
+ }
534
+
535
+ if (prevProps.placeholder !== this.props.placeholder) {
536
+ this.editorView.current.dispatch({
537
+ effects: placeholderCompartment.reconfigure(
538
+ this.props.placeholder ? placeholder(this.props.placeholder) : [],
539
+ ),
540
+ });
541
+ }
542
+
291
543
  if (
292
544
  prevProps.extraKeybindings !== this.props.extraKeybindings ||
293
545
  prevProps.onExecute !== this.props.onExecute
@@ -295,14 +547,34 @@ export class CypherEditor extends Component<CypherEditorProps> {
295
547
  this.editorView.current.dispatch({
296
548
  effects: keyBindingCompartment.reconfigure(
297
549
  keymap.of([
298
- ...executeKeybinding(this.props.onExecute),
550
+ ...executeKeybinding(
551
+ this.props.onExecute,
552
+ this.props.newLineOnEnter,
553
+ () => this.debouncedOnChange?.flush(),
554
+ ),
299
555
  ...this.props.extraKeybindings,
300
556
  ]),
301
557
  ),
302
558
  });
303
559
  }
304
560
 
305
- if (prevProps.history?.length !== this.props.history?.length) {
561
+ if (prevProps.domEventHandlers !== this.props.domEventHandlers) {
562
+ this.editorView.current.dispatch({
563
+ effects: domEventHandlerCompartment.reconfigure(
564
+ this.props.domEventHandlers
565
+ ? EditorView.domEventHandlers(this.props.domEventHandlers)
566
+ : [],
567
+ ),
568
+ });
569
+ }
570
+
571
+ // This component rerenders on every keystroke and comparing the
572
+ // full lists of editor strings on every render could be expensive.
573
+ const didChangeHistoryEstimate =
574
+ prevProps.history?.length !== this.props.history?.length ||
575
+ prevProps.history?.[0] !== this.props.history?.[0];
576
+
577
+ if (didChangeHistoryEstimate) {
306
578
  this.editorView.current.dispatch({
307
579
  effects: replaceHistory.of(this.props.history ?? []),
308
580
  });
@@ -315,10 +587,12 @@ export class CypherEditor extends Component<CypherEditorProps> {
315
587
  */
316
588
  this.schemaRef.current.schema = this.props.schema;
317
589
  this.schemaRef.current.lint = this.props.lint;
590
+ this.schemaRef.current.featureFlags = this.props.featureFlags;
318
591
  }
319
592
 
320
593
  componentWillUnmount(): void {
321
594
  this.editorView.current?.destroy();
595
+ cleanupWorkers();
322
596
  }
323
597
 
324
598
  render(): React.ReactNode {
@@ -0,0 +1 @@
1
+ export const DEBOUNCE_TIME = 200;