@neo4j-cypher/react-codemirror 2.0.0-next.33 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo4j-cypher/react-codemirror",
3
- "version": "2.0.0-next.33",
3
+ "version": "2.0.0-next.34",
4
4
  "keywords": [
5
5
  "codemirror",
6
6
  "codemirror 6",
@@ -47,13 +47,13 @@
47
47
  "style-mod": "^4.1.2",
48
48
  "vscode-languageserver-types": "^3.17.3",
49
49
  "workerpool": "^9.3.3",
50
- "@neo4j-cypher/language-support": "2.0.0-next.30",
51
- "@neo4j-cypher/lint-worker": "1.10.1-next.7"
50
+ "@neo4j-cypher/language-support": "2.0.0-next.31",
51
+ "@neo4j-cypher/lint-worker": "1.10.1-next.8"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@neo4j-ndl/base": "^3.2.10",
55
- "@playwright/experimental-ct-react": "^1.54.2",
56
- "@playwright/test": "^1.54.2",
55
+ "@playwright/experimental-ct-react": "^1.55.1",
56
+ "@playwright/test": "^1.55.1",
57
57
  "@types/lodash.debounce": "^4.0.9",
58
58
  "@types/react": "^18.0.28",
59
59
  "@types/react-dom": "^18.0.11",
@@ -61,7 +61,7 @@
61
61
  "copyfiles": "^2.4.1",
62
62
  "jsdom": "^24.1.1",
63
63
  "lodash": "^4.17.21",
64
- "playwright": "^1.54.2",
64
+ "playwright": "^1.55.1",
65
65
  "react": "^18.2.0",
66
66
  "react-dom": "^18.2.0",
67
67
  "vite": "^4.5.10"
@@ -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 {
@@ -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
 
@@ -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,
@@ -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',
@@ -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?: {