@neo4j-cypher/react-codemirror 2.0.0-next.8 → 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 (63) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/CypherEditor.d.ts +28 -1
  3. package/dist/CypherEditor.js +70 -11
  4. package/dist/CypherEditor.js.map +1 -1
  5. package/dist/e2e_tests/autoCompletion.spec.js +117 -1
  6. package/dist/e2e_tests/autoCompletion.spec.js.map +1 -1
  7. package/dist/e2e_tests/configuration.spec.js +11 -1
  8. package/dist/e2e_tests/configuration.spec.js.map +1 -1
  9. package/dist/e2e_tests/debounce.spec.d.ts +1 -0
  10. package/dist/e2e_tests/debounce.spec.js +63 -0
  11. package/dist/e2e_tests/debounce.spec.js.map +1 -0
  12. package/dist/e2e_tests/extraKeybindings.spec.js +0 -1
  13. package/dist/e2e_tests/extraKeybindings.spec.js.map +1 -1
  14. package/dist/e2e_tests/historyNavigation.spec.js +107 -16
  15. package/dist/e2e_tests/historyNavigation.spec.js.map +1 -1
  16. package/dist/e2e_tests/sanityChecks.spec.js +0 -1
  17. package/dist/e2e_tests/sanityChecks.spec.js.map +1 -1
  18. package/dist/e2e_tests/signatureHelp.spec.js +27 -0
  19. package/dist/e2e_tests/signatureHelp.spec.js.map +1 -1
  20. package/dist/e2e_tests/snippets.spec.js +0 -1
  21. package/dist/e2e_tests/snippets.spec.js.map +1 -1
  22. package/dist/e2e_tests/syntaxHighlighting.spec.js +0 -1
  23. package/dist/e2e_tests/syntaxHighlighting.spec.js.map +1 -1
  24. package/dist/historyNavigation.js +1 -1
  25. package/dist/historyNavigation.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/lang-cypher/autocomplete.d.ts +4 -1
  30. package/dist/lang-cypher/autocomplete.js +79 -17
  31. package/dist/lang-cypher/autocomplete.js.map +1 -1
  32. package/dist/lang-cypher/contants.test.js +2 -2
  33. package/dist/lang-cypher/contants.test.js.map +1 -1
  34. package/dist/lang-cypher/createCypherTheme.js +9 -2
  35. package/dist/lang-cypher/createCypherTheme.js.map +1 -1
  36. package/dist/lang-cypher/langCypher.d.ts +5 -0
  37. package/dist/lang-cypher/langCypher.js +11 -5
  38. package/dist/lang-cypher/langCypher.js.map +1 -1
  39. package/dist/lang-cypher/signatureHelp.js +4 -3
  40. package/dist/lang-cypher/signatureHelp.js.map +1 -1
  41. package/dist/lang-cypher/utils.d.ts +2 -0
  42. package/dist/lang-cypher/utils.js +10 -0
  43. package/dist/lang-cypher/utils.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +9 -9
  46. package/src/CypherEditor.tsx +131 -21
  47. package/src/e2e_tests/autoCompletion.spec.tsx +206 -2
  48. package/src/e2e_tests/configuration.spec.tsx +16 -2
  49. package/src/e2e_tests/debounce.spec.tsx +100 -0
  50. package/src/e2e_tests/extraKeybindings.spec.tsx +0 -2
  51. package/src/e2e_tests/historyNavigation.spec.tsx +136 -17
  52. package/src/e2e_tests/sanityChecks.spec.tsx +0 -2
  53. package/src/e2e_tests/signatureHelp.spec.tsx +71 -0
  54. package/src/e2e_tests/snippets.spec.tsx +0 -2
  55. package/src/e2e_tests/syntaxHighlighting.spec.tsx +0 -2
  56. package/src/historyNavigation.ts +1 -1
  57. package/src/index.ts +4 -1
  58. package/src/lang-cypher/autocomplete.ts +95 -19
  59. package/src/lang-cypher/contants.test.ts +5 -2
  60. package/src/lang-cypher/createCypherTheme.ts +9 -3
  61. package/src/lang-cypher/langCypher.ts +17 -5
  62. package/src/lang-cypher/signatureHelp.ts +5 -3
  63. package/src/lang-cypher/utils.ts +9 -0
@@ -2,8 +2,6 @@ import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
  import { CypherEditorPage } from './e2eUtils';
4
4
 
5
- test.use({ viewport: { width: 500, height: 500 } });
6
-
7
5
  test('respects preloaded history', async ({ page, mount }) => {
8
6
  const editorPage = new CypherEditorPage(page);
9
7
 
@@ -27,13 +25,75 @@ test('respects preloaded history', async ({ page, mount }) => {
27
25
  await editorPage.getEditor().press('ArrowUp');
28
26
  await expect(page.getByText('second')).toBeVisible();
29
27
 
28
+ // First arrow down is to get to end of line
29
+ await editorPage.getEditor().press('ArrowDown');
30
30
  await editorPage.getEditor().press('ArrowDown');
31
31
  await expect(page.getByText('first')).toBeVisible();
32
32
  await editorPage.getEditor().press('ArrowDown');
33
33
  await expect(page.getByText(initialValue)).toBeVisible();
34
34
  });
35
35
 
36
- test('can execute queries and see them in history', async ({ page, mount }) => {
36
+ test('can add new lines without onExecute', async ({ page, mount }) => {
37
+ const editorPage = new CypherEditorPage(page);
38
+
39
+ const editorComponent = await mount(<CypherEditor />);
40
+
41
+ // Ctrl-Enter does nothing when onExecute is false
42
+ await editorPage.getEditor().press('Control+Enter');
43
+ await expect(editorComponent).toHaveText('1\n', {
44
+ useInnerText: true,
45
+ });
46
+
47
+ // Enter adds new lines
48
+ await editorPage.getEditor().fill('Brock');
49
+ await editorPage.getEditor().press('Enter');
50
+ await editorPage.getEditor().press('Enter');
51
+ await expect(editorComponent).toHaveText('1\n2\n3\nBrock', {
52
+ useInnerText: true,
53
+ });
54
+
55
+ // Shift-Enter adds new lines
56
+ await editorPage.getEditor().press('Shift+Enter');
57
+ await editorPage.getEditor().press('Shift+Enter');
58
+ await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', {
59
+ useInnerText: true,
60
+ });
61
+ });
62
+
63
+ test('can add new lines with newLineOnEnter and without onExecute', async ({
64
+ page,
65
+ mount,
66
+ }) => {
67
+ const editorPage = new CypherEditorPage(page);
68
+
69
+ const editorComponent = await mount(<CypherEditor newLineOnEnter />);
70
+
71
+ // Ctrl-Enter does nothing when onExecute is false
72
+ await editorPage.getEditor().press('Control+Enter');
73
+ await expect(editorComponent).toHaveText('1\n', {
74
+ useInnerText: true,
75
+ });
76
+
77
+ // Enter adds new lines
78
+ await editorPage.getEditor().fill('Brock');
79
+ await editorPage.getEditor().press('Enter');
80
+ await editorPage.getEditor().press('Enter');
81
+ await expect(editorComponent).toHaveText('1\n2\n3\nBrock', {
82
+ useInnerText: true,
83
+ });
84
+
85
+ // Shift-Enter adds new lines
86
+ await editorPage.getEditor().press('Shift+Enter');
87
+ await editorPage.getEditor().press('Shift+Enter');
88
+ await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', {
89
+ useInnerText: true,
90
+ });
91
+ });
92
+
93
+ test('can execute queries and see them in history with newLineOnEnter', async ({
94
+ page,
95
+ mount,
96
+ }) => {
37
97
  const editorPage = new CypherEditorPage(page);
38
98
 
39
99
  const initialValue = `MATCH (n)
@@ -49,6 +109,7 @@ RETURN n;`;
49
109
  value={initialValue}
50
110
  history={history}
51
111
  onExecute={onExecute}
112
+ newLineOnEnter
52
113
  />,
53
114
  );
54
115
 
@@ -63,12 +124,12 @@ RETURN n;`;
63
124
  await editorPage.getEditor().press('Meta+Enter');
64
125
  expect(history.length).toBe(1);
65
126
 
66
- // Ensure only enter doesn't execute query
127
+ // Ensure cmd+enter is required in multiline
67
128
  await editorPage.getEditor().fill('multiline');
68
129
  await editorPage.getEditor().press('Enter');
69
130
  await editorPage.getEditor().press('Enter');
70
131
  await editorPage.getEditor().press('Enter');
71
- await editorPage.getEditor().press('Enter');
132
+ await editorPage.getEditor().press('Shift+Enter');
72
133
  await page.keyboard.type('entry');
73
134
  expect(history.length).toBe(1);
74
135
 
@@ -102,10 +163,15 @@ RETURN n;`;
102
163
  await expect(page.getByText('multiline')).toBeVisible();
103
164
 
104
165
  // arrow movements don't matter until bottom is hit
166
+ await editorPage.getEditor().press('ArrowDown');
167
+ await editorPage.getEditor().press('ArrowDown');
105
168
  await editorPage.getEditor().press('ArrowUp');
106
169
  await editorPage.getEditor().press('ArrowUp');
107
170
  await editorPage.getEditor().press('ArrowDown');
108
171
  await editorPage.getEditor().press('ArrowDown');
172
+ await editorPage.getEditor().press('ArrowDown');
173
+ await editorPage.getEditor().press('ArrowDown');
174
+ await editorPage.getEditor().press('ArrowDown');
109
175
 
110
176
  // editor still multiline
111
177
  await expect(page.getByText('multiline')).toBeVisible();
@@ -115,6 +181,59 @@ RETURN n;`;
115
181
  await expect(page.getByText('draft')).toBeVisible();
116
182
  });
117
183
 
184
+ test('can execute queries without newLineOnEnter', async ({ page, mount }) => {
185
+ const editorPage = new CypherEditorPage(page);
186
+
187
+ const initialValue = 'Brock';
188
+
189
+ const history: string[] = [];
190
+ const onExecute = (cmd: string) => {
191
+ history.unshift(cmd);
192
+ };
193
+
194
+ const editorComponent = await mount(
195
+ <CypherEditor value={initialValue} onExecute={onExecute} />,
196
+ );
197
+
198
+ // Cmd/Control still executes initial query
199
+ await editorPage.getEditor().press('Control+Enter');
200
+ await editorPage.getEditor().press('Meta+Enter');
201
+ expect(history.length).toBe(1);
202
+
203
+ // Ensure query execution doesn't fire if the query is only whitespace
204
+ await editorPage.getEditor().fill(' ');
205
+ await editorPage.getEditor().press('Control+Enter');
206
+ await editorPage.getEditor().press('Meta+Enter');
207
+ await editorPage.getEditor().press('Enter');
208
+ expect(history.length).toBe(1);
209
+
210
+ // Ensure enter executes query when in single line
211
+ await editorPage.getEditor().fill('Misty');
212
+ await editorPage.getEditor().press('Enter');
213
+ expect(history.length).toBe(2);
214
+ expect(history).toEqual(['Misty', 'Brock']);
215
+
216
+ // Ensure cmd+enter is required in multiline
217
+ await editorPage.getEditor().fill('multiline');
218
+ await editorPage.getEditor().press('Shift+Enter');
219
+ await editorPage.getEditor().press('Enter');
220
+ await editorPage.getEditor().press('A');
221
+
222
+ // line numbers and the text
223
+ await expect(editorComponent).toHaveText('1\n2\n3\nmultiline\nA', {
224
+ useInnerText: true,
225
+ });
226
+ await editorPage.getEditor().press('Enter');
227
+ await editorPage.getEditor().press('Enter');
228
+ await editorPage.getEditor().press('Enter');
229
+ await editorPage.getEditor().press('Enter');
230
+ expect(history.length).toBe(2);
231
+
232
+ await editorPage.getEditor().press('Control+Enter');
233
+ await editorPage.getEditor().press('Meta+Enter');
234
+ expect(history.length).toBe(3);
235
+ });
236
+
118
237
  test('can navigate with cmd+up as well', async ({ page, mount }) => {
119
238
  const editorPage = new CypherEditorPage(page);
120
239
  const isMac = process.platform === 'darwin';
@@ -127,11 +246,11 @@ test('can navigate with cmd+up as well', async ({ page, mount }) => {
127
246
  <CypherEditor
128
247
  value={initialValue}
129
248
  history={[
249
+ 'first',
130
250
  `one
131
251
  multiline
132
252
  entry
133
253
  .`,
134
- 'second',
135
254
  ]}
136
255
  onExecute={() => {
137
256
  /* needed to turn on history movements */
@@ -139,24 +258,24 @@ entry
139
258
  />,
140
259
  );
141
260
 
261
+ await editorPage.getEditor().press(metaUp);
262
+ await expect(page.getByText('first')).toBeVisible();
263
+
264
+ // move in history
142
265
  await editorPage.getEditor().press(metaUp);
143
266
  await expect(page.getByText('multiline')).toBeVisible();
144
267
 
145
- // Single meta up moves all the way to top of editor on mac
268
+ // Single meta down moves all the way to top of editor on mac
146
269
  if (isMac) {
147
- await editorPage.getEditor().press(metaUp);
270
+ await editorPage.getEditor().press(metaDown);
148
271
  } else {
149
- await editorPage.getEditor().press('ArrowUp');
150
- await editorPage.getEditor().press('ArrowUp');
151
- await editorPage.getEditor().press('ArrowUp');
152
- await editorPage.getEditor().press('ArrowUp');
272
+ await editorPage.getEditor().press('ArrowDown');
273
+ await editorPage.getEditor().press('ArrowDown');
274
+ await editorPage.getEditor().press('ArrowDown');
275
+ await editorPage.getEditor().press('ArrowDown');
153
276
  }
154
- // move in history
155
- await editorPage.getEditor().press(metaUp);
156
- await expect(page.getByText('second')).toBeVisible();
157
-
158
277
  await editorPage.getEditor().press(metaDown);
159
- await expect(page.getByText('multiline')).toBeVisible();
278
+ await expect(page.getByText('first')).toBeVisible();
160
279
  });
161
280
 
162
281
  test('test onExecute', async ({ page, mount }) => {
@@ -1,8 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
 
4
- test.use({ viewport: { width: 500, height: 500 } });
5
-
6
4
  test('can mount the editor with text', async ({ mount }) => {
7
5
  const component = await mount(<CypherEditor value="MATCH (n) RETURN n;" />);
8
6
 
@@ -307,3 +307,74 @@ test('Signature help does not blow up on empty query', async ({
307
307
  timeout: 2000,
308
308
  });
309
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
+
375
+ await expect(
376
+ page.locator('.cm-signature-help-panel.cm-tooltip-above'),
377
+ ).toBeVisible({
378
+ timeout: 2000,
379
+ });
380
+ });
@@ -1,8 +1,6 @@
1
1
  import { expect, test } from '@playwright/experimental-ct-react';
2
2
  import { CypherEditor } from '../CypherEditor';
3
3
 
4
- test.use({ viewport: { width: 500, height: 500 } });
5
-
6
4
  test('can complete pattern snippet', async ({ page, mount }) => {
7
5
  await mount(<CypherEditor />);
8
6
  const textField = page.getByRole('textbox');
@@ -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 = `
@@ -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 {
@@ -108,7 +109,7 @@ export const createCypherTheme = ({
108
109
  },
109
110
  '& .cm-signature-help-panel-contents': {
110
111
  overflow: 'auto',
111
- maxHeight: '100%',
112
+ maxHeight: '250px',
112
113
  },
113
114
  '& .cm-signature-help-panel-current-argument': {
114
115
  color: settings.autoCompletionPanel.matchingTextColor,
@@ -123,7 +124,12 @@ export const createCypherTheme = ({
123
124
  '& .cm-signature-help-panel-description': {
124
125
  padding: '5px',
125
126
  },
126
-
127
+ '.cm-completionInfo-signature': {
128
+ color: 'darkgrey',
129
+ },
130
+ '.cm-deprecated-completion': {
131
+ 'text-decoration': 'line-through',
132
+ },
127
133
  '.cm-tooltip-autocomplete': {
128
134
  maxWidth: '430px',
129
135
  '& > ul > li[aria-selected]': {
@@ -230,7 +236,7 @@ export const createCypherTheme = ({
230
236
  ([token, color]: [HighlightedCypherTokenTypes, string]): TagStyle => ({
231
237
  tag: tokenTypeToStyleTag[token],
232
238
  color,
233
- class: token === 'consoleCommand' ? 'cm-bold' : undefined,
239
+ class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined,
234
240
  }),
235
241
  );
236
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),
@@ -3,6 +3,7 @@ import { showTooltip, Tooltip } from '@codemirror/view';
3
3
  import { signatureHelp } from '@neo4j-cypher/language-support';
4
4
  import { SignatureInformation } from 'vscode-languageserver-types';
5
5
  import { CypherConfig } from './langCypher';
6
+ import { getDocString } from './utils';
6
7
 
7
8
  function getTriggerCharacter(query: string, caretPosition: number) {
8
9
  let i = caretPosition - 1;
@@ -27,7 +28,7 @@ const createSignatureHelpElement =
27
28
  }) =>
28
29
  () => {
29
30
  const parameters = signature.parameters;
30
- const doc = signature.documentation.toString();
31
+ const doc = getDocString(signature.documentation);
31
32
  const dom = document.createElement('div');
32
33
  dom.className = 'cm-signature-help-panel';
33
34
 
@@ -100,12 +101,13 @@ function getSignatureHelpTooltip(
100
101
  signatures[activeSignature].documentation !== undefined
101
102
  ) {
102
103
  const signature = signatures[activeSignature];
104
+ const showSignatureTooltipBelow =
105
+ config.showSignatureTooltipBelow ?? true;
103
106
 
104
107
  result = [
105
108
  {
106
109
  pos: caretPosition,
107
- above: true,
108
- strictSide: true,
110
+ above: !showSignatureTooltipBelow,
109
111
  arrow: true,
110
112
  create: createSignatureHelpElement({ signature, activeParameter }),
111
113
  },
@@ -0,0 +1,9 @@
1
+ import { MarkupContent } from 'vscode-languageserver-types';
2
+
3
+ export function getDocString(result: string | MarkupContent): string {
4
+ if (MarkupContent.is(result)) {
5
+ result.value;
6
+ } else {
7
+ return result;
8
+ }
9
+ }