@neo4j-cypher/react-codemirror 2.0.0-next.40 → 2.0.0-next.42
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/CHANGELOG.md +34 -0
- package/dist/src/CypherEditor.d.ts +12 -6
- package/dist/src/CypherEditor.js +11 -3
- package/dist/src/CypherEditor.js.map +1 -1
- package/dist/src/CypherEditor.test.js +20 -11
- package/dist/src/CypherEditor.test.js.map +1 -1
- package/dist/src/editorActions.js +83 -18
- package/dist/src/editorActions.js.map +1 -1
- package/dist/src/historyNavigation.d.ts +1 -1
- package/dist/src/historyNavigation.js.map +1 -1
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lang-cypher/autocomplete.d.ts +1 -1
- package/dist/src/lang-cypher/autocomplete.js +1 -1
- package/dist/src/lang-cypher/autocomplete.js.map +1 -1
- package/dist/src/lang-cypher/constants.d.ts +2 -2
- package/dist/src/lang-cypher/constants.js.map +1 -1
- package/dist/src/lang-cypher/createCypherTheme.d.ts +2 -2
- package/dist/src/lang-cypher/createCypherTheme.js +47 -1
- package/dist/src/lang-cypher/createCypherTheme.js.map +1 -1
- package/dist/src/lang-cypher/hover/hover.d.ts +3 -0
- package/dist/src/lang-cypher/hover/hover.js +26 -0
- package/dist/src/lang-cypher/hover/hover.js.map +1 -0
- package/dist/src/lang-cypher/hover/hoverRender.d.ts +9 -0
- package/dist/src/lang-cypher/hover/hoverRender.js +142 -0
- package/dist/src/lang-cypher/hover/hoverRender.js.map +1 -0
- package/dist/src/lang-cypher/hover/hoverRender.test.js +123 -0
- package/dist/src/lang-cypher/hover/hoverRender.test.js.map +1 -0
- package/dist/src/lang-cypher/langCypher.d.ts +2 -1
- package/dist/src/lang-cypher/langCypher.js +3 -0
- package/dist/src/lang-cypher/langCypher.js.map +1 -1
- package/dist/src/lang-cypher/lintWorker.mjs +168 -166
- package/dist/src/lang-cypher/parser-adapter.d.ts +6 -4
- package/dist/src/lang-cypher/parser-adapter.js +4 -1
- package/dist/src/lang-cypher/parser-adapter.js.map +1 -1
- package/dist/src/lang-cypher/prism-global.js +7 -0
- package/dist/src/lang-cypher/prism-global.js.map +1 -0
- package/dist/src/lang-cypher/signatureHelp.d.ts +2 -2
- package/dist/src/lang-cypher/signatureHelp.js +14 -24
- package/dist/src/lang-cypher/signatureHelp.js.map +1 -1
- package/dist/src/lang-cypher/syntaxValidation.d.ts +1 -1
- package/dist/src/lang-cypher/syntaxValidation.js +7 -2
- package/dist/src/lang-cypher/syntaxValidation.js.map +1 -1
- package/dist/src/lang-cypher/tooltipHighlighting.d.ts +21 -0
- package/dist/src/lang-cypher/tooltipHighlighting.js +76 -0
- package/dist/src/lang-cypher/tooltipHighlighting.js.map +1 -0
- package/dist/src/lang-cypher/tooltipHighlighting.test.js +63 -0
- package/dist/src/lang-cypher/tooltipHighlighting.test.js.map +1 -0
- package/dist/src/neo4jSetup.d.ts +1 -1
- package/dist/src/neo4jSetup.js.map +1 -1
- package/dist/src/richClipboardCopier.js +2 -2
- package/dist/src/richClipboardCopier.js.map +1 -1
- package/dist/src/richClipboardCopier.test.js.map +1 -1
- package/dist/src/{e2e_tests → tests/e2e}/autoCompletion.spec.js +1 -2
- package/dist/src/tests/e2e/autoCompletion.spec.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/configuration.spec.js +1 -1
- package/dist/src/tests/e2e/configuration.spec.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/debounce.spec.js +2 -2
- package/dist/src/tests/e2e/debounce.spec.js.map +1 -0
- package/dist/src/tests/e2e/e2eUtils.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/extraKeybindings.spec.js +1 -1
- package/dist/src/tests/e2e/extraKeybindings.spec.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/historyNavigation.spec.js +1 -1
- package/dist/src/tests/e2e/historyNavigation.spec.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/sanityChecks.spec.js +1 -1
- package/dist/src/tests/e2e/sanityChecks.spec.js.map +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/signatureHelp.spec.js +1 -2
- package/dist/src/tests/e2e/signatureHelp.spec.js.map +1 -0
- package/dist/src/tests/e2e/snippets.spec.d.ts +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/snippets.spec.js +1 -1
- package/dist/src/tests/e2e/snippets.spec.js.map +1 -0
- package/dist/src/tests/e2e/syntaxHighlighting.spec.d.ts +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/syntaxHighlighting.spec.js +2 -2
- package/dist/src/tests/e2e/syntaxHighlighting.spec.js.map +1 -0
- package/dist/src/tests/e2e/syntaxValidation.spec.d.ts +1 -0
- package/dist/src/{e2e_tests → tests/e2e}/syntaxValidation.spec.js +1 -1
- package/dist/src/tests/e2e/syntaxValidation.spec.js.map +1 -0
- package/dist/src/themes.d.ts +2 -2
- package/dist/src/themes.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vitest.config.d.mts +2 -0
- package/dist/vitest.config.mjs +16 -0
- package/dist/vitest.config.mjs.map +1 -0
- package/package.json +15 -12
- package/src/CypherEditor.test.tsx +28 -19
- package/src/CypherEditor.tsx +33 -19
- package/src/editorActions.ts +103 -22
- package/src/historyNavigation.ts +5 -3
- package/src/index.ts +17 -3
- package/src/lang-cypher/autocomplete.ts +3 -6
- package/src/lang-cypher/constants.ts +3 -2
- package/src/lang-cypher/createCypherTheme.ts +59 -10
- package/src/lang-cypher/hover/hover.ts +32 -0
- package/src/lang-cypher/hover/hoverRender.test.ts +176 -0
- package/src/lang-cypher/hover/hoverRender.ts +198 -0
- package/src/lang-cypher/langCypher.ts +5 -4
- package/src/lang-cypher/lintWorker.mjs +168 -166
- package/src/lang-cypher/parser-adapter.ts +10 -9
- package/src/lang-cypher/prism-global.ts +7 -0
- package/src/lang-cypher/signatureHelp.ts +26 -31
- package/src/lang-cypher/syntaxValidation.ts +9 -4
- package/src/lang-cypher/tooltipHighlighting.test.ts +78 -0
- package/src/lang-cypher/tooltipHighlighting.ts +119 -0
- package/src/neo4jSetup.tsx +3 -3
- package/src/richClipboardCopier.test.ts +7 -5
- package/src/richClipboardCopier.ts +2 -2
- package/src/{e2e_tests → tests/e2e}/autoCompletion.spec.tsx +1 -2
- package/src/{e2e_tests → tests/e2e}/configuration.spec.tsx +1 -1
- package/src/{e2e_tests → tests/e2e}/debounce.spec.tsx +2 -2
- package/src/{e2e_tests → tests/e2e}/extraKeybindings.spec.tsx +1 -1
- package/src/{e2e_tests → tests/e2e}/historyNavigation.spec.tsx +2 -2
- package/src/{e2e_tests → tests/e2e}/sanityChecks.spec.tsx +1 -1
- package/src/{e2e_tests → tests/e2e}/signatureHelp.spec.tsx +2 -3
- package/src/{e2e_tests → tests/e2e}/snippets.spec.tsx +1 -1
- package/src/{e2e_tests → tests/e2e}/syntaxHighlighting.spec.tsx +2 -2
- package/src/{e2e_tests → tests/e2e}/syntaxValidation.spec.tsx +1 -1
- package/src/themes.ts +2 -2
- package/dist/src/e2e_tests/autoCompletion.spec.js.map +0 -1
- package/dist/src/e2e_tests/configuration.spec.js.map +0 -1
- package/dist/src/e2e_tests/debounce.spec.js.map +0 -1
- package/dist/src/e2e_tests/e2eUtils.js.map +0 -1
- package/dist/src/e2e_tests/extraKeybindings.spec.js.map +0 -1
- package/dist/src/e2e_tests/historyNavigation.spec.js.map +0 -1
- package/dist/src/e2e_tests/performanceTest.spec.d.ts +0 -6
- package/dist/src/e2e_tests/performanceTest.spec.js +0 -97
- package/dist/src/e2e_tests/performanceTest.spec.js.map +0 -1
- package/dist/src/e2e_tests/sanityChecks.spec.js.map +0 -1
- package/dist/src/e2e_tests/signatureHelp.spec.js.map +0 -1
- package/dist/src/e2e_tests/snippets.spec.js.map +0 -1
- package/dist/src/e2e_tests/syntaxHighlighting.spec.js.map +0 -1
- package/dist/src/e2e_tests/syntaxValidation.spec.js.map +0 -1
- package/src/e2e_tests/performanceTest.spec.tsx +0 -163
- /package/dist/src/{e2e_tests/autoCompletion.spec.d.ts → lang-cypher/hover/hoverRender.test.d.ts} +0 -0
- /package/dist/src/{e2e_tests/configuration.spec.d.ts → lang-cypher/prism-global.d.ts} +0 -0
- /package/dist/src/{e2e_tests/debounce.spec.d.ts → lang-cypher/tooltipHighlighting.test.d.ts} +0 -0
- /package/dist/src/{e2e_tests/extraKeybindings.spec.d.ts → tests/e2e/autoCompletion.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests/historyNavigation.spec.d.ts → tests/e2e/configuration.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests/sanityChecks.spec.d.ts → tests/e2e/debounce.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests → tests/e2e}/e2eUtils.d.ts +0 -0
- /package/dist/src/{e2e_tests → tests/e2e}/e2eUtils.js +0 -0
- /package/dist/src/{e2e_tests/signatureHelp.spec.d.ts → tests/e2e/extraKeybindings.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests/snippets.spec.d.ts → tests/e2e/historyNavigation.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests/syntaxHighlighting.spec.d.ts → tests/e2e/sanityChecks.spec.d.ts} +0 -0
- /package/dist/src/{e2e_tests/syntaxValidation.spec.d.ts → tests/e2e/signatureHelp.spec.d.ts} +0 -0
- /package/src/{e2e_tests → tests/e2e}/e2eUtils.ts +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HoverTooltipSource } from '@codemirror/view';
|
|
2
|
+
import type { CypherConfig } from '../langCypher';
|
|
3
|
+
import { renderHoverInfo } from './hoverRender';
|
|
4
|
+
import { tooltipCypherHighlighter } from '../tooltipHighlighting.js';
|
|
5
|
+
|
|
6
|
+
export function getHoverSource(cfg: CypherConfig): HoverTooltipSource {
|
|
7
|
+
const hoverSource: HoverTooltipSource = (view, pos) => {
|
|
8
|
+
const doc = view.state.doc.toString();
|
|
9
|
+
const hoverInfo = cfg.languageService.hoverInfo(doc, {
|
|
10
|
+
caretPosition: pos,
|
|
11
|
+
dbSchema: cfg.schema ?? {},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!hoverInfo) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
pos: pos,
|
|
20
|
+
above: true,
|
|
21
|
+
create() {
|
|
22
|
+
const dom = document.createElement('div');
|
|
23
|
+
dom.className = 'cm-hover-tooltip';
|
|
24
|
+
dom.appendChild(
|
|
25
|
+
renderHoverInfo(hoverInfo, tooltipCypherHighlighter(view.state, cfg)),
|
|
26
|
+
);
|
|
27
|
+
return { dom };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
return hoverSource;
|
|
32
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import type { HoverInfo } from '@neo4j-cypher/language-support';
|
|
4
|
+
import {
|
|
5
|
+
CypherLanguageService,
|
|
6
|
+
testData,
|
|
7
|
+
} from '@neo4j-cypher/language-support';
|
|
8
|
+
import { describe, expect, test } from 'vitest';
|
|
9
|
+
import type { CypherFragmentKind } from '../tooltipHighlighting.js';
|
|
10
|
+
import { renderHoverInfo } from './hoverRender';
|
|
11
|
+
|
|
12
|
+
function methodHover(query: string): HoverInfo {
|
|
13
|
+
const languageService = new CypherLanguageService();
|
|
14
|
+
return languageService.hoverInfo(query, {
|
|
15
|
+
caretPosition: query.indexOf('(') - 1,
|
|
16
|
+
dbSchema: testData.mockSchema,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function renderMethodHover(query: string): HTMLElement {
|
|
21
|
+
return renderHoverInfo(methodHover(query));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('hover info html rendering', () => {
|
|
25
|
+
function highlightedFragments(
|
|
26
|
+
info: HoverInfo,
|
|
27
|
+
): [string, CypherFragmentKind][] {
|
|
28
|
+
const seen: [string, CypherFragmentKind][] = [];
|
|
29
|
+
renderHoverInfo(info, (code, kind, target) => {
|
|
30
|
+
seen.push([code, kind]);
|
|
31
|
+
target.textContent = code;
|
|
32
|
+
});
|
|
33
|
+
return seen;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('passes the fragment kind of each cypher block to the highlighter', () => {
|
|
37
|
+
expect(
|
|
38
|
+
highlightedFragments({
|
|
39
|
+
kind: 'variable',
|
|
40
|
+
variable: 'n',
|
|
41
|
+
types: ['Node'],
|
|
42
|
+
labelExpression: '(Person | Pet)',
|
|
43
|
+
}),
|
|
44
|
+
).toEqual([['(Person | Pet)', 'labelExpression']]);
|
|
45
|
+
|
|
46
|
+
expect(highlightedFragments(methodHover('RETURN abs(1)'))).toEqual([
|
|
47
|
+
['abs(input :: INTEGER | FLOAT) :: INTEGER | FLOAT', 'function'],
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(highlightedFragments(methodHover('CALL db.labels()'))).toEqual([
|
|
51
|
+
['db.labels() :: (label :: STRING)', 'procedure'],
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('renders a variable hover', () => {
|
|
56
|
+
expect(
|
|
57
|
+
renderHoverInfo({
|
|
58
|
+
kind: 'variable',
|
|
59
|
+
variable: 'n',
|
|
60
|
+
types: ['Node'],
|
|
61
|
+
labelExpression: '(Person | Pet)',
|
|
62
|
+
}).innerHTML,
|
|
63
|
+
).toBe('<p><code>n: Node</code></p><pre><code>(Person | Pet)</code></pre>');
|
|
64
|
+
|
|
65
|
+
expect(
|
|
66
|
+
renderHoverInfo({ kind: 'variable', variable: 'x', types: ['Integer'] })
|
|
67
|
+
.innerHTML,
|
|
68
|
+
).toBe('<p><code>x: Integer</code></p>');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('renders a function hover', () => {
|
|
72
|
+
expect(renderMethodHover('RETURN toFloat(1)').innerHTML).toBe(
|
|
73
|
+
[
|
|
74
|
+
'<pre><code>toFloat(input :: STRING | INTEGER | FLOAT) :: FLOAT</code></pre>',
|
|
75
|
+
'<p>Converts a <code>STRING</code>, <code>INTEGER</code> or <code>FLOAT</code> value to a <code>FLOAT</code> value.</p>',
|
|
76
|
+
'<p><strong>Parameters</strong></p>',
|
|
77
|
+
'<ul><li><code>input</code> - A value to be converted into a float.</li></ul>',
|
|
78
|
+
'<p><strong>Returns:</strong> <code>FLOAT</code></p>',
|
|
79
|
+
].join(''),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('renders a procedure hover', () => {
|
|
84
|
+
expect(renderMethodHover('CALL db.labels()').innerHTML).toBe(
|
|
85
|
+
[
|
|
86
|
+
'<pre><code>db.labels() :: (label :: STRING)</code></pre>',
|
|
87
|
+
"<p>List all labels attached to nodes within a database according to the user's access rights. The procedure returns empty results if the user is not authorized to view those labels.</p>",
|
|
88
|
+
'<p><strong>Returns</strong></p>',
|
|
89
|
+
'<ul><li><code>label</code> - A label within the database.</li></ul>',
|
|
90
|
+
].join(''),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('italicises the deprecation marker of a deprecated method', () => {
|
|
95
|
+
expect(
|
|
96
|
+
renderMethodHover('CYPHER 5 RETURN apoc.create.uuid()').innerHTML,
|
|
97
|
+
).toContain('<p>(<em>deprecated</em>) Returns a UUID.</p>');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// The wildcards of dbms.queryJmx' descriptions are shown as written, both
|
|
101
|
+
// in prose and inside a code span
|
|
102
|
+
test('renders the wildcards of a procedure description', () => {
|
|
103
|
+
const html = renderMethodHover('CALL dbms.queryJmx()').innerHTML;
|
|
104
|
+
|
|
105
|
+
expect(html).toContain(
|
|
106
|
+
'<p>Query JMX management data by domain and name. For instance, use <code>*:*</code> to find all JMX beans.</p>',
|
|
107
|
+
);
|
|
108
|
+
expect(html).toContain(
|
|
109
|
+
"<li><code>query</code> - A query for MBeans on this MBeanServer (e.g. '*:*,name=*neo4j*' for all metrics in neo4j database).</li>",
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('renders the paragraphs, bullet list and italics of a procedure description', () => {
|
|
114
|
+
const html = renderMethodHover(
|
|
115
|
+
'CALL db.index.fulltext.queryNodes()',
|
|
116
|
+
).innerHTML;
|
|
117
|
+
|
|
118
|
+
expect(html).toContain('Valid <em>key: value</em> pairs');
|
|
119
|
+
expect(html).toContain(
|
|
120
|
+
[
|
|
121
|
+
'<ul>',
|
|
122
|
+
"<li>'skip' -- to skip the top N results.</li>",
|
|
123
|
+
"<li>'limit' -- to limit the number of results returned.</li>",
|
|
124
|
+
"<li>'analyzer' -- to use the specified analyzer as a search analyzer for this query.</li>",
|
|
125
|
+
'</ul>',
|
|
126
|
+
'<p>The <code>options</code> map and any of the keys are optional.',
|
|
127
|
+
].join(''),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('renders a parameter that carries its own layout as a code block', () => {
|
|
132
|
+
expect(renderMethodHover('CALL apoc.export.csv.all()').innerHTML).toContain(
|
|
133
|
+
[
|
|
134
|
+
'<ul><li><code>file</code> - The name of the file to which the data will be exported.</li>',
|
|
135
|
+
'<li><code>config</code> -<pre><code>{\n',
|
|
136
|
+
' stream = false :: BOOLEAN,\n',
|
|
137
|
+
].join(''),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Schema descriptions list values such as 'AS_PATH_LIST', which must not
|
|
142
|
+
// turn into emphasis
|
|
143
|
+
test('leaves the underscores inside words alone', () => {
|
|
144
|
+
const description =
|
|
145
|
+
"JSON path options: ('ALWAYS_RETURN_LIST', 'AS_PATH_LIST', 'DEFAULT_PATH_LEAF_TO_NULL').";
|
|
146
|
+
|
|
147
|
+
const dom = renderHoverInfo({
|
|
148
|
+
kind: 'function',
|
|
149
|
+
signature: 'f(pathOptions :: LIST<STRING>) :: ANY',
|
|
150
|
+
description: '',
|
|
151
|
+
isDeprecated: false,
|
|
152
|
+
parameters: [{ name: 'pathOptions', description }],
|
|
153
|
+
returnType: 'ANY',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(dom.querySelector('em')).toBeNull();
|
|
157
|
+
expect(dom.querySelector('li').textContent).toBe(
|
|
158
|
+
`pathOptions - ${description}`,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('never renders descriptions as html', () => {
|
|
163
|
+
const dom = renderHoverInfo({
|
|
164
|
+
kind: 'procedure',
|
|
165
|
+
signature: 'p()',
|
|
166
|
+
description: '<img src=x onerror=alert(1)>',
|
|
167
|
+
isDeprecated: false,
|
|
168
|
+
parameters: [],
|
|
169
|
+
returnValues: [{ name: 'r', description: '<b>bold</b>' }],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(dom.querySelector('img')).toBeNull();
|
|
173
|
+
expect(dom.querySelector('b')).toBeNull();
|
|
174
|
+
expect(dom.textContent).toContain('<img src=x onerror=alert(1)>');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders language-support's HoverInfo into the hover tooltip.
|
|
3
|
+
*
|
|
4
|
+
* Everything is inserted as text nodes, so schema-provided descriptions can
|
|
5
|
+
* never inject HTML into the editor.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
HoverDescription,
|
|
10
|
+
HoverInfo,
|
|
11
|
+
} from '@neo4j-cypher/language-support';
|
|
12
|
+
import type {
|
|
13
|
+
CypherFragmentKind,
|
|
14
|
+
TooltipCypherHighlighter,
|
|
15
|
+
} from '../tooltipHighlighting.js';
|
|
16
|
+
|
|
17
|
+
// The schema writes the bullet lists of its descriptions with *
|
|
18
|
+
const bullet = '* ';
|
|
19
|
+
|
|
20
|
+
export function renderHoverInfo(
|
|
21
|
+
info: HoverInfo,
|
|
22
|
+
highlightHoverCode?: TooltipCypherHighlighter,
|
|
23
|
+
): HTMLElement {
|
|
24
|
+
const dom = document.createElement('div');
|
|
25
|
+
dom.className = 'cm-hover-info';
|
|
26
|
+
|
|
27
|
+
if (info.kind === 'variable') {
|
|
28
|
+
const typeLine = document.createElement('p');
|
|
29
|
+
typeLine.appendChild(
|
|
30
|
+
element('code', `${info.variable}: ${info.types.join(', ')}`),
|
|
31
|
+
);
|
|
32
|
+
dom.appendChild(typeLine);
|
|
33
|
+
|
|
34
|
+
if (info.labelExpression !== undefined) {
|
|
35
|
+
dom.appendChild(
|
|
36
|
+
codeBlock(info.labelExpression, 'labelExpression', highlightHoverCode),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return dom;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
dom.appendChild(codeBlock(info.signature, info.kind, highlightHoverCode));
|
|
43
|
+
|
|
44
|
+
const deprecatedMarker = info.isDeprecated
|
|
45
|
+
? [
|
|
46
|
+
document.createTextNode('('),
|
|
47
|
+
element('em', 'deprecated'),
|
|
48
|
+
document.createTextNode(') '),
|
|
49
|
+
]
|
|
50
|
+
: [];
|
|
51
|
+
renderDescription(dom, info.description, deprecatedMarker);
|
|
52
|
+
|
|
53
|
+
appendDescribedList(dom, 'Parameters', info.parameters);
|
|
54
|
+
|
|
55
|
+
if (info.kind === 'function') {
|
|
56
|
+
if (info.returnType) {
|
|
57
|
+
const returns = document.createElement('p');
|
|
58
|
+
returns.append(
|
|
59
|
+
element('strong', 'Returns:'),
|
|
60
|
+
' ',
|
|
61
|
+
element('code', info.returnType),
|
|
62
|
+
);
|
|
63
|
+
dom.appendChild(returns);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
appendDescribedList(dom, 'Returns', info.returnValues);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return dom;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function element(tag: string, text: string): HTMLElement {
|
|
73
|
+
const created = document.createElement(tag);
|
|
74
|
+
created.textContent = text;
|
|
75
|
+
return created;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function codeBlock(
|
|
79
|
+
code: string,
|
|
80
|
+
kind: CypherFragmentKind | undefined,
|
|
81
|
+
highlightHoverCode?: TooltipCypherHighlighter,
|
|
82
|
+
): HTMLElement {
|
|
83
|
+
const pre = document.createElement('pre');
|
|
84
|
+
const codeElement = document.createElement('code');
|
|
85
|
+
|
|
86
|
+
if (kind && highlightHoverCode) {
|
|
87
|
+
highlightHoverCode(code, kind, codeElement);
|
|
88
|
+
} else {
|
|
89
|
+
codeElement.textContent = code;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pre.appendChild(codeElement);
|
|
93
|
+
return pre;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function appendDescribedList(
|
|
97
|
+
dom: HTMLElement,
|
|
98
|
+
heading: string,
|
|
99
|
+
descriptions: HoverDescription[],
|
|
100
|
+
) {
|
|
101
|
+
if (descriptions.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const headingParagraph = document.createElement('p');
|
|
106
|
+
headingParagraph.appendChild(element('strong', heading));
|
|
107
|
+
dom.appendChild(headingParagraph);
|
|
108
|
+
|
|
109
|
+
const list = document.createElement('ul');
|
|
110
|
+
descriptions.forEach(({ name, description }) => {
|
|
111
|
+
const item = document.createElement('li');
|
|
112
|
+
item.append(element('code', name), ' -');
|
|
113
|
+
|
|
114
|
+
// Map-style descriptions (often configs) are laid out over several lines
|
|
115
|
+
const trimmed = description.trimEnd();
|
|
116
|
+
if (trimmed.includes('\n')) {
|
|
117
|
+
item.appendChild(codeBlock(trimmed, undefined));
|
|
118
|
+
} else {
|
|
119
|
+
item.append(' ');
|
|
120
|
+
renderInline(item, description);
|
|
121
|
+
}
|
|
122
|
+
list.appendChild(item);
|
|
123
|
+
});
|
|
124
|
+
dom.appendChild(list);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* A schema description is prose that can bring paragraphs and * bullet lists.
|
|
128
|
+
`lead` goes in front of its first paragraph. */
|
|
129
|
+
function renderDescription(
|
|
130
|
+
dom: HTMLElement,
|
|
131
|
+
description: string,
|
|
132
|
+
lead: Node[],
|
|
133
|
+
) {
|
|
134
|
+
const lines = description.split('\n');
|
|
135
|
+
let paragraph: string[] = [];
|
|
136
|
+
|
|
137
|
+
const flushParagraph = () => {
|
|
138
|
+
if (paragraph.length === 0 && lead.length === 0) return;
|
|
139
|
+
const p = document.createElement('p');
|
|
140
|
+
p.append(...lead.splice(0));
|
|
141
|
+
renderInline(p, paragraph.join('\n'));
|
|
142
|
+
dom.appendChild(p);
|
|
143
|
+
paragraph = [];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
let i = 0;
|
|
147
|
+
while (i < lines.length) {
|
|
148
|
+
if (lines[i].startsWith(bullet)) {
|
|
149
|
+
flushParagraph();
|
|
150
|
+
const list = document.createElement('ul');
|
|
151
|
+
while (i < lines.length && lines[i].startsWith(bullet)) {
|
|
152
|
+
const item = document.createElement('li');
|
|
153
|
+
renderInline(item, lines[i].slice(bullet.length));
|
|
154
|
+
list.appendChild(item);
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
dom.appendChild(list);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (lines[i].trim() === '') {
|
|
162
|
+
if (paragraph.length > 0) flushParagraph();
|
|
163
|
+
} else {
|
|
164
|
+
paragraph.push(lines[i]);
|
|
165
|
+
}
|
|
166
|
+
i++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
flushParagraph();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Code spans and italics, the markup descriptions write. Underscores only
|
|
173
|
+
count as emphasis outside a word, so that descriptions listing values like
|
|
174
|
+
'DEFAULT_PATH_LEAF_TO_NULL' keep them - the same rule CommonMark
|
|
175
|
+
(https://spec.commonmark.org/0.31.2/#example-374) follows. */
|
|
176
|
+
const inlineMarkup = /`([^`]+)`|(?<!\w)_([^_\n]+)_(?!\w)/g;
|
|
177
|
+
|
|
178
|
+
function renderInline(target: HTMLElement, text: string) {
|
|
179
|
+
let lastEnd = 0;
|
|
180
|
+
|
|
181
|
+
for (const match of text.matchAll(inlineMarkup)) {
|
|
182
|
+
const [matched, code, italic] = match;
|
|
183
|
+
|
|
184
|
+
if (match.index > lastEnd) {
|
|
185
|
+
target.append(text.slice(lastEnd, match.index));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
target.appendChild(
|
|
189
|
+
code !== undefined ? element('code', code.trim()) : element('em', italic),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
lastEnd = match.index + matched.length;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (lastEnd < text.length) {
|
|
196
|
+
target.append(text.slice(lastEnd));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
Language,
|
|
5
5
|
LanguageSupport,
|
|
6
6
|
} from '@codemirror/language';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type DbSchema,
|
|
10
|
-
} from '@neo4j-cypher/language-support';
|
|
7
|
+
import type { CypherLanguageService } from '@neo4j-cypher/language-support';
|
|
8
|
+
import { type DbSchema } from '@neo4j-cypher/language-support';
|
|
11
9
|
import { completionStyles, cypherAutocomplete } from './autocomplete';
|
|
12
10
|
import { ParserAdapter } from './parser-adapter';
|
|
13
11
|
import { signatureHelpTooltip } from './signatureHelp';
|
|
14
12
|
import { cypherLinter } from './syntaxValidation';
|
|
13
|
+
import { hoverTooltip } from '@codemirror/view';
|
|
14
|
+
import { getHoverSource } from './hover/hover.js';
|
|
15
15
|
|
|
16
16
|
const facet = defineLanguageFacet({
|
|
17
17
|
commentTokens: { block: { open: '/*', close: '*/' }, line: '//' },
|
|
@@ -42,5 +42,6 @@ export function cypher(config: CypherConfig) {
|
|
|
42
42
|
}),
|
|
43
43
|
cypherLinter(config),
|
|
44
44
|
signatureHelpTooltip(config),
|
|
45
|
+
hoverTooltip(getHoverSource(config)),
|
|
45
46
|
]);
|
|
46
47
|
}
|