@neo4j-cypher/react-codemirror 2.0.0-next.7 → 2.0.0-next.9

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 (74) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/CypherEditor.d.ts +60 -2
  3. package/dist/CypherEditor.js +115 -20
  4. package/dist/CypherEditor.js.map +1 -1
  5. package/dist/e2e_tests/autoCompletion.spec.js +112 -17
  6. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -1
  7. package/dist/e2e_tests/configuration.spec.d.ts +1 -0
  8. package/dist/e2e_tests/configuration.spec.js +83 -0
  9. package/dist/e2e_tests/configuration.spec.js.map +1 -0
  10. package/dist/e2e_tests/debounce.spec.d.ts +1 -0
  11. package/dist/e2e_tests/debounce.spec.js +63 -0
  12. package/dist/e2e_tests/debounce.spec.js.map +1 -0
  13. package/dist/e2e_tests/e2eUtils.js +9 -1
  14. package/dist/e2e_tests/e2eUtils.js.map +1 -1
  15. package/dist/e2e_tests/extraKeybindings.spec.js +0 -1
  16. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -1
  17. package/dist/e2e_tests/historyNavigation.spec.js +107 -16
  18. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -1
  19. package/dist/e2e_tests/sanityChecks.spec.js +0 -10
  20. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -1
  21. package/dist/e2e_tests/signatureHelp.spec.js +43 -15
  22. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -1
  23. package/dist/e2e_tests/snippets.spec.d.ts +1 -0
  24. package/dist/e2e_tests/snippets.spec.js +62 -0
  25. package/dist/e2e_tests/snippets.spec.js.map +1 -0
  26. package/dist/e2e_tests/syntaxHighlighting.spec.js +0 -1
  27. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -1
  28. package/dist/e2e_tests/syntaxValidation.spec.js +3 -3
  29. package/dist/e2e_tests/syntaxValidation.spec.js.map +1 -1
  30. package/dist/historyNavigation.js +1 -1
  31. package/dist/historyNavigation.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/lang-cypher/autocomplete.d.ts +4 -1
  36. package/dist/lang-cypher/autocomplete.js +79 -17
  37. package/dist/lang-cypher/autocomplete.js.map +1 -1
  38. package/dist/lang-cypher/contants.test.js +2 -2
  39. package/dist/lang-cypher/contants.test.js.map +1 -1
  40. package/dist/lang-cypher/createCypherTheme.js +34 -2
  41. package/dist/lang-cypher/createCypherTheme.js.map +1 -1
  42. package/dist/lang-cypher/langCypher.d.ts +5 -0
  43. package/dist/lang-cypher/langCypher.js +11 -5
  44. package/dist/lang-cypher/langCypher.js.map +1 -1
  45. package/dist/lang-cypher/signatureHelp.js +39 -22
  46. package/dist/lang-cypher/signatureHelp.js.map +1 -1
  47. package/dist/lang-cypher/utils.d.ts +2 -0
  48. package/dist/lang-cypher/utils.js +10 -0
  49. package/dist/lang-cypher/utils.js.map +1 -0
  50. package/dist/neo4jSetup.js +35 -1
  51. package/dist/neo4jSetup.js.map +1 -1
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +9 -9
  54. package/src/CypherEditor.tsx +233 -31
  55. package/src/e2e_tests/autoCompletion.spec.tsx +189 -18
  56. package/src/e2e_tests/configuration.spec.tsx +111 -0
  57. package/src/e2e_tests/debounce.spec.tsx +100 -0
  58. package/src/e2e_tests/e2eUtils.ts +11 -1
  59. package/src/e2e_tests/extraKeybindings.spec.tsx +0 -2
  60. package/src/e2e_tests/historyNavigation.spec.tsx +136 -17
  61. package/src/e2e_tests/sanityChecks.spec.tsx +0 -16
  62. package/src/e2e_tests/signatureHelp.spec.tsx +86 -18
  63. package/src/e2e_tests/snippets.spec.tsx +92 -0
  64. package/src/e2e_tests/syntaxHighlighting.spec.tsx +0 -2
  65. package/src/e2e_tests/syntaxValidation.spec.tsx +3 -3
  66. package/src/historyNavigation.ts +1 -1
  67. package/src/index.ts +4 -1
  68. package/src/lang-cypher/autocomplete.ts +95 -19
  69. package/src/lang-cypher/contants.test.ts +5 -2
  70. package/src/lang-cypher/createCypherTheme.ts +34 -2
  71. package/src/lang-cypher/langCypher.ts +17 -5
  72. package/src/lang-cypher/signatureHelp.ts +61 -30
  73. package/src/lang-cypher/utils.ts +9 -0
  74. package/src/neo4jSetup.tsx +51 -1
@@ -44,7 +44,7 @@ test('Signature help works for functions', async ({ page, mount }) => {
44
44
  />,
45
45
  );
46
46
 
47
- await expect(page.locator('.cm-tooltip-signature-help').last()).toBeVisible({
47
+ await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
48
48
  timeout: 2000,
49
49
  });
50
50
  });
@@ -60,7 +60,7 @@ test('Signature help works for procedures', async ({ page, mount }) => {
60
60
  />,
61
61
  );
62
62
 
63
- await expect(page.locator('.cm-tooltip-signature-help').last()).toBeVisible({
63
+ await expect(page.locator('.cm-signature-help-panel')).toBeVisible({
64
64
  timeout: 2000,
65
65
  });
66
66
  });
@@ -79,7 +79,7 @@ test('Signature help shows the description for the first argument', async ({
79
79
  />,
80
80
  );
81
81
 
82
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
82
+ const tooltip = page.locator('.cm-signature-help-panel');
83
83
 
84
84
  await testTooltip(tooltip, {
85
85
  includes: [
@@ -99,7 +99,7 @@ test('Signature help shows the description for the first argument when the curso
99
99
  <CypherEditor value={query} schema={testData.mockSchema} offset={21} />,
100
100
  );
101
101
 
102
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
102
+ const tooltip = page.locator('.cm-signature-help-panel');
103
103
 
104
104
  await testTooltip(tooltip, {
105
105
  includes: [
@@ -123,7 +123,7 @@ test('Signature help shows the description for the second argument', async ({
123
123
  />,
124
124
  );
125
125
 
126
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
126
+ const tooltip = page.locator('.cm-signature-help-panel');
127
127
 
128
128
  await testTooltip(tooltip, {
129
129
  includes: [
@@ -143,7 +143,7 @@ test('Signature help shows the description for the second argument when the curs
143
143
  <CypherEditor value={query} schema={testData.mockSchema} offset={27} />,
144
144
  );
145
145
 
146
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
146
+ const tooltip = page.locator('.cm-signature-help-panel');
147
147
 
148
148
  await testTooltip(tooltip, {
149
149
  includes: [
@@ -163,7 +163,7 @@ test('Signature help shows the description for the second argument when the curs
163
163
  <CypherEditor value={query} schema={testData.mockSchema} offset={28} />,
164
164
  );
165
165
 
166
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
166
+ const tooltip = page.locator('.cm-signature-help-panel');
167
167
 
168
168
  await testTooltip(tooltip, {
169
169
  includes: [
@@ -187,7 +187,7 @@ test('Signature help shows description for arguments with a space following a se
187
187
  />,
188
188
  );
189
189
 
190
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
190
+ const tooltip = page.locator('.cm-signature-help-panel');
191
191
 
192
192
  await testTooltip(tooltip, {
193
193
  includes: [
@@ -211,7 +211,7 @@ test('Signature help shows the description for the third argument', async ({
211
211
  />,
212
212
  );
213
213
 
214
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
214
+ const tooltip = page.locator('.cm-signature-help-panel');
215
215
 
216
216
  await testTooltip(tooltip, {
217
217
  includes: [
@@ -235,7 +235,7 @@ test('Signature help works on multiline queries', async ({ page, mount }) => {
235
235
  />,
236
236
  );
237
237
 
238
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
238
+ const tooltip = page.locator('.cm-signature-help-panel');
239
239
 
240
240
  await testTooltip(tooltip, {
241
241
  includes: [
@@ -258,14 +258,15 @@ test('Signature help only shows the description past the last argument', async (
258
258
  autofocus={true}
259
259
  />,
260
260
  );
261
+ 1;
261
262
 
262
- const tooltip = page.locator('.cm-tooltip-signature-help').last();
263
+ const tooltip = page.locator('.cm-signature-help-panel');
263
264
 
264
265
  await testTooltip(tooltip, {
265
266
  includes: [
266
- 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file',
267
+ 'apoc.import.csv(nodes :: LIST<MAP>, rels :: LIST<MAP>, config :: MAP)',
268
+ 'Imports `NODE` and `RELATIONSHIP` values with the given labels and types from the provided CSV file.',
267
269
  ],
268
- excludes: ['config :: MAP'],
269
270
  });
270
271
  });
271
272
 
@@ -283,9 +284,7 @@ test('Signature help does not show any help when method finished', async ({
283
284
  />,
284
285
  );
285
286
 
286
- await expect(
287
- page.locator('.cm-tooltip-signature-help').last(),
288
- ).not.toBeVisible({
287
+ await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
289
288
  timeout: 2000,
290
289
  });
291
290
  });
@@ -304,9 +303,78 @@ test('Signature help does not blow up on empty query', async ({
304
303
  />,
305
304
  );
306
305
 
306
+ await expect(page.locator('.cm-signature-help-panel')).not.toBeVisible({
307
+ timeout: 2000,
308
+ });
309
+ });
310
+
311
+ test('Signature help is shown below the text by default', async ({
312
+ page,
313
+ mount,
314
+ }) => {
315
+ // We need to introduce new lines to make sure there's
316
+ // enough space to show the tooltip above
317
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
318
+
319
+ await mount(
320
+ <CypherEditor
321
+ value={query}
322
+ schema={testData.mockSchema}
323
+ autofocus={true}
324
+ />,
325
+ );
326
+
327
+ await expect(
328
+ page.locator('.cm-signature-help-panel.cm-tooltip-below'),
329
+ ).toBeVisible({
330
+ timeout: 2000,
331
+ });
332
+ });
333
+
334
+ test('Setting showSignatureTooltipBelow to true shows the signature help above the text', async ({
335
+ page,
336
+ mount,
337
+ }) => {
338
+ // We need to introduce new lines to make sure there's
339
+ // enough space to show the tooltip above
340
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
341
+
342
+ await mount(
343
+ <CypherEditor
344
+ value={query}
345
+ schema={testData.mockSchema}
346
+ showSignatureTooltipBelow={true}
347
+ autofocus={true}
348
+ />,
349
+ );
350
+
351
+ await expect(
352
+ page.locator('.cm-signature-help-panel.cm-tooltip-below'),
353
+ ).toBeVisible({
354
+ timeout: 2000,
355
+ });
356
+ });
357
+
358
+ test('Setting showSignatureTooltipBelow to false shows the signature help above the text', async ({
359
+ page,
360
+ mount,
361
+ }) => {
362
+ // We need to introduce new lines to make sure there's
363
+ // enough space to show the tooltip above
364
+ const query = '\n\n\n\n\n\n\nRETURN abs(';
365
+
366
+ await mount(
367
+ <CypherEditor
368
+ value={query}
369
+ schema={testData.mockSchema}
370
+ showSignatureTooltipBelow={false}
371
+ autofocus={true}
372
+ />,
373
+ );
374
+
307
375
  await expect(
308
- page.locator('.cm-tooltip-signature-help').last(),
309
- ).not.toBeVisible({
376
+ page.locator('.cm-signature-help-panel.cm-tooltip-above'),
377
+ ).toBeVisible({
310
378
  timeout: 2000,
311
379
  });
312
380
  });
@@ -0,0 +1,92 @@
1
+ import { expect, test } from '@playwright/experimental-ct-react';
2
+ import { CypherEditor } from '../CypherEditor';
3
+
4
+ test('can complete pattern snippet', async ({ page, mount }) => {
5
+ await mount(<CypherEditor />);
6
+ const textField = page.getByRole('textbox');
7
+
8
+ await textField.fill('MATCH ()-[]->()');
9
+
10
+ await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
11
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
12
+
13
+ await textField.press('Tab');
14
+ await textField.press('Tab');
15
+
16
+ await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->( )');
17
+ });
18
+
19
+ test('can navigate snippet', async ({ page, mount }) => {
20
+ await mount(<CypherEditor />);
21
+ const textField = page.getByRole('textbox');
22
+
23
+ await textField.fill('CREATE INDEX abc FOR ()');
24
+
25
+ await page
26
+ .locator('.cm-tooltip-autocomplete')
27
+ .getByText('-[]-()', { exact: true })
28
+ .click();
29
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
30
+ await expect(page.locator('.cm-snippetField')).toHaveCount(2);
31
+
32
+ await textField.press('Tab');
33
+ await textField.press('Shift+Tab');
34
+
35
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[ ]-( )');
36
+
37
+ await textField.press('a');
38
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a]-( )');
39
+
40
+ await textField.press('Escape');
41
+ await textField.press('Escape');
42
+ await expect(page.locator('.cm-snippetField')).toHaveCount(0);
43
+ await textField.press('Tab');
44
+ await expect(textField).toHaveText('CREATE INDEX abc FOR ()-[a ]-( )');
45
+ });
46
+
47
+ test('can accept completion inside pattern snippet', async ({
48
+ page,
49
+ mount,
50
+ }) => {
51
+ await mount(<CypherEditor schema={{ labels: ['City'] }} />);
52
+ const textField = page.getByRole('textbox');
53
+
54
+ await textField.fill('MATCH ()-[]->()');
55
+
56
+ await page.locator('.cm-tooltip-autocomplete').getByText('-[]->()').click();
57
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
58
+
59
+ // move to node
60
+ await textField.press('Tab');
61
+
62
+ // get & accept completion
63
+ await textField.press(':');
64
+ await expect(
65
+ page.locator('.cm-tooltip-autocomplete').getByText('City'),
66
+ ).toBeVisible();
67
+
68
+ await textField.press('Tab');
69
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
70
+
71
+ // tab out of the snippet
72
+ await textField.press('Tab');
73
+
74
+ await expect(textField).toHaveText('MATCH ()-[]->()-[ ]->(:City)');
75
+ });
76
+
77
+ test('does not automatically open completion panel for expressions after snippet trigger char', async ({
78
+ page,
79
+ mount,
80
+ }) => {
81
+ await mount(<CypherEditor />);
82
+ const textField = page.getByRole('textbox');
83
+
84
+ await textField.fill('RETURN (1)');
85
+
86
+ // expect the panel to not show up
87
+ await expect(page.locator('.cm-tooltip-autocomplete')).not.toBeVisible();
88
+
89
+ // unless manually triggered
90
+ await textField.press('Control+ ');
91
+ await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible();
92
+ });
@@ -3,8 +3,6 @@ import { CypherEditor } from '../CypherEditor';
3
3
  import { darkThemeConstants, lightThemeConstants } from '../themes';
4
4
  import { CypherEditorPage } from './e2eUtils';
5
5
 
6
- test.use({ viewport: { width: 500, height: 500 } });
7
-
8
6
  test('light theme highlighting', async ({ page, mount }) => {
9
7
  const editorPage = new CypherEditorPage(page);
10
8
  const query = `
@@ -106,7 +106,7 @@ test('Semantic errors are correctly accumulated', async ({ page, mount }) => {
106
106
 
107
107
  await editorPage.checkErrorMessage(
108
108
  'MATCH (n)',
109
- 'Query cannot conclude with MATCH (must be a RETURN clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
109
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
110
110
  );
111
111
 
112
112
  await editorPage.checkErrorMessage(
@@ -126,7 +126,7 @@ test('Multiline errors are correctly placed', async ({ page, mount }) => {
126
126
 
127
127
  await editorPage.checkErrorMessage(
128
128
  'MATCH (n)',
129
- 'Query cannot conclude with MATCH (must be a RETURN clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
129
+ 'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
130
130
  );
131
131
 
132
132
  await editorPage.checkErrorMessage(
@@ -146,7 +146,7 @@ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
146
146
 
147
147
  await editorPage.checkErrorMessage(
148
148
  '-1',
149
- 'Query cannot conclude with CALL (must be a RETURN clause, an update clause, a unit subquery call, or a procedure call with no YIELD)',
149
+ 'Query cannot conclude with CALL (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
150
150
  );
151
151
 
152
152
  await editorPage.checkErrorMessage(
@@ -97,7 +97,7 @@ function navigateHistory(view: EditorView, direction: HistoryNavigation) {
97
97
  view.dispatch(
98
98
  view.state.update({
99
99
  effects: moveInHistory.of(direction),
100
- selection: { anchor: view.state.doc.length },
100
+ selection: { anchor: direction === 'BACK' ? 0 : view.state.doc.length },
101
101
  }),
102
102
  );
103
103
 
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
- export { CypherParser } from '@neo4j-cypher/language-support';
1
+ export {
2
+ CypherParser,
3
+ _internalFeatureFlags,
4
+ } from '@neo4j-cypher/language-support';
2
5
  export { CypherEditor } from './CypherEditor';
3
6
  export { cypher } from './lang-cypher/langCypher';
4
7
  export { darkThemeConstants, lightThemeConstants } from './themes';
@@ -1,8 +1,16 @@
1
- import { CompletionSource, snippet } from '@codemirror/autocomplete';
1
+ import {
2
+ Completion,
3
+ CompletionSource,
4
+ snippet,
5
+ } from '@codemirror/autocomplete';
2
6
  import { autocomplete } from '@neo4j-cypher/language-support';
3
- import { CompletionItemKind } from 'vscode-languageserver-types';
7
+ import {
8
+ CompletionItemKind,
9
+ CompletionItemTag,
10
+ } from 'vscode-languageserver-types';
4
11
  import { CompletionItemIcons } from '../icons';
5
12
  import type { CypherConfig } from './langCypher';
13
+ import { getDocString } from './utils';
6
14
 
7
15
  const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
8
16
  const map: Record<CompletionItemKind, CompletionItemIcons> = {
@@ -37,12 +45,22 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
37
45
  return map[c];
38
46
  };
39
47
 
48
+ export const completionStyles: (
49
+ completion: Completion & { deprecated?: boolean },
50
+ ) => string = (completion) => {
51
+ if (completion.deprecated) {
52
+ return 'cm-deprecated-completion';
53
+ } else {
54
+ return null;
55
+ }
56
+ };
57
+
40
58
  export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
41
59
  (config) => (context) => {
42
- const textUntilCursor = context.state.doc.toString().slice(0, context.pos);
60
+ const documentText = context.state.doc.toString();
43
61
 
44
62
  const triggerCharacters = ['.', ':', '{', '$', ')'];
45
- const lastCharacter = textUntilCursor.slice(-1);
63
+ const lastCharacter = documentText.at(context.pos - 1);
46
64
 
47
65
  const lastWord = context.matchBefore(/\w*/);
48
66
  const inWord = lastWord.from !== lastWord.to;
@@ -59,23 +77,81 @@ export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
59
77
  }
60
78
 
61
79
  const options = autocomplete(
62
- textUntilCursor,
80
+ documentText,
63
81
  config.schema ?? {},
64
- undefined,
82
+ context.pos,
65
83
  context.explicit,
66
84
  );
67
85
 
68
- return {
69
- from: context.matchBefore(/(\w|\$)*$/).from,
70
- options: options.map((o) => ({
71
- label: o.label,
72
- type: completionKindToCodemirrorIcon(o.kind),
73
- apply:
74
- o.kind === CompletionItemKind.Snippet
75
- ? // codemirror requires an empty snippet space to be able to tab out of the completion
76
- snippet((o.insertText ?? o.label) + '${}')
77
- : undefined,
78
- detail: o.detail,
79
- })),
80
- };
86
+ if (config.featureFlags?.signatureInfoOnAutoCompletions) {
87
+ return {
88
+ from: context.matchBefore(/(\w|\$)*$/).from,
89
+ options: options.map((o) => {
90
+ let maybeInfo = {};
91
+ let emptyInfo = true;
92
+ const newDiv = document.createElement('div');
93
+
94
+ if (o.signature) {
95
+ const header = document.createElement('p');
96
+ header.setAttribute('class', 'cm-completionInfo-signature');
97
+ header.textContent = o.signature;
98
+ if (header.textContent.length > 0) {
99
+ emptyInfo = false;
100
+ newDiv.appendChild(header);
101
+ }
102
+ }
103
+
104
+ if (o.documentation) {
105
+ const paragraph = document.createElement('p');
106
+ paragraph.textContent = getDocString(o.documentation);
107
+ if (paragraph.textContent.length > 0) {
108
+ emptyInfo = false;
109
+ newDiv.appendChild(paragraph);
110
+ }
111
+ }
112
+
113
+ if (!emptyInfo) {
114
+ maybeInfo = {
115
+ info: () => Promise.resolve(newDiv),
116
+ };
117
+ }
118
+ const deprecated =
119
+ o.tags?.find((tag) => tag === CompletionItemTag.Deprecated) ??
120
+ false;
121
+ // The negative boost moves the deprecation down the list
122
+ // so we offer the user the completions that are
123
+ // deprecated the last
124
+ const maybeDeprecated = deprecated
125
+ ? { boost: -99, deprecated: true }
126
+ : {};
127
+
128
+ return {
129
+ label: o.label,
130
+ type: completionKindToCodemirrorIcon(o.kind),
131
+ apply:
132
+ o.kind === CompletionItemKind.Snippet
133
+ ? // codemirror requires an empty snippet space to be able to tab out of the completion
134
+ snippet((o.insertText ?? o.label) + '${}')
135
+ : undefined,
136
+ detail: o.detail,
137
+ ...maybeDeprecated,
138
+ ...maybeInfo,
139
+ };
140
+ }),
141
+ };
142
+ } else {
143
+ return {
144
+ from: context.matchBefore(/(\w|\$)*$/).from,
145
+ options: options.map((o) => ({
146
+ label: o.label,
147
+ type: completionKindToCodemirrorIcon(o.kind),
148
+ apply:
149
+ o.kind === CompletionItemKind.Snippet
150
+ ? // codemirror requires an empty snippet space to be able to tab out of the completion
151
+ snippet((o.insertText ?? o.label) + '${}')
152
+ : undefined,
153
+ detail: o.detail,
154
+ })),
155
+ };
156
+ }
81
157
  };
@@ -1,5 +1,8 @@
1
1
  import { tags } from '@lezer/highlight';
2
- import { applySyntaxColouring } from '@neo4j-cypher/language-support';
2
+ import {
3
+ applySyntaxColouring,
4
+ CypherTokenType,
5
+ } from '@neo4j-cypher/language-support';
3
6
  import { tokenTypeToStyleTag } from './constants';
4
7
 
5
8
  const cypherQueryWithAllTokenTypes = `MATCH (variable :Label)-[:REL_TYPE]->()
@@ -54,7 +57,7 @@ test('correctly parses all cypher token types to style tags', () => {
54
57
  ]);
55
58
 
56
59
  const styleTags = tokenTypes.map((tokenType) => {
57
- if (tokenType === 'none') return undefined;
60
+ if (tokenType === CypherTokenType.none) return undefined;
58
61
  return tokenTypeToStyleTag[tokenType];
59
62
  });
60
63
  const correctTags = [
@@ -5,6 +5,7 @@ import {
5
5
  } from '@codemirror/language';
6
6
  import { Extension } from '@codemirror/state';
7
7
  import { EditorView } from '@codemirror/view';
8
+ import { CypherTokenType } from '@neo4j-cypher/language-support';
8
9
  import { StyleSpec } from 'style-mod';
9
10
  import { HighlightedCypherTokenTypes, tokenTypeToStyleTag } from './constants';
10
11
  import {
@@ -64,8 +65,9 @@ export const createCypherTheme = ({
64
65
  color: settings.gutterForeground,
65
66
  border: 'none',
66
67
  },
67
- '&.cm-editor .cm-scroller': {
68
+ '&.cm-editor': {
68
69
  fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
70
+ height: '100%',
69
71
  },
70
72
  '.cm-content': {
71
73
  caretColor: settings.cursor,
@@ -99,7 +101,37 @@ export const createCypherTheme = ({
99
101
  color: settings.autoCompletionPanel.matchingTextColor,
100
102
  textDecoration: 'none',
101
103
  },
104
+ '& .cm-signature-help-panel': {
105
+ backgroundColor: settings.autoCompletionPanel.backgroundColor,
106
+ maxWidth: '700px',
107
+ maxHeight: '250px',
108
+ fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
109
+ },
110
+ '& .cm-signature-help-panel-contents': {
111
+ overflow: 'auto',
112
+ maxHeight: '250px',
113
+ },
114
+ '& .cm-signature-help-panel-current-argument': {
115
+ color: settings.autoCompletionPanel.matchingTextColor,
116
+ fontWeight: 'bold',
117
+ },
118
+ '& .cm-signature-help-panel-separator': {
119
+ borderBottom: '1px solid #ccc',
120
+ },
121
+ '& .cm-signature-help-panel-name': {
122
+ padding: '5px',
123
+ },
124
+ '& .cm-signature-help-panel-description': {
125
+ padding: '5px',
126
+ },
127
+ '.cm-completionInfo-signature': {
128
+ color: 'darkgrey',
129
+ },
130
+ '.cm-deprecated-completion': {
131
+ 'text-decoration': 'line-through',
132
+ },
102
133
  '.cm-tooltip-autocomplete': {
134
+ maxWidth: '430px',
103
135
  '& > ul > li[aria-selected]': {
104
136
  backgroundColor: settings.autoCompletionPanel.selectedColor,
105
137
  color: settings.foreground,
@@ -204,7 +236,7 @@ export const createCypherTheme = ({
204
236
  ([token, color]: [HighlightedCypherTokenTypes, string]): TagStyle => ({
205
237
  tag: tokenTypeToStyleTag[token],
206
238
  color,
207
- class: token === 'consoleCommand' ? 'cm-bold' : undefined,
239
+ class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined,
208
240
  }),
209
241
  );
210
242
  const highlightStyle = HighlightStyle.define(styles);
@@ -1,13 +1,14 @@
1
+ import { autocompletion } from '@codemirror/autocomplete';
1
2
  import {
2
3
  defineLanguageFacet,
3
4
  Language,
4
5
  LanguageSupport,
5
6
  } from '@codemirror/language';
6
7
  import {
7
- setConsoleCommandsEnabled,
8
+ _internalFeatureFlags,
8
9
  type DbSchema,
9
10
  } from '@neo4j-cypher/language-support';
10
- import { cypherAutocomplete } from './autocomplete';
11
+ import { completionStyles, cypherAutocomplete } from './autocomplete';
11
12
  import { ParserAdapter } from './parser-adapter';
12
13
  import { signatureHelpTooltip } from './signatureHelp';
13
14
  import { cypherLinter, semanticAnalysisLinter } from './syntaxValidation';
@@ -19,20 +20,31 @@ const facet = defineLanguageFacet({
19
20
 
20
21
  export type CypherConfig = {
21
22
  lint?: boolean;
23
+ showSignatureTooltipBelow?: boolean;
24
+ featureFlags?: {
25
+ signatureInfoOnAutoCompletions?: boolean;
26
+ consoleCommands?: boolean;
27
+ };
22
28
  schema?: DbSchema;
23
29
  useLightVersion: boolean;
24
30
  setUseLightVersion?: (useLightVersion: boolean) => void;
25
31
  };
26
32
 
27
33
  export function cypher(config: CypherConfig) {
28
- setConsoleCommandsEnabled(true);
34
+ const featureFlags = config.featureFlags;
35
+ // We allow to override the consoleCommands feature flag
36
+ if (featureFlags.consoleCommands !== undefined) {
37
+ _internalFeatureFlags.consoleCommands = featureFlags.consoleCommands;
38
+ }
39
+
29
40
  const parserAdapter = new ParserAdapter(facet, config);
30
41
 
31
42
  const cypherLanguage = new Language(facet, parserAdapter, [], 'cypher');
32
43
 
33
44
  return new LanguageSupport(cypherLanguage, [
34
- cypherLanguage.data.of({
35
- autocomplete: cypherAutocomplete(config),
45
+ autocompletion({
46
+ override: [cypherAutocomplete(config)],
47
+ optionClass: completionStyles,
36
48
  }),
37
49
  cypherLinter(config),
38
50
  semanticAnalysisLinter(config),