@neo4j-cypher/react-codemirror 2.0.0-next.3 → 2.0.0-next.30
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 +257 -0
- package/README.md +3 -2
- package/dist/{types → src}/CypherEditor.d.ts +81 -3
- package/dist/src/CypherEditor.js +336 -0
- package/dist/src/CypherEditor.js.map +1 -0
- package/dist/src/CypherEditor.test.js +154 -0
- package/dist/src/CypherEditor.test.js.map +1 -0
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/e2e_tests/autoCompletion.spec.js +332 -0
- package/dist/src/e2e_tests/autoCompletion.spec.js.map +1 -0
- package/dist/src/e2e_tests/configuration.spec.js +83 -0
- package/dist/src/e2e_tests/configuration.spec.js.map +1 -0
- package/dist/src/e2e_tests/debounce.spec.js +66 -0
- package/dist/src/e2e_tests/debounce.spec.js.map +1 -0
- package/dist/{types/e2e_tests/e2e-utils.d.ts → src/e2e_tests/e2eUtils.d.ts} +2 -0
- package/dist/src/e2e_tests/e2eUtils.js +79 -0
- package/dist/src/e2e_tests/e2eUtils.js.map +1 -0
- package/dist/src/e2e_tests/extraKeybindings.spec.js +43 -0
- package/dist/src/e2e_tests/extraKeybindings.spec.js.map +1 -0
- package/dist/src/e2e_tests/historyNavigation.spec.js +227 -0
- package/dist/src/e2e_tests/historyNavigation.spec.js.map +1 -0
- package/dist/src/e2e_tests/performanceTest.spec.d.ts +6 -0
- package/dist/src/e2e_tests/performanceTest.spec.js +97 -0
- package/dist/src/e2e_tests/performanceTest.spec.js.map +1 -0
- package/dist/src/e2e_tests/sanityChecks.spec.js +53 -0
- package/dist/src/e2e_tests/sanityChecks.spec.js.map +1 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js +228 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -0
- package/dist/src/e2e_tests/snippets.spec.js +62 -0
- package/dist/src/e2e_tests/snippets.spec.js.map +1 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.d.ts +1 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.js +90 -0
- package/dist/src/e2e_tests/syntaxHighlighting.spec.js.map +1 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.d.ts +1 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.js +126 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -0
- package/dist/src/historyNavigation.js +163 -0
- package/dist/src/historyNavigation.js.map +1 -0
- package/dist/{types → src}/icons.d.ts +1 -1
- package/dist/src/icons.js +62 -0
- package/dist/src/icons.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lang-cypher/autocomplete.d.ts +6 -0
- package/dist/src/lang-cypher/autocomplete.js +113 -0
- package/dist/src/lang-cypher/autocomplete.js.map +1 -0
- package/dist/{types → src}/lang-cypher/constants.d.ts +11 -0
- package/dist/src/lang-cypher/constants.js +69 -0
- package/dist/src/lang-cypher/constants.js.map +1 -0
- package/dist/src/lang-cypher/contants.test.d.ts +1 -0
- package/dist/src/lang-cypher/contants.test.js +103 -0
- package/dist/src/lang-cypher/contants.test.js.map +1 -0
- package/dist/src/lang-cypher/createCypherTheme.js +183 -0
- package/dist/src/lang-cypher/createCypherTheme.js.map +1 -0
- package/dist/src/lang-cypher/langCypher.d.ts +13 -0
- package/dist/src/lang-cypher/langCypher.js +23 -0
- package/dist/src/lang-cypher/langCypher.js.map +1 -0
- package/dist/src/lang-cypher/lintWorker.mjs +2022 -0
- package/dist/src/lang-cypher/parser-adapter.d.ts +19 -0
- package/dist/src/lang-cypher/parser-adapter.js +113 -0
- package/dist/src/lang-cypher/parser-adapter.js.map +1 -0
- package/dist/src/lang-cypher/signatureHelp.d.ts +4 -0
- package/dist/src/lang-cypher/signatureHelp.js +109 -0
- package/dist/src/lang-cypher/signatureHelp.js.map +1 -0
- package/dist/{types/lang-cypher/syntax-validation.d.ts → src/lang-cypher/syntaxValidation.d.ts} +2 -1
- package/dist/src/lang-cypher/syntaxValidation.js +57 -0
- package/dist/src/lang-cypher/syntaxValidation.js.map +1 -0
- package/dist/src/lang-cypher/themeIcons.js +22 -0
- package/dist/src/lang-cypher/themeIcons.js.map +1 -0
- package/dist/src/lang-cypher/utils.d.ts +2 -0
- package/dist/src/lang-cypher/utils.js +10 -0
- package/dist/src/lang-cypher/utils.js.map +1 -0
- package/dist/src/ndlTokensCopy.d.ts +570 -0
- package/dist/src/ndlTokensCopy.js +571 -0
- package/dist/src/ndlTokensCopy.js.map +1 -0
- package/dist/src/ndlTokensCopy.test.d.ts +1 -0
- package/dist/src/ndlTokensCopy.test.js +12 -0
- package/dist/src/ndlTokensCopy.test.js.map +1 -0
- package/dist/src/neo4jSetup.d.ts +6 -0
- package/dist/src/neo4jSetup.js +120 -0
- package/dist/src/neo4jSetup.js.map +1 -0
- package/dist/src/richClipboardCopier.d.ts +4 -0
- package/dist/src/richClipboardCopier.js +78 -0
- package/dist/src/richClipboardCopier.js.map +1 -0
- package/dist/src/richClipboardCopier.test.d.ts +1 -0
- package/dist/src/richClipboardCopier.test.js +53 -0
- package/dist/src/richClipboardCopier.test.js.map +1 -0
- package/dist/{types → src}/themes.d.ts +1 -1
- package/dist/src/themes.js +93 -0
- package/dist/src/themes.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +43 -41
- package/src/CypherEditor.test.tsx +204 -0
- package/src/CypherEditor.tsx +316 -42
- package/src/constants.ts +1 -0
- package/src/e2e_tests/autoCompletion.spec.tsx +571 -0
- package/src/e2e_tests/configuration.spec.tsx +111 -0
- package/src/e2e_tests/debounce.spec.tsx +106 -0
- package/src/e2e_tests/{e2e-utils.ts → e2eUtils.ts} +41 -3
- package/src/e2e_tests/{extra-keybindings.spec.tsx → extraKeybindings.spec.tsx} +1 -3
- package/src/e2e_tests/{history-navigation.spec.tsx → historyNavigation.spec.tsx} +137 -18
- package/src/e2e_tests/performanceTest.spec.tsx +163 -0
- package/src/e2e_tests/{sanity-checks.spec.tsx → sanityChecks.spec.tsx} +7 -22
- package/src/e2e_tests/signatureHelp.spec.tsx +444 -0
- package/src/e2e_tests/snippets.spec.tsx +92 -0
- package/src/e2e_tests/{syntax-highlighting.spec.tsx → syntaxHighlighting.spec.tsx} +26 -24
- package/src/e2e_tests/syntaxValidation.spec.tsx +259 -0
- package/src/{history-navigation.ts → historyNavigation.ts} +1 -1
- package/src/icons.ts +3 -0
- package/src/index.ts +2 -2
- package/src/lang-cypher/autocomplete.ts +99 -18
- package/src/lang-cypher/constants.ts +27 -0
- package/src/lang-cypher/contants.test.ts +6 -2
- package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +45 -2
- package/src/lang-cypher/langCypher.ts +42 -0
- package/src/lang-cypher/lintWorker.mjs +2022 -0
- package/src/lang-cypher/parser-adapter.ts +145 -0
- package/src/lang-cypher/signatureHelp.ts +151 -0
- package/src/lang-cypher/syntaxValidation.ts +72 -0
- package/src/lang-cypher/utils.ts +9 -0
- package/src/{ndl-tokens-copy.test.ts → ndlTokensCopy.test.ts} +2 -1
- package/src/ndlTokensCopy.ts +570 -0
- package/src/{neo4j-setup.tsx → neo4jSetup.tsx} +78 -17
- package/src/richClipboardCopier.test.ts +65 -0
- package/src/richClipboardCopier.ts +99 -0
- package/src/themes.ts +45 -70
- package/src/viteEnv.d.ts +1 -0
- package/dist/cjs/index.cjs +0 -1440
- package/dist/cjs/index.cjs.map +0 -7
- package/dist/esm/index.mjs +0 -1463
- package/dist/esm/index.mjs.map +0 -7
- package/dist/types/e2e_tests/mock-data.d.ts +0 -3779
- package/dist/types/index.d.ts +0 -4
- package/dist/types/lang-cypher/ParserAdapter.d.ts +0 -14
- package/dist/types/lang-cypher/autocomplete.d.ts +0 -3
- package/dist/types/lang-cypher/lang-cypher.d.ts +0 -7
- package/dist/types/ndl-tokens-copy.d.ts +0 -379
- package/dist/types/neo4j-setup.d.ts +0 -2
- package/dist/types/tsconfig.tsbuildinfo +0 -1
- package/src/e2e_tests/auto-completion.spec.tsx +0 -232
- package/src/e2e_tests/mock-data.ts +0 -4310
- package/src/e2e_tests/performance-test.spec.tsx +0 -71
- package/src/e2e_tests/syntax-validation.spec.tsx +0 -156
- package/src/lang-cypher/ParserAdapter.ts +0 -92
- package/src/lang-cypher/lang-cypher.ts +0 -32
- package/src/lang-cypher/syntax-validation.ts +0 -24
- package/src/ndl-tokens-copy.ts +0 -379
- /package/dist/{types/e2e_tests/auto-completion.spec.d.ts → src/CypherEditor.test.d.ts} +0 -0
- /package/dist/{types/e2e_tests/extra-keybindings.spec.d.ts → src/e2e_tests/autoCompletion.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/history-navigation.spec.d.ts → src/e2e_tests/configuration.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/performance-test.spec.d.ts → src/e2e_tests/debounce.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/sanity-checks.spec.d.ts → src/e2e_tests/extraKeybindings.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/syntax-highlighting.spec.d.ts → src/e2e_tests/historyNavigation.spec.d.ts} +0 -0
- /package/dist/{types/e2e_tests/syntax-validation.spec.d.ts → src/e2e_tests/sanityChecks.spec.d.ts} +0 -0
- /package/dist/{types/lang-cypher/contants.test.d.ts → src/e2e_tests/signatureHelp.spec.d.ts} +0 -0
- /package/dist/{types/ndl-tokens-copy.test.d.ts → src/e2e_tests/snippets.spec.d.ts} +0 -0
- /package/dist/{types/history-navigation.d.ts → src/historyNavigation.d.ts} +0 -0
- /package/dist/{types/lang-cypher/create-cypher-theme.d.ts → src/lang-cypher/createCypherTheme.d.ts} +0 -0
- /package/dist/{types/lang-cypher/theme-icons.d.ts → src/lang-cypher/themeIcons.d.ts} +0 -0
- /package/src/lang-cypher/{theme-icons.ts → themeIcons.ts} +0 -0
package/src/CypherEditor.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { insertNewline } from '@codemirror/commands';
|
|
1
2
|
import {
|
|
2
3
|
Annotation,
|
|
3
4
|
Compartment,
|
|
@@ -9,18 +10,24 @@ import {
|
|
|
9
10
|
KeyBinding,
|
|
10
11
|
keymap,
|
|
11
12
|
lineNumbers,
|
|
13
|
+
placeholder,
|
|
12
14
|
ViewUpdate,
|
|
13
15
|
} from '@codemirror/view';
|
|
14
|
-
import type
|
|
16
|
+
import { formatQuery, type DbSchema } from '@neo4j-cypher/language-support';
|
|
17
|
+
import debounce from 'lodash.debounce';
|
|
15
18
|
import { Component, createRef } from 'react';
|
|
19
|
+
import { DEBOUNCE_TIME } from './constants';
|
|
16
20
|
import {
|
|
17
21
|
replaceHistory,
|
|
18
22
|
replMode as historyNavigation,
|
|
19
|
-
} from './
|
|
20
|
-
import { cypher, CypherConfig } from './lang-cypher/
|
|
21
|
-
import {
|
|
23
|
+
} from './historyNavigation';
|
|
24
|
+
import { cypher, CypherConfig } from './lang-cypher/langCypher';
|
|
25
|
+
import { cleanupWorkers } from './lang-cypher/syntaxValidation';
|
|
26
|
+
import { basicNeo4jSetup } from './neo4jSetup';
|
|
22
27
|
import { getThemeExtension } from './themes';
|
|
28
|
+
import { richClipboardCopier } from './richClipboardCopier';
|
|
23
29
|
|
|
30
|
+
type DomEventHandlers = Parameters<typeof EditorView.domEventHandlers>[0];
|
|
24
31
|
export interface CypherEditorProps {
|
|
25
32
|
/**
|
|
26
33
|
* The prompt to show on single line editors
|
|
@@ -40,7 +47,14 @@ export interface CypherEditorProps {
|
|
|
40
47
|
*/
|
|
41
48
|
onExecute?: (cmd: string) => void;
|
|
42
49
|
/**
|
|
43
|
-
*
|
|
50
|
+
* If true, pressing enter will add a new line to the editor and cmd/ctrl + enter will execute the query.
|
|
51
|
+
* Otherwise pressing enter on a single line will execute the query.
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
newLineOnEnter?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The editor history navigable via up/down arrow keys. Order newest to oldest.
|
|
44
58
|
* Add to this list with the `onExecute` callback for REPL style history.
|
|
45
59
|
*/
|
|
46
60
|
history?: string[];
|
|
@@ -57,6 +71,10 @@ export interface CypherEditorProps {
|
|
|
57
71
|
* @default false
|
|
58
72
|
*/
|
|
59
73
|
autofocus?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Where to place the cursor in the query. Cannot be enabled at the same time than autofocus
|
|
76
|
+
*/
|
|
77
|
+
offset?: number;
|
|
60
78
|
/**
|
|
61
79
|
* Whether the editor should wrap lines.
|
|
62
80
|
*
|
|
@@ -69,6 +87,20 @@ export interface CypherEditorProps {
|
|
|
69
87
|
* @default true
|
|
70
88
|
*/
|
|
71
89
|
lint?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the signature help tooltip should be shown below the text.
|
|
92
|
+
* If false, it will be shown above.
|
|
93
|
+
*
|
|
94
|
+
* @default true
|
|
95
|
+
*/
|
|
96
|
+
showSignatureTooltipBelow?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Internal feature flags for the editor. Don't use in production
|
|
99
|
+
*
|
|
100
|
+
*/
|
|
101
|
+
featureFlags?: {
|
|
102
|
+
consoleCommands?: boolean;
|
|
103
|
+
};
|
|
72
104
|
/**
|
|
73
105
|
* The schema to use for autocompletion and linting.
|
|
74
106
|
*
|
|
@@ -96,32 +128,158 @@ export interface CypherEditorProps {
|
|
|
96
128
|
* @param {ViewUpdate} viewUpdate - the view update from codemirror
|
|
97
129
|
*/
|
|
98
130
|
onChange?(value: string, viewUpdate: ViewUpdate): void;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map of event handlers to add to the editor.
|
|
134
|
+
*
|
|
135
|
+
* Note that the props are compared by reference, meaning object defined inline
|
|
136
|
+
* will cause the editor to re-render (much like the style prop does in this example:
|
|
137
|
+
* <div style={{}} />
|
|
138
|
+
*
|
|
139
|
+
* Memoize the object if you want/need to avoid this.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* // listen to blur events
|
|
143
|
+
* <CypherEditor domEventHandlers={{blur: () => console.log("blur event fired")}} />
|
|
144
|
+
*/
|
|
145
|
+
domEventHandlers?: DomEventHandlers;
|
|
146
|
+
/**
|
|
147
|
+
* Placeholder text to display when the editor is empty.
|
|
148
|
+
*/
|
|
149
|
+
placeholder?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Whether the editor should show line numbers.
|
|
152
|
+
*
|
|
153
|
+
* @default true
|
|
154
|
+
*/
|
|
155
|
+
lineNumbers?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Whether the editor is read-only.
|
|
158
|
+
*
|
|
159
|
+
* @default false
|
|
160
|
+
*/
|
|
161
|
+
readonly?: boolean;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* String value to assign to the aria-label attribute of the editor.
|
|
165
|
+
*/
|
|
166
|
+
ariaLabel?: string;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Whether keybindings for inserting indents with the Tab key should be disabled.
|
|
170
|
+
*
|
|
171
|
+
* true will not create keybindings for inserting indents.
|
|
172
|
+
* false will create keybindings for inserting indents.
|
|
173
|
+
*
|
|
174
|
+
* @default false
|
|
175
|
+
*/
|
|
176
|
+
moveFocusOnTab?: boolean;
|
|
99
177
|
}
|
|
100
178
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
179
|
+
const format = (view: EditorView): void => {
|
|
180
|
+
try {
|
|
181
|
+
const doc = view.state.doc.toString();
|
|
182
|
+
const { formattedQuery, newCursorPos } = formatQuery(doc, {
|
|
183
|
+
cursorPosition: view.state.selection.main.anchor,
|
|
184
|
+
});
|
|
185
|
+
view.dispatch({
|
|
186
|
+
changes: {
|
|
187
|
+
from: 0,
|
|
188
|
+
to: doc.length,
|
|
189
|
+
insert: formattedQuery,
|
|
190
|
+
},
|
|
191
|
+
selection: { anchor: newCursorPos },
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// Formatting failed, likely because of a syntax error
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const executeKeybinding = (
|
|
199
|
+
onExecute?: (cmd: string) => void,
|
|
200
|
+
newLineOnEnter?: boolean,
|
|
201
|
+
flush?: () => void,
|
|
202
|
+
) => {
|
|
203
|
+
const keybindings: Record<string, KeyBinding> = {
|
|
204
|
+
'Shift-Enter': {
|
|
205
|
+
key: 'Shift-Enter',
|
|
206
|
+
run: insertNewline,
|
|
207
|
+
},
|
|
208
|
+
'Ctrl-Enter': {
|
|
209
|
+
key: 'Ctrl-Enter',
|
|
210
|
+
run: () => true,
|
|
211
|
+
},
|
|
212
|
+
Enter: {
|
|
213
|
+
key: 'Enter',
|
|
214
|
+
run: insertNewline,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
if (onExecute) {
|
|
218
|
+
keybindings['Ctrl-Enter'] = {
|
|
219
|
+
key: 'Ctrl-Enter',
|
|
220
|
+
mac: 'Mod-Enter',
|
|
221
|
+
preventDefault: true,
|
|
222
|
+
run: (view: EditorView) => {
|
|
223
|
+
const doc = view.state.doc.toString();
|
|
224
|
+
if (doc.trim() !== '') {
|
|
225
|
+
flush?.();
|
|
226
|
+
onExecute(doc);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (!newLineOnEnter) {
|
|
234
|
+
keybindings['Enter'] = {
|
|
235
|
+
key: 'Enter',
|
|
236
|
+
preventDefault: true,
|
|
237
|
+
run: (view: EditorView) => {
|
|
238
|
+
const doc = view.state.doc.toString();
|
|
239
|
+
if (doc.includes('\n')) {
|
|
240
|
+
// Returning false means the event will mark the event
|
|
241
|
+
// as not handled and the default behavior will be executed
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (doc.trim() !== '') {
|
|
246
|
+
flush?.();
|
|
247
|
+
onExecute(doc);
|
|
248
|
+
}
|
|
113
249
|
|
|
114
|
-
|
|
115
|
-
},
|
|
250
|
+
return true;
|
|
116
251
|
},
|
|
117
|
-
|
|
118
|
-
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return Object.values(keybindings);
|
|
257
|
+
};
|
|
119
258
|
|
|
120
259
|
const themeCompartment = new Compartment();
|
|
121
260
|
const keyBindingCompartment = new Compartment();
|
|
261
|
+
const lineNumbersCompartment = new Compartment();
|
|
262
|
+
const readOnlyCompartment = new Compartment();
|
|
263
|
+
const placeholderCompartment = new Compartment();
|
|
264
|
+
const domEventHandlerCompartment = new Compartment();
|
|
265
|
+
|
|
266
|
+
const formatLineNumber =
|
|
267
|
+
(prompt?: string) => (a: number, state: EditorState) => {
|
|
268
|
+
if (state.doc.lines === 1 && prompt !== undefined) {
|
|
269
|
+
return prompt;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return a.toString();
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
type CypherEditorState = { cypherSupportEnabled: boolean };
|
|
122
276
|
|
|
123
277
|
const ExternalEdit = Annotation.define<boolean>();
|
|
124
|
-
|
|
278
|
+
|
|
279
|
+
export class CypherEditor extends Component<
|
|
280
|
+
CypherEditorProps,
|
|
281
|
+
CypherEditorState
|
|
282
|
+
> {
|
|
125
283
|
/**
|
|
126
284
|
* The codemirror editor container.
|
|
127
285
|
*/
|
|
@@ -136,6 +294,13 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
136
294
|
editorView: React.MutableRefObject<EditorView> = createRef();
|
|
137
295
|
private schemaRef: React.MutableRefObject<CypherConfig> = createRef();
|
|
138
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Format Cypher query
|
|
299
|
+
*/
|
|
300
|
+
format() {
|
|
301
|
+
format(this.editorView.current);
|
|
302
|
+
}
|
|
303
|
+
|
|
139
304
|
/**
|
|
140
305
|
* Focus the editor
|
|
141
306
|
*/
|
|
@@ -158,13 +323,20 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
158
323
|
*/
|
|
159
324
|
setValueAndFocus(value = '') {
|
|
160
325
|
const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
|
|
326
|
+
// Normalize line endings to LF that CM expects.
|
|
327
|
+
// Prevents issues with inserted values that contain CRLF line endings.
|
|
328
|
+
// https://codemirror.net/docs/ref/?utm_source=chatgpt.com#state.EditorState^lineSeparator
|
|
329
|
+
const normalizedValue = value.replace(/\r\n/g, '\n');
|
|
161
330
|
this.editorView.current.dispatch({
|
|
162
331
|
changes: {
|
|
163
332
|
from: 0,
|
|
164
333
|
to: currentCmValue.length,
|
|
165
|
-
insert:
|
|
334
|
+
insert: normalizedValue,
|
|
335
|
+
},
|
|
336
|
+
selection: {
|
|
337
|
+
anchor: normalizedValue.length,
|
|
338
|
+
head: normalizedValue.length,
|
|
166
339
|
},
|
|
167
|
-
selection: { anchor: value.length, head: value.length },
|
|
168
340
|
});
|
|
169
341
|
this.editorView.current?.focus();
|
|
170
342
|
}
|
|
@@ -174,11 +346,24 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
174
346
|
schema: {},
|
|
175
347
|
overrideThemeBackgroundColor: false,
|
|
176
348
|
lineWrap: false,
|
|
349
|
+
showSignatureTooltipBelow: true,
|
|
177
350
|
extraKeybindings: [],
|
|
178
351
|
history: [],
|
|
179
352
|
theme: 'light',
|
|
353
|
+
lineNumbers: true,
|
|
354
|
+
newLineOnEnter: false,
|
|
355
|
+
moveFocusOnTab: false,
|
|
180
356
|
};
|
|
181
357
|
|
|
358
|
+
private debouncedOnChange = this.props.onChange
|
|
359
|
+
? debounce(
|
|
360
|
+
((value, viewUpdate) => {
|
|
361
|
+
this.props.onChange(value, viewUpdate);
|
|
362
|
+
}) satisfies CypherEditorProps['onChange'],
|
|
363
|
+
DEBOUNCE_TIME,
|
|
364
|
+
)
|
|
365
|
+
: undefined;
|
|
366
|
+
|
|
182
367
|
componentDidMount(): void {
|
|
183
368
|
const {
|
|
184
369
|
theme,
|
|
@@ -187,18 +372,34 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
187
372
|
overrideThemeBackgroundColor,
|
|
188
373
|
schema,
|
|
189
374
|
lint,
|
|
190
|
-
|
|
375
|
+
showSignatureTooltipBelow,
|
|
376
|
+
featureFlags,
|
|
191
377
|
onExecute,
|
|
378
|
+
newLineOnEnter,
|
|
192
379
|
} = this.props;
|
|
193
380
|
|
|
194
|
-
this.schemaRef.current = {
|
|
381
|
+
this.schemaRef.current = {
|
|
382
|
+
schema,
|
|
383
|
+
lint,
|
|
384
|
+
showSignatureTooltipBelow,
|
|
385
|
+
featureFlags: {
|
|
386
|
+
consoleCommands: true,
|
|
387
|
+
...featureFlags,
|
|
388
|
+
},
|
|
389
|
+
useLightVersion: false,
|
|
390
|
+
setUseLightVersion: (newVal) => {
|
|
391
|
+
if (this.schemaRef.current !== undefined) {
|
|
392
|
+
this.schemaRef.current.useLightVersion = newVal;
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
};
|
|
195
396
|
|
|
196
397
|
const themeExtension = getThemeExtension(
|
|
197
398
|
theme,
|
|
198
399
|
overrideThemeBackgroundColor,
|
|
199
400
|
);
|
|
200
401
|
|
|
201
|
-
const changeListener =
|
|
402
|
+
const changeListener = this.debouncedOnChange
|
|
202
403
|
? [
|
|
203
404
|
EditorView.updateListener.of((upt: ViewUpdate) => {
|
|
204
405
|
const wasUserEdit = !upt.transactions.some((tr) =>
|
|
@@ -208,7 +409,7 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
208
409
|
if (upt.docChanged && wasUserEdit) {
|
|
209
410
|
const doc = upt.state.doc;
|
|
210
411
|
const value = doc.toString();
|
|
211
|
-
|
|
412
|
+
this.debouncedOnChange(value, upt);
|
|
212
413
|
}
|
|
213
414
|
}),
|
|
214
415
|
]
|
|
@@ -217,24 +418,40 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
217
418
|
this.editorState.current = EditorState.create({
|
|
218
419
|
extensions: [
|
|
219
420
|
keyBindingCompartment.of(
|
|
220
|
-
keymap.of([
|
|
421
|
+
keymap.of([
|
|
422
|
+
...executeKeybinding(onExecute, newLineOnEnter, () =>
|
|
423
|
+
this.debouncedOnChange?.flush(),
|
|
424
|
+
),
|
|
425
|
+
...extraKeybindings,
|
|
426
|
+
]),
|
|
221
427
|
),
|
|
428
|
+
richClipboardCopier,
|
|
222
429
|
historyNavigation(this.props),
|
|
223
|
-
basicNeo4jSetup(),
|
|
430
|
+
basicNeo4jSetup(this.props),
|
|
224
431
|
themeCompartment.of(themeExtension),
|
|
225
432
|
changeListener,
|
|
226
433
|
cypher(this.schemaRef.current),
|
|
227
434
|
lineWrap ? EditorView.lineWrapping : [],
|
|
228
435
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
436
|
+
lineNumbersCompartment.of(
|
|
437
|
+
this.props.lineNumbers
|
|
438
|
+
? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
|
|
439
|
+
: [],
|
|
440
|
+
),
|
|
441
|
+
readOnlyCompartment.of(EditorState.readOnly.of(this.props.readonly)),
|
|
442
|
+
placeholderCompartment.of(
|
|
443
|
+
this.props.placeholder ? placeholder(this.props.placeholder) : [],
|
|
444
|
+
),
|
|
445
|
+
domEventHandlerCompartment.of(
|
|
446
|
+
this.props.domEventHandlers
|
|
447
|
+
? EditorView.domEventHandlers(this.props.domEventHandlers)
|
|
448
|
+
: [],
|
|
449
|
+
),
|
|
450
|
+
this.props.ariaLabel
|
|
451
|
+
? EditorView.contentAttributes.of({
|
|
452
|
+
'aria-label': this.props.ariaLabel,
|
|
453
|
+
})
|
|
454
|
+
: [],
|
|
238
455
|
],
|
|
239
456
|
doc: this.props.value,
|
|
240
457
|
});
|
|
@@ -249,6 +466,8 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
249
466
|
if (this.props.value) {
|
|
250
467
|
this.updateCursorPosition(this.props.value.length);
|
|
251
468
|
}
|
|
469
|
+
} else if (this.props.offset) {
|
|
470
|
+
this.updateCursorPosition(this.props.offset);
|
|
252
471
|
}
|
|
253
472
|
}
|
|
254
473
|
|
|
@@ -260,7 +479,11 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
260
479
|
// Handle externally set value
|
|
261
480
|
const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
|
|
262
481
|
|
|
263
|
-
if (
|
|
482
|
+
if (
|
|
483
|
+
this.props.value !== undefined && // If the component becomes uncontolled, we just leave the value as is
|
|
484
|
+
this.props.value !== prevProps.value && // The value prop has changed, we need to update the editor
|
|
485
|
+
this.props.value !== currentCmValue // No need to dispatch an update if the value is the same
|
|
486
|
+
) {
|
|
264
487
|
this.editorView.current.dispatch({
|
|
265
488
|
changes: {
|
|
266
489
|
from: 0,
|
|
@@ -288,6 +511,35 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
288
511
|
});
|
|
289
512
|
}
|
|
290
513
|
|
|
514
|
+
if (
|
|
515
|
+
prevProps.lineNumbers !== this.props.lineNumbers ||
|
|
516
|
+
prevProps.prompt !== this.props.prompt
|
|
517
|
+
) {
|
|
518
|
+
this.editorView.current.dispatch({
|
|
519
|
+
effects: lineNumbersCompartment.reconfigure(
|
|
520
|
+
this.props.lineNumbers
|
|
521
|
+
? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
|
|
522
|
+
: [],
|
|
523
|
+
),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (prevProps.readonly !== this.props.readonly) {
|
|
528
|
+
this.editorView.current.dispatch({
|
|
529
|
+
effects: readOnlyCompartment.reconfigure(
|
|
530
|
+
EditorState.readOnly.of(this.props.readonly),
|
|
531
|
+
),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (prevProps.placeholder !== this.props.placeholder) {
|
|
536
|
+
this.editorView.current.dispatch({
|
|
537
|
+
effects: placeholderCompartment.reconfigure(
|
|
538
|
+
this.props.placeholder ? placeholder(this.props.placeholder) : [],
|
|
539
|
+
),
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
291
543
|
if (
|
|
292
544
|
prevProps.extraKeybindings !== this.props.extraKeybindings ||
|
|
293
545
|
prevProps.onExecute !== this.props.onExecute
|
|
@@ -295,14 +547,34 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
295
547
|
this.editorView.current.dispatch({
|
|
296
548
|
effects: keyBindingCompartment.reconfigure(
|
|
297
549
|
keymap.of([
|
|
298
|
-
...executeKeybinding(
|
|
550
|
+
...executeKeybinding(
|
|
551
|
+
this.props.onExecute,
|
|
552
|
+
this.props.newLineOnEnter,
|
|
553
|
+
() => this.debouncedOnChange?.flush(),
|
|
554
|
+
),
|
|
299
555
|
...this.props.extraKeybindings,
|
|
300
556
|
]),
|
|
301
557
|
),
|
|
302
558
|
});
|
|
303
559
|
}
|
|
304
560
|
|
|
305
|
-
if (prevProps.
|
|
561
|
+
if (prevProps.domEventHandlers !== this.props.domEventHandlers) {
|
|
562
|
+
this.editorView.current.dispatch({
|
|
563
|
+
effects: domEventHandlerCompartment.reconfigure(
|
|
564
|
+
this.props.domEventHandlers
|
|
565
|
+
? EditorView.domEventHandlers(this.props.domEventHandlers)
|
|
566
|
+
: [],
|
|
567
|
+
),
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// This component rerenders on every keystroke and comparing the
|
|
572
|
+
// full lists of editor strings on every render could be expensive.
|
|
573
|
+
const didChangeHistoryEstimate =
|
|
574
|
+
prevProps.history?.length !== this.props.history?.length ||
|
|
575
|
+
prevProps.history?.[0] !== this.props.history?.[0];
|
|
576
|
+
|
|
577
|
+
if (didChangeHistoryEstimate) {
|
|
306
578
|
this.editorView.current.dispatch({
|
|
307
579
|
effects: replaceHistory.of(this.props.history ?? []),
|
|
308
580
|
});
|
|
@@ -315,10 +587,12 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
315
587
|
*/
|
|
316
588
|
this.schemaRef.current.schema = this.props.schema;
|
|
317
589
|
this.schemaRef.current.lint = this.props.lint;
|
|
590
|
+
this.schemaRef.current.featureFlags = this.props.featureFlags;
|
|
318
591
|
}
|
|
319
592
|
|
|
320
593
|
componentWillUnmount(): void {
|
|
321
594
|
this.editorView.current?.destroy();
|
|
595
|
+
cleanupWorkers();
|
|
322
596
|
}
|
|
323
597
|
|
|
324
598
|
render(): React.ReactNode {
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEBOUNCE_TIME = 200;
|