@neo4j-cypher/react-codemirror 2.0.0-next.32 → 2.0.0-next.34

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 (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/src/CypherEditor.d.ts +15 -1
  3. package/dist/src/CypherEditor.js +66 -2
  4. package/dist/src/CypherEditor.js.map +1 -1
  5. package/dist/src/CypherEditor.test.js.map +1 -1
  6. package/dist/src/e2e_tests/autoCompletion.spec.js +6 -3
  7. package/dist/src/e2e_tests/autoCompletion.spec.js.map +1 -1
  8. package/dist/src/e2e_tests/debounce.spec.js +1 -1
  9. package/dist/src/e2e_tests/debounce.spec.js.map +1 -1
  10. package/dist/src/e2e_tests/signatureHelp.spec.js +0 -1
  11. package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -1
  12. package/dist/src/e2e_tests/snippets.spec.js.map +1 -1
  13. package/dist/src/e2e_tests/syntaxValidation.spec.js +10 -4
  14. package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -1
  15. package/dist/src/lang-cypher/autocomplete.js +6 -3
  16. package/dist/src/lang-cypher/autocomplete.js.map +1 -1
  17. package/dist/src/lang-cypher/contants.test.js +2 -2
  18. package/dist/src/lang-cypher/contants.test.js.map +1 -1
  19. package/dist/src/lang-cypher/createCypherTheme.js.map +1 -1
  20. package/dist/src/lang-cypher/langCypher.d.ts +2 -1
  21. package/dist/src/lang-cypher/langCypher.js.map +1 -1
  22. package/dist/src/lang-cypher/lintWorker.mjs +176 -175
  23. package/dist/src/lang-cypher/parser-adapter.js +1 -2
  24. package/dist/src/lang-cypher/parser-adapter.js.map +1 -1
  25. package/dist/src/lang-cypher/signatureHelp.js +1 -2
  26. package/dist/src/lang-cypher/signatureHelp.js.map +1 -1
  27. package/dist/src/lang-cypher/syntaxValidation.js +1 -2
  28. package/dist/src/lang-cypher/syntaxValidation.js.map +1 -1
  29. package/dist/src/lang-cypher/utils.js +1 -1
  30. package/dist/src/lang-cypher/utils.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +27 -27
  33. package/src/CypherEditor.test.tsx +18 -19
  34. package/src/CypherEditor.tsx +87 -2
  35. package/src/e2e_tests/autoCompletion.spec.tsx +13 -7
  36. package/src/e2e_tests/debounce.spec.tsx +32 -36
  37. package/src/e2e_tests/signatureHelp.spec.tsx +0 -1
  38. package/src/e2e_tests/snippets.spec.tsx +0 -1
  39. package/src/e2e_tests/syntaxValidation.spec.tsx +21 -13
  40. package/src/lang-cypher/autocomplete.ts +7 -8
  41. package/src/lang-cypher/contants.test.ts +2 -2
  42. package/src/lang-cypher/createCypherTheme.ts +7 -21
  43. package/src/lang-cypher/langCypher.ts +5 -1
  44. package/src/lang-cypher/lintWorker.mjs +176 -175
  45. package/src/lang-cypher/parser-adapter.ts +6 -6
  46. package/src/lang-cypher/signatureHelp.ts +5 -2
  47. package/src/lang-cypher/syntaxValidation.ts +1 -2
  48. package/src/lang-cypher/utils.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@neo4j-cypher/react-codemirror",
3
+ "version": "2.0.0-next.34",
4
+ "keywords": [
5
+ "codemirror",
6
+ "codemirror 6",
7
+ "cypher",
8
+ "editor",
9
+ "neo4j",
10
+ "react"
11
+ ],
12
+ "bugs": {
13
+ "url": "https://github.com/neo4j/cypher-language-support/issues"
14
+ },
3
15
  "license": "Apache-2.0",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git://github.com/neo4j/cypher-language-support.git"
19
+ },
4
20
  "files": [
5
21
  "dist",
6
22
  "src",
@@ -9,30 +25,10 @@
9
25
  "LICENSE.md",
10
26
  "CHANGELOG.md"
11
27
  ],
12
- "keywords": [
13
- "neo4j",
14
- "cypher",
15
- "react",
16
- "editor",
17
- "codemirror",
18
- "codemirror 6"
19
- ],
20
- "version": "2.0.0-next.32",
21
- "main": "./dist/src/index.js",
22
- "types": "./dist/src/index.d.ts",
23
28
  "type": "module",
24
29
  "sideEffects": false,
25
- "repository": {
26
- "type": "git",
27
- "url": "git://github.com/neo4j/cypher-language-support.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/neo4j/cypher-language-support/issues"
31
- },
32
- "engineStrict": true,
33
- "engines": {
34
- "node": ">=24.11.1"
35
- },
30
+ "main": "./dist/src/index.js",
31
+ "types": "./dist/src/index.d.ts",
36
32
  "dependencies": {
37
33
  "@codemirror/autocomplete": "^6.18.6",
38
34
  "@codemirror/commands": "^6.8.1",
@@ -51,13 +47,13 @@
51
47
  "style-mod": "^4.1.2",
52
48
  "vscode-languageserver-types": "^3.17.3",
53
49
  "workerpool": "^9.3.3",
54
- "@neo4j-cypher/language-support": "2.0.0-next.29",
55
- "@neo4j-cypher/lint-worker": "1.10.1-next.6"
50
+ "@neo4j-cypher/language-support": "2.0.0-next.31",
51
+ "@neo4j-cypher/lint-worker": "1.10.1-next.8"
56
52
  },
57
53
  "devDependencies": {
58
54
  "@neo4j-ndl/base": "^3.2.10",
59
- "@playwright/experimental-ct-react": "^1.54.2",
60
- "@playwright/test": "^1.54.2",
55
+ "@playwright/experimental-ct-react": "^1.55.1",
56
+ "@playwright/test": "^1.55.1",
61
57
  "@types/lodash.debounce": "^4.0.9",
62
58
  "@types/react": "^18.0.28",
63
59
  "@types/react-dom": "^18.0.11",
@@ -65,7 +61,7 @@
65
61
  "copyfiles": "^2.4.1",
66
62
  "jsdom": "^24.1.1",
67
63
  "lodash": "^4.17.21",
68
- "playwright": "^1.54.2",
64
+ "playwright": "^1.55.1",
69
65
  "react": "^18.2.0",
70
66
  "react-dom": "^18.2.0",
71
67
  "vite": "^4.5.10"
@@ -73,6 +69,10 @@
73
69
  "peerDependencies": {
74
70
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
75
71
  },
72
+ "engines": {
73
+ "node": ">=24.11.1"
74
+ },
75
+ "engineStrict": true,
76
76
  "scripts": {
77
77
  "dev": "tsc --watch",
78
78
  "build": "pnpm copy-lint-worker && tsc --declaration --outDir dist/",
@@ -124,27 +124,24 @@ test.fails('new props.value should cancel onChange', async () => {
124
124
  });
125
125
 
126
126
  // value updates from outside onExecute are overwritten by pending updates
127
- test.fails(
128
- 'new props.value set to same value should cancel onChange',
129
- async () => {
130
- // 1. value is set initially
131
- value = 'same value';
132
- rerender();
127
+ test.fails('new props.value set to same value should cancel onChange', async () => {
128
+ // 1. value is set initially
129
+ value = 'same value';
130
+ rerender();
133
131
 
134
- // 2. value is updated internally
135
- ref.current.setValueAndFocus('update');
132
+ // 2. value is updated internally
133
+ ref.current.setValueAndFocus('update');
136
134
 
137
- // 3. editor is rerendered with a new value while a value update is still pending
138
- value = 'same value';
139
- rerender();
135
+ // 3. editor is rerendered with a new value while a value update is still pending
136
+ value = 'same value';
137
+ rerender();
140
138
 
141
- await debounce();
139
+ await debounce();
142
140
 
143
- // expect(onChange).not.toHaveBeenCalled();
144
- expect(getEditorValue()).toBe('same value');
145
- expect(value).toBe('same value');
146
- },
147
- );
141
+ // expect(onChange).not.toHaveBeenCalled();
142
+ expect(getEditorValue()).toBe('same value');
143
+ expect(value).toBe('same value');
144
+ });
148
145
 
149
146
  test('rerender should not cancel onChange', async () => {
150
147
  // 1. value is updated internally
@@ -200,5 +197,7 @@ test('rerender with prior external update should not cancel onChange', async ()
200
197
  });
201
198
 
202
199
  test('setValueAndFocus should handle CRLF newline characters', () => {
203
- expect(() => ref.current.setValueAndFocus('new value\r\nnew line')).not.toThrow();
204
- });
200
+ expect(() =>
201
+ ref.current.setValueAndFocus('new value\r\nnew line'),
202
+ ).not.toThrow();
203
+ });
@@ -13,7 +13,11 @@ import {
13
13
  placeholder,
14
14
  ViewUpdate,
15
15
  } from '@codemirror/view';
16
- import { formatQuery, type DbSchema } from '@neo4j-cypher/language-support';
16
+ import {
17
+ formatQuery,
18
+ CypherLanguageService,
19
+ type DbSchema,
20
+ } from '@neo4j-cypher/language-support';
17
21
  import debounce from 'lodash.debounce';
18
22
  import { Component, createRef } from 'react';
19
23
  import { DEBOUNCE_TIME } from './constants';
@@ -26,6 +30,8 @@ import { cleanupWorkers } from './lang-cypher/syntaxValidation';
26
30
  import { basicNeo4jSetup } from './neo4jSetup';
27
31
  import { getThemeExtension } from './themes';
28
32
  import { richClipboardCopier } from './richClipboardCopier';
33
+ import { LintWorker } from '@neo4j-cypher/lint-worker';
34
+ import workerpool from 'workerpool';
29
35
 
30
36
  type DomEventHandlers = Parameters<typeof EditorView.domEventHandlers>[0];
31
37
  export interface CypherEditorProps {
@@ -190,7 +196,7 @@ const format = (view: EditorView): void => {
190
196
  },
191
197
  selection: { anchor: newCursorPos },
192
198
  });
193
- } catch (error) {
199
+ } catch {
194
200
  // Formatting failed, likely because of a syntax error
195
201
  }
196
202
  };
@@ -275,11 +281,72 @@ const formatLineNumber =
275
281
  type CypherEditorState = { cypherSupportEnabled: boolean };
276
282
 
277
283
  const ExternalEdit = Annotation.define<boolean>();
284
+ const WorkerURL = new URL('./lang-cypher/lintWorker.mjs', import.meta.url)
285
+ .pathname;
286
+
287
+ class CodemirrorSymbolFetcher {
288
+ constructor(languageService: CypherLanguageService) {
289
+ this.languageService = languageService;
290
+ }
291
+ private languageService: CypherLanguageService;
292
+ private processing = false;
293
+ private nextJob: {
294
+ query: string;
295
+ schema: DbSchema;
296
+ };
297
+ private symbolTablePool = workerpool.pool(WorkerURL, {
298
+ minWorkers: 1,
299
+ workerOpts: { type: 'module' },
300
+ workerTerminateTimeout: 2000,
301
+ });
302
+
303
+ public queueSymbolJob(query: string, schema: DbSchema) {
304
+ this.nextJob = { query, schema };
305
+ if (!this.processing) {
306
+ void this.processJobQueue();
307
+ }
308
+ }
309
+
310
+ public terminate() {
311
+ this.nextJob = undefined;
312
+ void this.symbolTablePool.terminate();
313
+ }
314
+
315
+ private async processJobQueue() {
316
+ this.processing = true;
317
+ while (this.nextJob) {
318
+ try {
319
+ const proxyWorker =
320
+ (await this.symbolTablePool.proxy()) as unknown as LintWorker;
321
+ const query = this.nextJob.query;
322
+ const dbSchema = this.nextJob.schema;
323
+ this.nextJob = undefined;
324
+
325
+ const result = await proxyWorker.lintCypherQuery(query, dbSchema);
326
+
327
+ if (result.symbolTables) {
328
+ this.languageService.setSymbolsInfo({
329
+ query,
330
+ symbolTables: result.symbolTables,
331
+ });
332
+ }
333
+ } catch (err) {
334
+ //eslint-disable-next-line
335
+ console.log('Symbol table calculation failed ' + String(err));
336
+ }
337
+ }
338
+ this.processing = false;
339
+ }
340
+ }
278
341
 
279
342
  export class CypherEditor extends Component<
280
343
  CypherEditorProps,
281
344
  CypherEditorState
282
345
  > {
346
+ /**
347
+ * The symbol fetcher object used to fetch the current symbol table on document changes
348
+ */
349
+ symbolFetcher: CodemirrorSymbolFetcher;
283
350
  /**
284
351
  * The codemirror editor container.
285
352
  */
@@ -379,6 +446,7 @@ export class CypherEditor extends Component<
379
446
  } = this.props;
380
447
 
381
448
  this.schemaRef.current = {
449
+ languageService: new CypherLanguageService(),
382
450
  schema,
383
451
  lint,
384
452
  showSignatureTooltipBelow,
@@ -394,6 +462,10 @@ export class CypherEditor extends Component<
394
462
  },
395
463
  };
396
464
 
465
+ this.symbolFetcher = new CodemirrorSymbolFetcher(
466
+ this.schemaRef.current.languageService,
467
+ );
468
+
397
469
  const themeExtension = getThemeExtension(
398
470
  theme,
399
471
  overrideThemeBackgroundColor,
@@ -402,6 +474,12 @@ export class CypherEditor extends Component<
402
474
  const changeListener = this.debouncedOnChange
403
475
  ? [
404
476
  EditorView.updateListener.of((upt: ViewUpdate) => {
477
+ if (upt.docChanged) {
478
+ this.symbolFetcher.queueSymbolJob(
479
+ upt.state.doc.toString(),
480
+ this.schemaRef.current.schema,
481
+ );
482
+ }
405
483
  const wasUserEdit = !upt.transactions.some((tr) =>
406
484
  tr.annotation(ExternalEdit),
407
485
  );
@@ -452,6 +530,12 @@ export class CypherEditor extends Component<
452
530
  'aria-label': this.props.ariaLabel,
453
531
  })
454
532
  : [],
533
+ !this.props.moveFocusOnTab
534
+ ? EditorView.contentAttributes.of({
535
+ 'aria-description':
536
+ 'Press Escape to leave the editor and continue tabbing through the page',
537
+ })
538
+ : [],
455
539
  ],
456
540
  doc: this.props.value,
457
541
  });
@@ -592,6 +676,7 @@ export class CypherEditor extends Component<
592
676
 
593
677
  componentWillUnmount(): void {
594
678
  this.editorView.current?.destroy();
679
+ this.symbolFetcher?.terminate();
595
680
  cleanupWorkers();
596
681
  }
597
682
 
@@ -33,7 +33,10 @@ RETURN n;`}
33
33
  await textField.press('Control+ ');
34
34
 
35
35
  await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
36
- await page.locator('.cm-tooltip-autocomplete').getByText('WHERE', {exact: true}).click();
36
+ await page
37
+ .locator('.cm-tooltip-autocomplete')
38
+ .getByText('WHERE', { exact: true })
39
+ .click();
37
40
 
38
41
  await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
39
42
 
@@ -544,13 +547,16 @@ test('completions depend on the Cypher version', async ({ page, mount }) => {
544
547
  ).toBeVisible();
545
548
  });
546
549
 
547
- test('does not complete properties for non node / relationship variables', async ({ page, mount }) => {
550
+ test('does not complete properties for non node / relationship variables', async ({
551
+ page,
552
+ mount,
553
+ }) => {
548
554
  await mount(
549
555
  <CypherEditor
550
- schema={{
551
- propertyKeys: ["nodeProperty"]
556
+ schema={{
557
+ propertyKeys: ['nodeProperty'],
552
558
  }}
553
- />
559
+ />,
554
560
  );
555
561
 
556
562
  const textField = page.getByRole('textbox');
@@ -562,10 +568,10 @@ test('does not complete properties for non node / relationship variables', async
562
568
 
563
569
  await textField.fill('WITH 1 AS x RETURN x.');
564
570
  // This could be flaky if the semantic analysis takes too long
565
- await page.waitForTimeout(500)
571
+ await page.waitForTimeout(500);
566
572
  await textField.press('Escape');
567
573
  await textField.press('Control+ ');
568
574
  await expect(
569
- page.locator('.cm-tooltip-autocomplete').getByText('nodeProperty')
575
+ page.locator('.cm-tooltip-autocomplete').getByText('nodeProperty'),
570
576
  ).not.toBeVisible();
571
577
  });
@@ -28,47 +28,43 @@ test.fail(
28
28
  );
29
29
 
30
30
  // TODO Fix this test
31
- test.fixme(
32
- 'onExecute updates should override debounce updates',
33
- async ({ mount, page }) => {
34
- const editorPage = new CypherEditorPage(page);
35
- let value = '';
36
-
37
- const onExecute = () => {
38
- value = '';
39
- void component.update(
40
- <CypherEditor
41
- value={value}
42
- onChange={onChange}
43
- onExecute={onExecute}
44
- />,
45
- );
46
- };
47
-
48
- const onChange = (val: string) => {
49
- value = val;
50
- void component.update(
51
- <CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
52
- );
53
- };
31
+ test.fixme('onExecute updates should override debounce updates', async ({
32
+ mount,
33
+ page,
34
+ }) => {
35
+ const editorPage = new CypherEditorPage(page);
36
+ let value = '';
54
37
 
55
- const component = await mount(
38
+ const onExecute = () => {
39
+ value = '';
40
+ void component.update(
56
41
  <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
57
42
  );
43
+ };
58
44
 
59
- await editorPage.getEditor().pressSequentially('RETURN 1');
60
- await editorPage.getEditor().press('Enter');
61
- await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
62
- await expect(component).not.toContainText('RETURN 1');
45
+ const onChange = (val: string) => {
46
+ value = val;
47
+ void component.update(
48
+ <CypherEditor value={val} onChange={onChange} onExecute={onExecute} />,
49
+ );
50
+ };
63
51
 
64
- await editorPage.getEditor().pressSequentially('RETURN 1');
65
- await editorPage.getEditor().pressSequentially('');
66
- await editorPage.getEditor().pressSequentially('RETURN 1');
67
- await editorPage.getEditor().press('Enter');
68
- await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
69
- await expect(component).not.toContainText('RETURN 1');
70
- },
71
- );
52
+ const component = await mount(
53
+ <CypherEditor value={value} onChange={onChange} onExecute={onExecute} />,
54
+ );
55
+
56
+ await editorPage.getEditor().pressSequentially('RETURN 1');
57
+ await editorPage.getEditor().press('Enter');
58
+ await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
59
+ await expect(component).not.toContainText('RETURN 1');
60
+
61
+ await editorPage.getEditor().pressSequentially('RETURN 1');
62
+ await editorPage.getEditor().pressSequentially('');
63
+ await editorPage.getEditor().pressSequentially('RETURN 1');
64
+ await editorPage.getEditor().press('Enter');
65
+ await page.waitForTimeout(DEBOUNCE_TIME_WITH_MARGIN);
66
+ await expect(component).not.toContainText('RETURN 1');
67
+ });
72
68
 
73
69
  test('onExecute should fire after debounced updates', async ({
74
70
  mount,
@@ -263,7 +263,6 @@ test('Signature help only shows the description past the last argument', async (
263
263
  autofocus={true}
264
264
  />,
265
265
  );
266
- 1;
267
266
 
268
267
  const tooltip = page.locator('.cm-signature-help-panel');
269
268
 
@@ -1,7 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
 
4
-
5
4
  test('does not automatically open completion panel for expressions after snippet trigger char', async ({
6
5
  page,
7
6
  mount,
@@ -182,9 +182,9 @@ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
182
182
  );
183
183
  });
184
184
 
185
- test('Syntax highlighting works as expected with multiple separate linting messages', async ( {
185
+ test('Syntax highlighting works as expected with multiple separate linting messages', async ({
186
186
  page,
187
- mount
187
+ mount,
188
188
  }) => {
189
189
  const editorPage = new CypherEditorPage(page);
190
190
  const query = `MATCH (n)--(m) CALL (n) {RETURN id(n) AS b} RETURN apoc.create.uuid(), a`;
@@ -192,12 +192,20 @@ test('Syntax highlighting works as expected with multiple separate linting messa
192
192
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
193
193
  await expect(
194
194
  editorPage.page.locator('.cm-deprecated-element').last(),
195
- ).toBeVisible({ timeout: 10000 });
195
+ ).toBeVisible({
196
+ timeout: 10000,
197
+ });
196
198
  await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
197
- await editorPage.checkWarningMessage('id', `The query used a deprecated function. ('id' has been replaced by 'elementId or consider using an application-generated id')`);
198
- await editorPage.checkWarningMessage('apoc.create.uuid', 'Function apoc.create.uuid is deprecated. Alternative: Neo4j randomUUID() function');
199
+ await editorPage.checkWarningMessage(
200
+ 'id',
201
+ `The query used a deprecated function. ('id' has been replaced by 'elementId or consider using an application-generated id')`,
202
+ );
203
+ await editorPage.checkWarningMessage(
204
+ 'apoc.create.uuid',
205
+ 'Function apoc.create.uuid is deprecated. Alternative: Neo4j randomUUID() function',
206
+ );
199
207
  await editorPage.checkErrorMessage('a', 'Variable `a` not defined');
200
- })
208
+ });
201
209
 
202
210
  test('Strikethroughs are shown for deprecated functions', async ({
203
211
  page,
@@ -209,7 +217,9 @@ test('Strikethroughs are shown for deprecated functions', async ({
209
217
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
210
218
  await expect(
211
219
  editorPage.page.locator('.cm-deprecated-element').last(),
212
- ).toBeVisible({ timeout: 10000 });
220
+ ).toBeVisible({
221
+ timeout: 10000,
222
+ });
213
223
  await editorPage.checkWarningMessage('id', 'Function id is deprecated.');
214
224
  });
215
225
 
@@ -223,7 +233,9 @@ test('Strikethroughs are shown for deprecated procedures', async ({
223
233
  await mount(<CypherEditor value={query} schema={testData.mockSchema} />);
224
234
  await expect(
225
235
  editorPage.page.locator('.cm-deprecated-element').last(),
226
- ).toBeVisible({ timeout: 10000 });
236
+ ).toBeVisible({
237
+ timeout: 10000,
238
+ });
227
239
 
228
240
  await editorPage.checkWarningMessage(
229
241
  'apoc.create.uuids',
@@ -235,11 +247,7 @@ test('Syntax validation depends on the Cypher version', async ({
235
247
  page,
236
248
  mount,
237
249
  }) => {
238
- await mount(
239
- <CypherEditor
240
- schema={testData.mockSchema}
241
- />,
242
- );
250
+ await mount(<CypherEditor schema={testData.mockSchema} />);
243
251
 
244
252
  const editorPage = new CypherEditorPage(page);
245
253
  const textField = page.getByRole('textbox');
@@ -3,10 +3,7 @@ import {
3
3
  CompletionSource,
4
4
  snippet,
5
5
  } from '@codemirror/autocomplete';
6
- import {
7
- autocomplete,
8
- shouldAutoCompleteYield,
9
- } from '@neo4j-cypher/language-support';
6
+ import { shouldAutoCompleteYield } from '@neo4j-cypher/language-support';
10
7
  import {
11
8
  CompletionItemKind,
12
9
  CompletionItemTag,
@@ -62,7 +59,7 @@ export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
62
59
  (config) => (context) => {
63
60
  const documentText = context.state.doc.toString();
64
61
  const offset = context.pos;
65
- const triggerCharacters = ['.', ':', '{', '$', ')', ']'];
62
+ const triggerCharacters = ['.', ':', '{', '$', ')', ']', '-', '<'];
66
63
  const lastCharacter = documentText.at(offset - 1);
67
64
  const yieldTriggered = shouldAutoCompleteYield(documentText, offset);
68
65
  const lastWord = context.matchBefore(/\w*/);
@@ -81,11 +78,13 @@ export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
81
78
  return null;
82
79
  }
83
80
 
84
- const options = autocomplete(
81
+ const options = config.languageService.autocomplete(
85
82
  documentText,
86
83
  config.schema ?? {},
87
- offset,
88
- context.explicit,
84
+ {
85
+ caretPosition: offset,
86
+ manual: context.explicit,
87
+ },
89
88
  );
90
89
 
91
90
  return {
@@ -1,6 +1,6 @@
1
1
  import { tags } from '@lezer/highlight';
2
2
  import {
3
- applySyntaxColouring,
3
+ highlightSyntax,
4
4
  CypherTokenType,
5
5
  } from '@neo4j-cypher/language-support';
6
6
  import { expect, test } from 'vitest';
@@ -14,7 +14,7 @@ WHERE variable.property = "String"
14
14
  RETURN variable;`;
15
15
 
16
16
  test('correctly parses all cypher token types to style tags', () => {
17
- const tokens = applySyntaxColouring(cypherQueryWithAllTokenTypes);
17
+ const tokens = highlightSyntax(cypherQueryWithAllTokenTypes);
18
18
  const tokenTypes = tokens.map((token) => token.tokenType);
19
19
  expect(tokenTypes).toEqual([
20
20
  'keyword',
@@ -160,24 +160,16 @@ export const createCypherTheme = ({
160
160
  border: 'none',
161
161
  verticalAlign: 'middle',
162
162
  '&[name=next]::before': {
163
- content: `url("data:image/svg+xml;base64,${window.btoa(
164
- downArrowSvg,
165
- )}")`,
163
+ content: `url("data:image/svg+xml;base64,${window.btoa(downArrowSvg)}")`,
166
164
  },
167
165
  '&[name=prev]::before': {
168
- content: `url("data:image/svg+xml;base64,${window.btoa(
169
- upArrowSvg,
170
- )}")`,
166
+ content: `url("data:image/svg+xml;base64,${window.btoa(upArrowSvg)}")`,
171
167
  },
172
168
  '&[name=replace]::before': {
173
- content: `url("data:image/svg+xml;base64,${window.btoa(
174
- replaceSvg,
175
- )}")`,
169
+ content: `url("data:image/svg+xml;base64,${window.btoa(replaceSvg)}")`,
176
170
  },
177
171
  '&[name=replaceAll]::before': {
178
- content: `url("data:image/svg+xml;base64,${window.btoa(
179
- replaceAllSvg,
180
- )}")`,
172
+ content: `url("data:image/svg+xml;base64,${window.btoa(replaceAllSvg)}")`,
181
173
  },
182
174
  width: '20px',
183
175
  height: '20px',
@@ -209,19 +201,13 @@ export const createCypherTheme = ({
209
201
  borderRadius: '4px',
210
202
 
211
203
  '&[name=case]::before': {
212
- content: `url("data:image/svg+xml;base64,${window.btoa(
213
- caseSensitiveSvg,
214
- )}")`,
204
+ content: `url("data:image/svg+xml;base64,${window.btoa(caseSensitiveSvg)}")`,
215
205
  },
216
206
  '&[name=re]::before': {
217
- content: `url("data:image/svg+xml;base64,${window.btoa(
218
- regexSvg,
219
- )}")`,
207
+ content: `url("data:image/svg+xml;base64,${window.btoa(regexSvg)}")`,
220
208
  },
221
209
  '&[name=word]::before': {
222
- content: `url("data:image/svg+xml;base64,${window.btoa(
223
- byWordSvg,
224
- )}")`,
210
+ content: `url("data:image/svg+xml;base64,${window.btoa(byWordSvg)}")`,
225
211
  },
226
212
  '&:hover': {
227
213
  backgroundColor: settings.searchPanel.buttonHoverBackground,
@@ -4,7 +4,10 @@ import {
4
4
  Language,
5
5
  LanguageSupport,
6
6
  } from '@codemirror/language';
7
- import { type DbSchema } from '@neo4j-cypher/language-support';
7
+ import {
8
+ CypherLanguageService,
9
+ type DbSchema,
10
+ } from '@neo4j-cypher/language-support';
8
11
  import { completionStyles, cypherAutocomplete } from './autocomplete';
9
12
  import { ParserAdapter } from './parser-adapter';
10
13
  import { signatureHelpTooltip } from './signatureHelp';
@@ -16,6 +19,7 @@ const facet = defineLanguageFacet({
16
19
  });
17
20
 
18
21
  export type CypherConfig = {
22
+ languageService: CypherLanguageService;
19
23
  lint?: boolean;
20
24
  showSignatureTooltipBelow?: boolean;
21
25
  featureFlags?: {