@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,16 @@
|
|
|
1
|
+
import { defineProject } from 'vitest/config';
|
|
2
|
+
export default defineProject({
|
|
3
|
+
test: {
|
|
4
|
+
globals: true,
|
|
5
|
+
projects: [
|
|
6
|
+
{
|
|
7
|
+
test: {
|
|
8
|
+
name: 'unit',
|
|
9
|
+
include: ['src/**/*.test.{ts,tsx}'],
|
|
10
|
+
exclude: ['src/tests/e2e/**'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=vitest.config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.config.mjs","sourceRoot":"","sources":["../vitest.config.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAe,aAAa,CAAC;IAC3B,IAAI,EAAE;QACJ,OAAO,EAAE,IAAI;QAEb,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE;oBACJ,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,CAAC,wBAAwB,CAAC;oBACnC,OAAO,EAAE,CAAC,kBAAkB,CAAC;iBAC9B;aACF;SACF;KACF;CACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-cypher/react-codemirror",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.42",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"codemirror",
|
|
6
6
|
"codemirror 6",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"CHANGELOG.md"
|
|
27
27
|
],
|
|
28
28
|
"type": "module",
|
|
29
|
-
"sideEffects":
|
|
29
|
+
"sideEffects": [
|
|
30
|
+
"**/lang-cypher/prism-global.js"
|
|
31
|
+
],
|
|
30
32
|
"main": "./dist/src/index.js",
|
|
31
33
|
"types": "./dist/src/index.d.ts",
|
|
32
34
|
"dependencies": {
|
|
@@ -46,23 +48,23 @@
|
|
|
46
48
|
"style-mod": "^4.1.2",
|
|
47
49
|
"vscode-languageserver-types": "^3.17.3",
|
|
48
50
|
"workerpool": "^9.3.3",
|
|
49
|
-
"@neo4j-cypher/language-support": "2.0.0-next.
|
|
50
|
-
"@neo4j-cypher/lint-worker": "1.10.1-next.
|
|
51
|
+
"@neo4j-cypher/language-support": "2.0.0-next.39",
|
|
52
|
+
"@neo4j-cypher/lint-worker": "1.10.1-next.16"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@neo4j-ndl/base": "^3.2.10",
|
|
54
|
-
"@playwright/experimental-ct-react": "
|
|
55
|
-
"@playwright/test": "
|
|
56
|
+
"@playwright/experimental-ct-react": "1.62.1",
|
|
57
|
+
"@playwright/test": "1.62.1",
|
|
56
58
|
"@types/lodash.debounce": "^4.0.9",
|
|
57
59
|
"@types/react": "^18.0.28",
|
|
58
60
|
"@types/react-dom": "^18.0.11",
|
|
59
|
-
"@vitejs/plugin-react": "^
|
|
61
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
60
62
|
"copyfiles": "^2.4.1",
|
|
61
63
|
"jsdom": "^24.1.1",
|
|
62
|
-
"playwright": "
|
|
64
|
+
"playwright": "1.62.1",
|
|
63
65
|
"react": "^18.2.0",
|
|
64
66
|
"react-dom": "^18.2.0",
|
|
65
|
-
"vite": "^
|
|
67
|
+
"vite": "^8.2.2"
|
|
66
68
|
},
|
|
67
69
|
"peerDependencies": {
|
|
68
70
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
@@ -75,10 +77,11 @@
|
|
|
75
77
|
"dev": "tsc --watch",
|
|
76
78
|
"build": "pnpm copy-lint-worker && tsc --declaration --outDir dist/",
|
|
77
79
|
"copy-lint-worker": "copyfiles -u 4 ../lint-worker/dist/esm/lintWorker.mjs dist/src/lang-cypher && copyfiles -u 4 ../lint-worker/dist/esm/lintWorker.mjs src/lang-cypher",
|
|
78
|
-
"
|
|
80
|
+
"check-api-report": "tsx checkApiReport.ts",
|
|
81
|
+
"gen-api-report": "pnpm build && pnpm check-api-report",
|
|
82
|
+
"clean": "rm -rf dist tsconfig*.tsbuildinfo",
|
|
79
83
|
"test": "vitest run",
|
|
80
84
|
"test:e2e": "playwright test -c playwright-ct.config.ts",
|
|
81
|
-
"test:e2e-ui": "playwright test -c playwright-ct.config.ts --ui"
|
|
82
|
-
"benchmark": "BENCHMARKING=true playwright test -c playwright-ct.config.ts -g \"benchmarking & performance test session\""
|
|
85
|
+
"test:e2e-ui": "playwright test -c playwright-ct.config.ts --ui"
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
|
-
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import type { EditorView } from '@codemirror/view';
|
|
4
4
|
import { createRef } from 'react';
|
|
5
5
|
import { createRoot } from 'react-dom/client';
|
|
6
6
|
import { act } from 'react-dom/test-utils';
|
|
@@ -13,22 +13,25 @@ let root: ReturnType<typeof createRoot>;
|
|
|
13
13
|
|
|
14
14
|
const ref = createRef<CypherEditor>();
|
|
15
15
|
let value = '';
|
|
16
|
-
const onChange = vi.fn(
|
|
16
|
+
const onChange = vi.fn<(v: string) => void>((v) => {
|
|
17
17
|
value = v;
|
|
18
18
|
rerender();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
22
|
+
(global as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
22
23
|
const DEBOUNCE_TIME_WITH_MARGIN = DEBOUNCE_TIME + 100;
|
|
23
24
|
|
|
24
25
|
/** Avoids crash in test environment */
|
|
25
26
|
function mockEditorView(editorView: EditorView) {
|
|
26
|
-
editorView.coordsAtPos = vi
|
|
27
|
-
left:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
editorView.coordsAtPos = vi
|
|
28
|
+
.fn<() => { left: number; top: number; right: number; bottom: number }>()
|
|
29
|
+
.mockReturnValue({
|
|
30
|
+
left: 0,
|
|
31
|
+
top: 0,
|
|
32
|
+
right: 0,
|
|
33
|
+
bottom: 0,
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
async function debounce() {
|
|
@@ -107,24 +110,28 @@ test('props.value set to undefined preserves editorValue', () => {
|
|
|
107
110
|
expect(getEditorValue()).toBe('initial');
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
//
|
|
111
|
-
|
|
113
|
+
// This test documents unexpected behaviour:
|
|
114
|
+
// value updates (from outside onExecute) are overwritten by pending updates.
|
|
115
|
+
test('new props.value should cancel onChange', async () => {
|
|
112
116
|
// 1. value is updated internally
|
|
113
117
|
ref.current.setValueAndFocus('update');
|
|
114
118
|
|
|
115
119
|
// 2. editor is rerendered with a new value while a value update is still pending
|
|
120
|
+
// the update to 'new external value' is overwritten by the pending update to 'update'
|
|
116
121
|
value = 'new external value';
|
|
117
122
|
rerender();
|
|
118
123
|
|
|
119
124
|
await debounce();
|
|
120
125
|
|
|
121
|
-
//
|
|
122
|
-
expect(
|
|
123
|
-
expect(
|
|
126
|
+
// NOTE: These assertions currently check for the WRONG behavior (they are inverted).
|
|
127
|
+
expect(onChange).toHaveBeenCalled();
|
|
128
|
+
expect(getEditorValue()).not.toBe('new external value');
|
|
129
|
+
expect(value).not.toBe('new external value');
|
|
124
130
|
});
|
|
125
131
|
|
|
126
|
-
//
|
|
127
|
-
|
|
132
|
+
// This test documents unexpected behaviour:
|
|
133
|
+
// value updates (from outside onExecute) are overwritten by pending updates
|
|
134
|
+
test('new props.value set to same value should cancel onChange', async () => {
|
|
128
135
|
// 1. value is set initially
|
|
129
136
|
value = 'same value';
|
|
130
137
|
rerender();
|
|
@@ -133,14 +140,16 @@ test.fails('new props.value set to same value should cancel onChange', async ()
|
|
|
133
140
|
ref.current.setValueAndFocus('update');
|
|
134
141
|
|
|
135
142
|
// 3. editor is rerendered with a new value while a value update is still pending
|
|
143
|
+
// the update to 'same value' is overwritten by the pending update to 'update'
|
|
136
144
|
value = 'same value';
|
|
137
145
|
rerender();
|
|
138
146
|
|
|
139
147
|
await debounce();
|
|
140
148
|
|
|
141
|
-
//
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
149
|
+
// NOTE: These assertions currently check for the WRONG behavior (they are inverted).
|
|
150
|
+
expect(onChange).toHaveBeenCalled();
|
|
151
|
+
expect(getEditorValue()).not.toBe('same value');
|
|
152
|
+
expect(value).not.toBe('same value');
|
|
144
153
|
});
|
|
145
154
|
|
|
146
155
|
test('rerender should not cancel onChange', async () => {
|
package/src/CypherEditor.tsx
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { insertNewline } from '@codemirror/commands';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Extension,
|
|
7
|
-
} from '@codemirror/state';
|
|
8
|
-
import {
|
|
9
|
-
EditorView,
|
|
10
|
-
KeyBinding,
|
|
11
|
-
keymap,
|
|
12
|
-
lineNumbers,
|
|
13
|
-
placeholder,
|
|
14
|
-
ViewUpdate,
|
|
15
|
-
} from '@codemirror/view';
|
|
2
|
+
import type { Extension } from '@codemirror/state';
|
|
3
|
+
import { Annotation, Compartment, EditorState } from '@codemirror/state';
|
|
4
|
+
import type { KeyBinding, ViewUpdate } from '@codemirror/view';
|
|
5
|
+
import { EditorView, keymap, lineNumbers, placeholder } from '@codemirror/view';
|
|
16
6
|
import {
|
|
17
7
|
createInlinePanelController,
|
|
18
8
|
type InlinePanelCallbacks,
|
|
@@ -37,15 +27,18 @@ import {
|
|
|
37
27
|
replaceHistory,
|
|
38
28
|
replMode as historyNavigation,
|
|
39
29
|
} from './historyNavigation';
|
|
40
|
-
import {
|
|
30
|
+
import type { CypherConfig } from './lang-cypher/langCypher';
|
|
31
|
+
import { cypher } from './lang-cypher/langCypher';
|
|
41
32
|
import { cleanupWorkers } from './lang-cypher/syntaxValidation';
|
|
42
33
|
import { basicNeo4jSetup } from './neo4jSetup';
|
|
43
34
|
import { getThemeExtension } from './themes';
|
|
44
35
|
import { richClipboardCopier } from './richClipboardCopier';
|
|
45
|
-
import { LintWorker } from '@neo4j-cypher/lint-worker';
|
|
36
|
+
import type { LintWorker } from '@neo4j-cypher/lint-worker';
|
|
46
37
|
import workerpool from 'workerpool';
|
|
47
38
|
|
|
48
|
-
type DomEventHandlers = Parameters<
|
|
39
|
+
export type DomEventHandlers = Parameters<
|
|
40
|
+
typeof EditorView.domEventHandlers
|
|
41
|
+
>[0];
|
|
49
42
|
|
|
50
43
|
/**
|
|
51
44
|
* Normalize CRLF line endings to LF, which is what CodeMirror expects.
|
|
@@ -196,6 +189,12 @@ export interface CypherEditorProps {
|
|
|
196
189
|
*/
|
|
197
190
|
ariaLabel?: string;
|
|
198
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Extra attributes to set on the editable content element, e.g. `aria-invalid` and
|
|
194
|
+
* `aria-describedby` to associate the editor with an error message rendered outside of it.
|
|
195
|
+
*/
|
|
196
|
+
contentAttributes?: Record<string, string>;
|
|
197
|
+
|
|
199
198
|
/**
|
|
200
199
|
* Whether keybindings for inserting indents with the Tab key should be disabled.
|
|
201
200
|
*
|
|
@@ -323,6 +322,7 @@ const readOnlyCompartment = new Compartment();
|
|
|
323
322
|
const placeholderCompartment = new Compartment();
|
|
324
323
|
const domEventHandlerCompartment = new Compartment();
|
|
325
324
|
const diffCompartment = new Compartment();
|
|
325
|
+
const contentAttributesCompartment = new Compartment();
|
|
326
326
|
|
|
327
327
|
const formatLineNumber =
|
|
328
328
|
(prompt?: string) => (a: number, state: EditorState) => {
|
|
@@ -333,7 +333,7 @@ const formatLineNumber =
|
|
|
333
333
|
return a.toString();
|
|
334
334
|
};
|
|
335
335
|
|
|
336
|
-
type CypherEditorState = {
|
|
336
|
+
export type CypherEditorState = {
|
|
337
337
|
editorActionsContainer: HTMLElement | null;
|
|
338
338
|
};
|
|
339
339
|
|
|
@@ -341,7 +341,7 @@ const ExternalEdit = Annotation.define<boolean>();
|
|
|
341
341
|
const WorkerURL = new URL('./lang-cypher/lintWorker.mjs', import.meta.url)
|
|
342
342
|
.pathname;
|
|
343
343
|
|
|
344
|
-
class CodemirrorSymbolFetcher {
|
|
344
|
+
export class CodemirrorSymbolFetcher {
|
|
345
345
|
constructor(languageService: CypherLanguageService) {
|
|
346
346
|
this.languageService = languageService;
|
|
347
347
|
}
|
|
@@ -610,6 +610,9 @@ export class CypherEditor extends Component<
|
|
|
610
610
|
'aria-label': this.props.ariaLabel,
|
|
611
611
|
})
|
|
612
612
|
: [],
|
|
613
|
+
contentAttributesCompartment.of(
|
|
614
|
+
EditorView.contentAttributes.of(this.props.contentAttributes ?? {}),
|
|
615
|
+
),
|
|
613
616
|
!this.props.moveFocusOnTab
|
|
614
617
|
? EditorView.contentAttributes.of({
|
|
615
618
|
'aria-description':
|
|
@@ -756,6 +759,17 @@ export class CypherEditor extends Component<
|
|
|
756
759
|
});
|
|
757
760
|
}
|
|
758
761
|
|
|
762
|
+
if (
|
|
763
|
+
JSON.stringify(prevProps.contentAttributes) !==
|
|
764
|
+
JSON.stringify(this.props.contentAttributes)
|
|
765
|
+
) {
|
|
766
|
+
this.editorView.current.dispatch({
|
|
767
|
+
effects: contentAttributesCompartment.reconfigure(
|
|
768
|
+
EditorView.contentAttributes.of(this.props.contentAttributes ?? {}),
|
|
769
|
+
),
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
759
773
|
if (prevProps.readonly !== this.props.readonly) {
|
|
760
774
|
this.editorView.current.dispatch({
|
|
761
775
|
effects: readOnlyCompartment.reconfigure(
|
package/src/editorActions.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { StateEffect, StateField, type Extension } from '@codemirror/state';
|
|
|
2
2
|
import {
|
|
3
3
|
Decoration,
|
|
4
4
|
EditorView,
|
|
5
|
+
type Rect,
|
|
5
6
|
ViewPlugin,
|
|
6
7
|
type ViewUpdate,
|
|
7
8
|
WidgetType,
|
|
@@ -40,10 +41,23 @@ export type EditorActionsController = {
|
|
|
40
41
|
* `absolute` on the editor element (which does not scroll). It never moves
|
|
41
42
|
* when the document grows, when an inline panel opens above the first line,
|
|
42
43
|
* or when the content scrolls — content simply scrolls behind it.
|
|
44
|
+
*
|
|
45
|
+
* The overlay is measured against the scroller's visible box rather than
|
|
46
|
+
* against the spacer or the content: the spacer moves down the document
|
|
47
|
+
* whenever something renders above the first line (an inline panel, the
|
|
48
|
+
* deleted lines of a diff), and the content reaches past the right edge as
|
|
49
|
+
* soon as it overflows.
|
|
50
|
+
* Vertically the cluster is centred on the first line.
|
|
43
51
|
*/
|
|
44
52
|
|
|
45
53
|
const SPACER_BOTTOM_GAP = 2;
|
|
46
54
|
const SPACER_LEFT_GAP = 4;
|
|
55
|
+
/**
|
|
56
|
+
* Floor for the overlay's inset from the visible right edge, so it stays off
|
|
57
|
+
* the content's last pixel even if a theme zeroes `.cm-line`'s right padding
|
|
58
|
+
* (the base theme's 2px, which is what normally provides the inset).
|
|
59
|
+
*/
|
|
60
|
+
const MIN_OVERLAY_RIGHT_INSET = 2;
|
|
47
61
|
|
|
48
62
|
export function createEditorActionsController(): EditorActionsController {
|
|
49
63
|
const setActiveEffect = StateEffect.define<boolean>();
|
|
@@ -74,6 +88,38 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
74
88
|
return true;
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The spacer floats right, so CodeMirror's default — the widget's own box —
|
|
93
|
+
* puts the caret at the right edge of the cluster whenever the spacer is
|
|
94
|
+
* the only thing it can measure on the first line (an empty document).
|
|
95
|
+
* Report the start of the line's inline content instead: the node after
|
|
96
|
+
* the spacer (text, a placeholder, or CodeMirror's trailing `<br>`).
|
|
97
|
+
*/
|
|
98
|
+
coordsAt(dom: HTMLElement): Rect | null {
|
|
99
|
+
const next = dom.nextSibling;
|
|
100
|
+
if (!next) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
let rects: DOMRectList;
|
|
104
|
+
if (next instanceof Element) {
|
|
105
|
+
rects = next.getClientRects();
|
|
106
|
+
} else {
|
|
107
|
+
const range = document.createRange();
|
|
108
|
+
range.selectNodeContents(next);
|
|
109
|
+
rects = range.getClientRects();
|
|
110
|
+
}
|
|
111
|
+
const rect = rects[0];
|
|
112
|
+
if (!rect) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
left: rect.left,
|
|
117
|
+
right: rect.left,
|
|
118
|
+
top: rect.top,
|
|
119
|
+
bottom: rect.bottom,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
77
123
|
ignoreEvent(): boolean {
|
|
78
124
|
return true;
|
|
79
125
|
}
|
|
@@ -136,13 +182,11 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
136
182
|
`${container.offsetWidth}px`,
|
|
137
183
|
);
|
|
138
184
|
view.dom.style.setProperty(
|
|
139
|
-
'--cm-editor-actions-
|
|
140
|
-
`${container.
|
|
141
|
-
);
|
|
142
|
-
view.dom.style.setProperty(
|
|
143
|
-
'--cm-editor-actions-content-min-height',
|
|
144
|
-
`${container.offsetHeight + SPACER_BOTTOM_GAP}px`,
|
|
185
|
+
'--cm-editor-actions-reserved-width',
|
|
186
|
+
`${container.offsetWidth + SPACER_LEFT_GAP}px`,
|
|
145
187
|
);
|
|
188
|
+
// The spacer's height follows the overlay's position, so `align`
|
|
189
|
+
// owns it.
|
|
146
190
|
this.align(view);
|
|
147
191
|
});
|
|
148
192
|
this.resizeObserver.observe(container);
|
|
@@ -153,28 +197,52 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
153
197
|
}
|
|
154
198
|
|
|
155
199
|
private align(view: EditorView): void {
|
|
156
|
-
view.requestMeasure<{
|
|
200
|
+
view.requestMeasure<{
|
|
201
|
+
top: number;
|
|
202
|
+
right: number;
|
|
203
|
+
spacerHeight: number;
|
|
204
|
+
contentMinHeight: number;
|
|
205
|
+
} | null>({
|
|
157
206
|
read: () => {
|
|
158
207
|
if (!this.dom) {
|
|
159
208
|
return null;
|
|
160
209
|
}
|
|
161
210
|
const editorRect = view.dom.getBoundingClientRect();
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
211
|
+
const scrollerRect = view.scrollDOM.getBoundingClientRect();
|
|
212
|
+
const style = window.getComputedStyle(view.contentDOM);
|
|
213
|
+
const visibleRight = scrollerRect.left + view.scrollDOM.clientWidth;
|
|
214
|
+
// Match the inset `.cm-line`'s right padding gives the spacer, so
|
|
215
|
+
// the overlay lands on the space the spacer reserves and stays off
|
|
216
|
+
// `contentRect.right - 1`, the pixel CodeMirror hit-tests to
|
|
217
|
+
// measure a wrapped line's selection. Cover it and the first
|
|
218
|
+
// line's highlight collapses to zero width — hence the floor.
|
|
219
|
+
const line = view.contentDOM.querySelector('.cm-line');
|
|
220
|
+
const lineRightPadding = line
|
|
221
|
+
? parseFloat(window.getComputedStyle(line).paddingRight) || 0
|
|
222
|
+
: 0;
|
|
223
|
+
|
|
224
|
+
const contentPaddingTop = parseFloat(style.paddingTop) || 0;
|
|
225
|
+
const height = this.dom.offsetHeight;
|
|
226
|
+
// How far the cluster has to rise above the first line's top to
|
|
227
|
+
// sit centred on it, limited to the padding it can rise into.
|
|
228
|
+
const lift = Math.min(
|
|
229
|
+
Math.max((height - view.defaultLineHeight) / 2, 0),
|
|
230
|
+
contentPaddingTop,
|
|
231
|
+
);
|
|
232
|
+
|
|
175
233
|
return {
|
|
176
|
-
top:
|
|
177
|
-
right:
|
|
234
|
+
top: scrollerRect.top - editorRect.top + contentPaddingTop - lift,
|
|
235
|
+
right:
|
|
236
|
+
editorRect.right -
|
|
237
|
+
visibleRight +
|
|
238
|
+
(parseFloat(style.paddingRight) || 0) +
|
|
239
|
+
Math.max(lineRightPadding, MIN_OVERLAY_RIGHT_INSET),
|
|
240
|
+
// The spacer starts at the first line's top, so it only needs to
|
|
241
|
+
// clear the part of the cluster that hangs below it.
|
|
242
|
+
spacerHeight: height - lift,
|
|
243
|
+
// A floor under the content, so an empty document is still tall
|
|
244
|
+
// enough to hold the overlay.
|
|
245
|
+
contentMinHeight: contentPaddingTop - lift + height,
|
|
178
246
|
};
|
|
179
247
|
},
|
|
180
248
|
write: (pos) => {
|
|
@@ -189,6 +257,14 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
189
257
|
'--cm-editor-actions-right',
|
|
190
258
|
`${pos.right}px`,
|
|
191
259
|
);
|
|
260
|
+
view.dom.style.setProperty(
|
|
261
|
+
'--cm-editor-actions-height',
|
|
262
|
+
`${pos.spacerHeight}px`,
|
|
263
|
+
);
|
|
264
|
+
view.dom.style.setProperty(
|
|
265
|
+
'--cm-editor-actions-content-min-height',
|
|
266
|
+
`${pos.contentMinHeight}px`,
|
|
267
|
+
);
|
|
192
268
|
},
|
|
193
269
|
});
|
|
194
270
|
}
|
|
@@ -206,6 +282,7 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
206
282
|
root.style.removeProperty('--cm-editor-actions-width');
|
|
207
283
|
root.style.removeProperty('--cm-editor-actions-height');
|
|
208
284
|
root.style.removeProperty('--cm-editor-actions-content-min-height');
|
|
285
|
+
root.style.removeProperty('--cm-editor-actions-reserved-width');
|
|
209
286
|
root.style.removeProperty('--cm-editor-actions-top');
|
|
210
287
|
root.style.removeProperty('--cm-editor-actions-right');
|
|
211
288
|
}
|
|
@@ -230,6 +307,10 @@ export function createEditorActionsController(): EditorActionsController {
|
|
|
230
307
|
WebkitUserSelect: 'none',
|
|
231
308
|
pointerEvents: 'none',
|
|
232
309
|
},
|
|
310
|
+
// The spacer only clears the first line, so a panel above it makes its own room.
|
|
311
|
+
'.cm-inline-panel': {
|
|
312
|
+
paddingRight: 'var(--cm-editor-actions-reserved-width, 0px)',
|
|
313
|
+
},
|
|
233
314
|
'.cm-placeholder': {
|
|
234
315
|
display: 'inline',
|
|
235
316
|
},
|
package/src/historyNavigation.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Extension
|
|
2
|
-
import {
|
|
1
|
+
import type { Extension } from '@codemirror/state';
|
|
2
|
+
import { StateEffect } from '@codemirror/state';
|
|
3
|
+
import type { EditorView } from '@codemirror/view';
|
|
4
|
+
import { keymap } from '@codemirror/view';
|
|
3
5
|
|
|
4
6
|
import { StateField } from '@codemirror/state';
|
|
5
7
|
|
|
@@ -10,7 +12,7 @@ type HistoryState = {
|
|
|
10
12
|
documentUpdate: string | null;
|
|
11
13
|
};
|
|
12
14
|
const DRAFT_ENTRY_INDEX = -1;
|
|
13
|
-
const historyInitialState = {
|
|
15
|
+
const historyInitialState: HistoryState = {
|
|
14
16
|
history: [],
|
|
15
17
|
index: DRAFT_ENTRY_INDEX,
|
|
16
18
|
documentUpdate: null,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
import * as LanguageSupport from '@neo4j-cypher/language-support';
|
|
2
|
+
export { LanguageSupport };
|
|
3
|
+
export {
|
|
4
|
+
CypherEditor,
|
|
5
|
+
CypherEditorProps,
|
|
6
|
+
CypherEditorState,
|
|
7
|
+
CodemirrorSymbolFetcher,
|
|
8
|
+
DomEventHandlers,
|
|
9
|
+
} from './CypherEditor';
|
|
3
10
|
export type { InlinePanelProps } from './CypherEditor';
|
|
11
|
+
export type { InlinePanelCallbacks } from './inlinePanel.js';
|
|
4
12
|
export type { DiffProps } from './diffView';
|
|
5
|
-
export { cypher } from './lang-cypher/langCypher';
|
|
13
|
+
export { cypher, CypherConfig } from './lang-cypher/langCypher';
|
|
6
14
|
export { darkThemeConstants, lightThemeConstants } from './themes';
|
|
15
|
+
export type {
|
|
16
|
+
ThemeOptions,
|
|
17
|
+
DiffColors,
|
|
18
|
+
} from './lang-cypher/createCypherTheme.js';
|
|
19
|
+
export type { HostPortalCallbacks } from './hostCallbacks.js';
|
|
20
|
+
export type { HighlightedCypherTokenTypes } from './lang-cypher/constants.js';
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
CompletionSource,
|
|
4
|
-
snippet,
|
|
5
|
-
} from '@codemirror/autocomplete';
|
|
1
|
+
import type { Completion, CompletionSource } from '@codemirror/autocomplete';
|
|
2
|
+
import { snippet } from '@codemirror/autocomplete';
|
|
6
3
|
import { shouldAutoCompleteYield } from '@neo4j-cypher/language-support';
|
|
7
4
|
import {
|
|
8
5
|
CompletionItemKind,
|
|
9
6
|
CompletionItemTag,
|
|
10
7
|
} from 'vscode-languageserver-types';
|
|
11
|
-
import { CompletionItemIcons } from '../icons';
|
|
8
|
+
import type { CompletionItemIcons } from '../icons';
|
|
12
9
|
import type { CypherConfig } from './langCypher';
|
|
13
10
|
import { getDocString } from './utils';
|
|
14
11
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { languageDataProp } from '@codemirror/language';
|
|
2
2
|
import type { Facet } from '@codemirror/state';
|
|
3
3
|
import { NodeSet, NodeType } from '@lezer/common';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import type { Tag } from '@lezer/highlight';
|
|
5
|
+
import { styleTags, tags } from '@lezer/highlight';
|
|
6
|
+
import type { CypherTokenType } from '@neo4j-cypher/language-support';
|
|
6
7
|
|
|
7
8
|
export const cypherTokenTypeToNode = (facet: Facet<unknown>) => ({
|
|
8
9
|
topNode: NodeType.define({
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
TagStyle,
|
|
5
|
-
} from '@codemirror/language';
|
|
6
|
-
import { Extension } from '@codemirror/state';
|
|
1
|
+
import type { TagStyle } from '@codemirror/language';
|
|
2
|
+
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
3
|
+
import type { Extension } from '@codemirror/state';
|
|
7
4
|
import { EditorView } from '@codemirror/view';
|
|
8
5
|
import { CypherTokenType } from '@neo4j-cypher/language-support';
|
|
9
|
-
import { StyleSpec } from 'style-mod';
|
|
10
|
-
import { HighlightedCypherTokenTypes
|
|
6
|
+
import type { StyleSpec } from 'style-mod';
|
|
7
|
+
import type { HighlightedCypherTokenTypes } from './constants';
|
|
8
|
+
import { tokenTypeToStyleTag } from './constants';
|
|
11
9
|
import {
|
|
12
10
|
byWordSvg,
|
|
13
11
|
caseSensitiveSvg,
|
|
@@ -141,6 +139,53 @@ export const createCypherTheme = ({
|
|
|
141
139
|
'& .cm-signature-help-panel-description': {
|
|
142
140
|
padding: '5px',
|
|
143
141
|
},
|
|
142
|
+
// Shared between both hover tooltips and hover errors
|
|
143
|
+
'& .cm-tooltip-hover': {
|
|
144
|
+
maxWidth: '800px',
|
|
145
|
+
},
|
|
146
|
+
'& .cm-hover-tooltip': {
|
|
147
|
+
backgroundColor: settings.autoCompletionPanel.backgroundColor,
|
|
148
|
+
color: settings.foreground,
|
|
149
|
+
maxHeight: '300px',
|
|
150
|
+
overflow: 'auto',
|
|
151
|
+
padding: '2px 10px',
|
|
152
|
+
},
|
|
153
|
+
'& .cm-hover-info': {
|
|
154
|
+
fontFamily:
|
|
155
|
+
'-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif',
|
|
156
|
+
fontSize: '13px',
|
|
157
|
+
lineHeight: '1.5',
|
|
158
|
+
'& p': {
|
|
159
|
+
margin: '6px 0',
|
|
160
|
+
},
|
|
161
|
+
'& ul': {
|
|
162
|
+
margin: '6px 0',
|
|
163
|
+
paddingLeft: '18px',
|
|
164
|
+
},
|
|
165
|
+
'& li': {
|
|
166
|
+
margin: '2px 0',
|
|
167
|
+
},
|
|
168
|
+
'& code': {
|
|
169
|
+
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
170
|
+
fontSize: '12px',
|
|
171
|
+
},
|
|
172
|
+
'& pre': {
|
|
173
|
+
margin: '6px 0',
|
|
174
|
+
padding: '6px 8px',
|
|
175
|
+
borderRadius: '3px',
|
|
176
|
+
whiteSpace: 'pre-wrap',
|
|
177
|
+
overflowWrap: 'break-word',
|
|
178
|
+
backgroundColor: dark
|
|
179
|
+
? 'rgba(255, 255, 255, 0.08)'
|
|
180
|
+
: 'rgba(0, 0, 0, 0.05)',
|
|
181
|
+
},
|
|
182
|
+
'& > *:first-child': {
|
|
183
|
+
marginTop: '0',
|
|
184
|
+
},
|
|
185
|
+
'& > *:last-child': {
|
|
186
|
+
marginBottom: '0',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
144
189
|
'.cm-completionInfo-signature': {
|
|
145
190
|
color: 'darkgrey',
|
|
146
191
|
},
|
|
@@ -255,8 +300,12 @@ export const createCypherTheme = ({
|
|
|
255
300
|
|
|
256
301
|
const themeExtension = EditorView.theme(themeOptions, { dark });
|
|
257
302
|
|
|
258
|
-
const styles =
|
|
259
|
-
(
|
|
303
|
+
const styles = (
|
|
304
|
+
Object.entries(highlightStyles) as Array<
|
|
305
|
+
[HighlightedCypherTokenTypes, string]
|
|
306
|
+
>
|
|
307
|
+
).map(
|
|
308
|
+
([token, color]): TagStyle => ({
|
|
260
309
|
tag: tokenTypeToStyleTag[token],
|
|
261
310
|
color,
|
|
262
311
|
class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined,
|