@neo4j-cypher/react-codemirror 2.0.0-next.4 → 2.0.0-next.40
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 +350 -0
- package/README.md +3 -2
- package/dist/src/CypherEditor.d.ts +243 -0
- package/dist/src/CypherEditor.js +513 -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/diffView.d.ts +13 -0
- package/dist/src/diffView.js +11 -0
- package/dist/src/diffView.js.map +1 -0
- package/dist/src/e2e_tests/autoCompletion.spec.js +335 -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 +64 -0
- package/dist/src/e2e_tests/sanityChecks.spec.js.map +1 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js +227 -0
- package/dist/src/e2e_tests/signatureHelp.spec.js.map +1 -0
- package/dist/src/e2e_tests/snippets.spec.js +14 -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 +129 -0
- package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -0
- package/dist/src/editorActions.d.ts +17 -0
- package/dist/src/editorActions.js +194 -0
- package/dist/src/editorActions.js.map +1 -0
- package/dist/src/historyNavigation.js +163 -0
- package/dist/src/historyNavigation.js.map +1 -0
- package/dist/src/hostCallbacks.d.ts +9 -0
- package/dist/src/hostCallbacks.js +2 -0
- package/dist/src/hostCallbacks.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 +6 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inlinePanel.d.ts +27 -0
- package/dist/src/inlinePanel.js +86 -0
- package/dist/src/inlinePanel.js.map +1 -0
- package/dist/src/lang-cypher/autocomplete.d.ts +6 -0
- package/dist/src/lang-cypher/autocomplete.js +116 -0
- package/dist/src/lang-cypher/autocomplete.js.map +1 -0
- package/dist/{types → src}/lang-cypher/constants.d.ts +13 -0
- package/dist/src/lang-cypher/constants.js +74 -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/{types/lang-cypher/create-cypher-theme.d.ts → src/lang-cypher/createCypherTheme.d.ts} +12 -1
- package/dist/src/lang-cypher/createCypherTheme.js +202 -0
- package/dist/src/lang-cypher/createCypherTheme.js.map +1 -0
- package/dist/src/lang-cypher/langCypher.d.ts +14 -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 +2035 -0
- package/dist/src/lang-cypher/parser-adapter.d.ts +19 -0
- package/dist/src/lang-cypher/parser-adapter.js +112 -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 +108 -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 +60 -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/src/themes.d.ts +5 -0
- package/dist/src/themes.js +107 -0
- package/dist/src/themes.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +62 -62
- package/src/CypherEditor.test.tsx +203 -0
- package/src/CypherEditor.tsx +584 -48
- package/src/constants.ts +1 -0
- package/src/diffView.ts +24 -0
- package/src/e2e_tests/autoCompletion.spec.tsx +577 -0
- package/src/e2e_tests/configuration.spec.tsx +111 -0
- package/src/e2e_tests/debounce.spec.tsx +102 -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} +28 -20
- package/src/e2e_tests/signatureHelp.spec.tsx +443 -0
- package/src/e2e_tests/snippets.spec.tsx +19 -0
- package/src/e2e_tests/{syntax-highlighting.spec.tsx → syntaxHighlighting.spec.tsx} +26 -24
- package/src/e2e_tests/syntaxValidation.spec.tsx +252 -0
- package/src/editorActions.ts +253 -0
- package/src/{history-navigation.ts → historyNavigation.ts} +1 -1
- package/src/hostCallbacks.ts +9 -0
- package/src/icons.ts +3 -0
- package/src/index.ts +4 -2
- package/src/inlinePanel.ts +140 -0
- package/src/lang-cypher/autocomplete.ts +98 -18
- package/src/lang-cypher/constants.ts +32 -0
- package/src/lang-cypher/contants.test.ts +7 -3
- package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +85 -23
- package/src/lang-cypher/langCypher.ts +46 -0
- package/src/lang-cypher/lintWorker.mjs +2035 -0
- package/src/lang-cypher/parser-adapter.ts +145 -0
- package/src/lang-cypher/signatureHelp.ts +154 -0
- package/src/lang-cypher/syntaxValidation.ts +76 -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 +61 -69
- package/src/viteEnv.d.ts +1 -0
- package/dist/cjs/index.cjs +0 -1441
- package/dist/cjs/index.cjs.map +0 -7
- package/dist/esm/index.mjs +0 -1464
- package/dist/esm/index.mjs.map +0 -7
- package/dist/types/CypherEditor.d.ts +0 -113
- 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/themes.d.ts +0 -11
- 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/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,17 +10,54 @@ import {
|
|
|
9
10
|
KeyBinding,
|
|
10
11
|
keymap,
|
|
11
12
|
lineNumbers,
|
|
13
|
+
placeholder,
|
|
12
14
|
ViewUpdate,
|
|
13
15
|
} from '@codemirror/view';
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
+
import {
|
|
17
|
+
createInlinePanelController,
|
|
18
|
+
type InlinePanelCallbacks,
|
|
19
|
+
type InlinePanelController,
|
|
20
|
+
} from './inlinePanel';
|
|
21
|
+
import { createDiffExtension, type DiffProps } from './diffView';
|
|
22
|
+
import {
|
|
23
|
+
createEditorActionsController,
|
|
24
|
+
type EditorActionsCallbacks,
|
|
25
|
+
type EditorActionsController,
|
|
26
|
+
} from './editorActions';
|
|
27
|
+
import {
|
|
28
|
+
formatQuery,
|
|
29
|
+
CypherLanguageService,
|
|
30
|
+
type DbSchema,
|
|
31
|
+
} from '@neo4j-cypher/language-support';
|
|
32
|
+
import debounce from 'lodash.debounce';
|
|
33
|
+
import { Component, createRef, type ReactNode } from 'react';
|
|
34
|
+
import { createPortal } from 'react-dom';
|
|
35
|
+
import { DEBOUNCE_TIME } from './constants';
|
|
16
36
|
import {
|
|
17
37
|
replaceHistory,
|
|
18
38
|
replMode as historyNavigation,
|
|
19
|
-
} from './
|
|
20
|
-
import { cypher, CypherConfig } from './lang-cypher/
|
|
21
|
-
import {
|
|
39
|
+
} from './historyNavigation';
|
|
40
|
+
import { cypher, CypherConfig } from './lang-cypher/langCypher';
|
|
41
|
+
import { cleanupWorkers } from './lang-cypher/syntaxValidation';
|
|
42
|
+
import { basicNeo4jSetup } from './neo4jSetup';
|
|
22
43
|
import { getThemeExtension } from './themes';
|
|
44
|
+
import { richClipboardCopier } from './richClipboardCopier';
|
|
45
|
+
import { LintWorker } from '@neo4j-cypher/lint-worker';
|
|
46
|
+
import workerpool from 'workerpool';
|
|
47
|
+
|
|
48
|
+
type DomEventHandlers = Parameters<typeof EditorView.domEventHandlers>[0];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize CRLF line endings to LF, which is what CodeMirror expects.
|
|
52
|
+
* CodeMirror collapses `\r\n` into a single line break, so a raw `value`
|
|
53
|
+
* string with CRLF endings is longer than the resulting document. Normalizing
|
|
54
|
+
* up front keeps the document length in sync with the value length used for
|
|
55
|
+
* cursor placement, and avoids "Selection points outside of document" errors.
|
|
56
|
+
* https://codemirror.net/docs/ref/#state.EditorState^lineSeparator
|
|
57
|
+
*/
|
|
58
|
+
function normalizeLineEndings(value: string): string {
|
|
59
|
+
return value.replace(/\r\n/g, '\n');
|
|
60
|
+
}
|
|
23
61
|
|
|
24
62
|
export interface CypherEditorProps {
|
|
25
63
|
/**
|
|
@@ -40,7 +78,14 @@ export interface CypherEditorProps {
|
|
|
40
78
|
*/
|
|
41
79
|
onExecute?: (cmd: string) => void;
|
|
42
80
|
/**
|
|
43
|
-
*
|
|
81
|
+
* If true, pressing enter will add a new line to the editor and cmd/ctrl + enter will execute the query.
|
|
82
|
+
* Otherwise pressing enter on a single line will execute the query.
|
|
83
|
+
*
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
newLineOnEnter?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The editor history navigable via up/down arrow keys. Order newest to oldest.
|
|
44
89
|
* Add to this list with the `onExecute` callback for REPL style history.
|
|
45
90
|
*/
|
|
46
91
|
history?: string[];
|
|
@@ -57,6 +102,10 @@ export interface CypherEditorProps {
|
|
|
57
102
|
* @default false
|
|
58
103
|
*/
|
|
59
104
|
autofocus?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Where to place the cursor in the query. Cannot be enabled at the same time than autofocus
|
|
107
|
+
*/
|
|
108
|
+
offset?: number;
|
|
60
109
|
/**
|
|
61
110
|
* Whether the editor should wrap lines.
|
|
62
111
|
*
|
|
@@ -69,6 +118,20 @@ export interface CypherEditorProps {
|
|
|
69
118
|
* @default true
|
|
70
119
|
*/
|
|
71
120
|
lint?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Whether the signature help tooltip should be shown below the text.
|
|
123
|
+
* If false, it will be shown above.
|
|
124
|
+
*
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
showSignatureTooltipBelow?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Internal feature flags for the editor. Don't use in production
|
|
130
|
+
*
|
|
131
|
+
*/
|
|
132
|
+
featureFlags?: {
|
|
133
|
+
consoleCommands?: boolean;
|
|
134
|
+
};
|
|
72
135
|
/**
|
|
73
136
|
* The schema to use for autocompletion and linting.
|
|
74
137
|
*
|
|
@@ -96,32 +159,253 @@ export interface CypherEditorProps {
|
|
|
96
159
|
* @param {ViewUpdate} viewUpdate - the view update from codemirror
|
|
97
160
|
*/
|
|
98
161
|
onChange?(value: string, viewUpdate: ViewUpdate): void;
|
|
99
|
-
}
|
|
100
162
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Map of event handlers to add to the editor.
|
|
165
|
+
*
|
|
166
|
+
* Note that the props are compared by reference, meaning object defined inline
|
|
167
|
+
* will cause the editor to re-render (much like the style prop does in this example:
|
|
168
|
+
* <div style={{}} />
|
|
169
|
+
*
|
|
170
|
+
* Memoize the object if you want/need to avoid this.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* // listen to blur events
|
|
174
|
+
* <CypherEditor domEventHandlers={{blur: () => console.log("blur event fired")}} />
|
|
175
|
+
*/
|
|
176
|
+
domEventHandlers?: DomEventHandlers;
|
|
177
|
+
/**
|
|
178
|
+
* Placeholder text to display when the editor is empty.
|
|
179
|
+
*/
|
|
180
|
+
placeholder?: string;
|
|
181
|
+
/**
|
|
182
|
+
* Whether the editor should show line numbers.
|
|
183
|
+
*
|
|
184
|
+
* @default true
|
|
185
|
+
*/
|
|
186
|
+
lineNumbers?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Whether the editor is read-only.
|
|
189
|
+
*
|
|
190
|
+
* @default false
|
|
191
|
+
*/
|
|
192
|
+
readonly?: boolean;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* String value to assign to the aria-label attribute of the editor.
|
|
196
|
+
*/
|
|
197
|
+
ariaLabel?: string;
|
|
113
198
|
|
|
114
|
-
|
|
115
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Whether keybindings for inserting indents with the Tab key should be disabled.
|
|
201
|
+
*
|
|
202
|
+
* true will not create keybindings for inserting indents.
|
|
203
|
+
* false will create keybindings for inserting indents.
|
|
204
|
+
*
|
|
205
|
+
* @default false
|
|
206
|
+
*/
|
|
207
|
+
moveFocusOnTab?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Render a panel as a block widget inside the editor.
|
|
210
|
+
* The widget DOM is only rebuilt when `pos` or `placement` change
|
|
211
|
+
*/
|
|
212
|
+
inlinePanel?: InlinePanelProps | null;
|
|
213
|
+
/**
|
|
214
|
+
* Render a unified diff of the current document against `diff.original`.
|
|
215
|
+
* Deleted lines are shown as uneditable widgets.
|
|
216
|
+
*/
|
|
217
|
+
diff?: DiffProps | null;
|
|
218
|
+
/**
|
|
219
|
+
* React content rendered as a cluster of action buttons (e.g. a context menu
|
|
220
|
+
* + Run) pinned to the top-right corner of the editor. Omit (or `null`) to
|
|
221
|
+
* hide it.
|
|
222
|
+
*/
|
|
223
|
+
editorActions?: ReactNode;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type InlinePanelProps = {
|
|
227
|
+
/**
|
|
228
|
+
* Position the panel anchors to.
|
|
229
|
+
*/
|
|
230
|
+
pos: number;
|
|
231
|
+
/**
|
|
232
|
+
* Whether to render above or below the line
|
|
233
|
+
*
|
|
234
|
+
* @default 'above'
|
|
235
|
+
*/
|
|
236
|
+
placement?: 'above' | 'below';
|
|
237
|
+
} & InlinePanelCallbacks;
|
|
238
|
+
|
|
239
|
+
const format = (view: EditorView): void => {
|
|
240
|
+
try {
|
|
241
|
+
const doc = view.state.doc.toString();
|
|
242
|
+
const { formattedQuery, newCursorPos } = formatQuery(doc, {
|
|
243
|
+
cursorPosition: view.state.selection.main.anchor,
|
|
244
|
+
});
|
|
245
|
+
view.dispatch({
|
|
246
|
+
changes: {
|
|
247
|
+
from: 0,
|
|
248
|
+
to: doc.length,
|
|
249
|
+
insert: formattedQuery,
|
|
250
|
+
},
|
|
251
|
+
selection: { anchor: newCursorPos },
|
|
252
|
+
});
|
|
253
|
+
} catch {
|
|
254
|
+
// Formatting failed, likely because of a syntax error
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const executeKeybinding = (
|
|
259
|
+
onExecute?: (cmd: string) => void,
|
|
260
|
+
newLineOnEnter?: boolean,
|
|
261
|
+
flush?: () => void,
|
|
262
|
+
) => {
|
|
263
|
+
const keybindings: Record<string, KeyBinding> = {
|
|
264
|
+
'Shift-Enter': {
|
|
265
|
+
key: 'Shift-Enter',
|
|
266
|
+
run: insertNewline,
|
|
267
|
+
},
|
|
268
|
+
'Ctrl-Enter': {
|
|
269
|
+
key: 'Ctrl-Enter',
|
|
270
|
+
run: () => true,
|
|
271
|
+
},
|
|
272
|
+
Enter: {
|
|
273
|
+
key: 'Enter',
|
|
274
|
+
run: insertNewline,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
if (onExecute) {
|
|
278
|
+
keybindings['Ctrl-Enter'] = {
|
|
279
|
+
key: 'Ctrl-Enter',
|
|
280
|
+
mac: 'Mod-Enter',
|
|
281
|
+
preventDefault: true,
|
|
282
|
+
run: (view: EditorView) => {
|
|
283
|
+
const doc = view.state.doc.toString();
|
|
284
|
+
if (doc.trim() !== '') {
|
|
285
|
+
flush?.();
|
|
286
|
+
onExecute(doc);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return true;
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
if (!newLineOnEnter) {
|
|
294
|
+
keybindings['Enter'] = {
|
|
295
|
+
key: 'Enter',
|
|
296
|
+
preventDefault: true,
|
|
297
|
+
run: (view: EditorView) => {
|
|
298
|
+
const doc = view.state.doc.toString();
|
|
299
|
+
if (doc.includes('\n')) {
|
|
300
|
+
// Returning false means the event will mark the event
|
|
301
|
+
// as not handled and the default behavior will be executed
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (doc.trim() !== '') {
|
|
306
|
+
flush?.();
|
|
307
|
+
onExecute(doc);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return true;
|
|
116
311
|
},
|
|
117
|
-
|
|
118
|
-
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return Object.values(keybindings);
|
|
317
|
+
};
|
|
119
318
|
|
|
120
319
|
const themeCompartment = new Compartment();
|
|
121
320
|
const keyBindingCompartment = new Compartment();
|
|
321
|
+
const lineNumbersCompartment = new Compartment();
|
|
322
|
+
const readOnlyCompartment = new Compartment();
|
|
323
|
+
const placeholderCompartment = new Compartment();
|
|
324
|
+
const domEventHandlerCompartment = new Compartment();
|
|
325
|
+
const diffCompartment = new Compartment();
|
|
326
|
+
|
|
327
|
+
const formatLineNumber =
|
|
328
|
+
(prompt?: string) => (a: number, state: EditorState) => {
|
|
329
|
+
if (state.doc.lines === 1 && prompt !== undefined) {
|
|
330
|
+
return prompt;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return a.toString();
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
type CypherEditorState = {
|
|
337
|
+
editorActionsContainer: HTMLElement | null;
|
|
338
|
+
};
|
|
122
339
|
|
|
123
340
|
const ExternalEdit = Annotation.define<boolean>();
|
|
124
|
-
|
|
341
|
+
const WorkerURL = new URL('./lang-cypher/lintWorker.mjs', import.meta.url)
|
|
342
|
+
.pathname;
|
|
343
|
+
|
|
344
|
+
class CodemirrorSymbolFetcher {
|
|
345
|
+
constructor(languageService: CypherLanguageService) {
|
|
346
|
+
this.languageService = languageService;
|
|
347
|
+
}
|
|
348
|
+
private languageService: CypherLanguageService;
|
|
349
|
+
private processing = false;
|
|
350
|
+
private nextJob:
|
|
351
|
+
| {
|
|
352
|
+
query: string;
|
|
353
|
+
schema: DbSchema;
|
|
354
|
+
}
|
|
355
|
+
| undefined;
|
|
356
|
+
private symbolTablePool = workerpool.pool(WorkerURL, {
|
|
357
|
+
minWorkers: 1,
|
|
358
|
+
workerOpts: { type: 'module' },
|
|
359
|
+
workerTerminateTimeout: 2000,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
public queueSymbolJob(query: string, schema: DbSchema) {
|
|
363
|
+
this.nextJob = { query, schema };
|
|
364
|
+
if (!this.processing) {
|
|
365
|
+
void this.processJobQueue();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public terminate() {
|
|
370
|
+
this.nextJob = undefined;
|
|
371
|
+
void this.symbolTablePool.terminate();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private async processJobQueue() {
|
|
375
|
+
this.processing = true;
|
|
376
|
+
while (this.nextJob) {
|
|
377
|
+
try {
|
|
378
|
+
const query = this.nextJob.query;
|
|
379
|
+
const dbSchema = this.nextJob.schema;
|
|
380
|
+
this.nextJob = undefined;
|
|
381
|
+
const proxyWorker =
|
|
382
|
+
(await this.symbolTablePool.proxy()) as unknown as LintWorker;
|
|
383
|
+
|
|
384
|
+
const result = await proxyWorker.lintCypherQuery(query, dbSchema);
|
|
385
|
+
|
|
386
|
+
if (result.symbolTables) {
|
|
387
|
+
this.languageService.setSymbolsInfo({
|
|
388
|
+
query,
|
|
389
|
+
symbolTables: result.symbolTables,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
} catch (err) {
|
|
393
|
+
//eslint-disable-next-line
|
|
394
|
+
console.log('Symbol table calculation failed ' + String(err));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.processing = false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export class CypherEditor extends Component<
|
|
402
|
+
CypherEditorProps,
|
|
403
|
+
CypherEditorState
|
|
404
|
+
> {
|
|
405
|
+
/**
|
|
406
|
+
* The symbol fetcher object used to fetch the current symbol table on document changes
|
|
407
|
+
*/
|
|
408
|
+
symbolFetcher: CodemirrorSymbolFetcher;
|
|
125
409
|
/**
|
|
126
410
|
* The codemirror editor container.
|
|
127
411
|
*/
|
|
@@ -135,6 +419,25 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
135
419
|
*/
|
|
136
420
|
editorView: React.MutableRefObject<EditorView> = createRef();
|
|
137
421
|
private schemaRef: React.MutableRefObject<CypherConfig> = createRef();
|
|
422
|
+
private inlinePanelController: InlinePanelController | null = null;
|
|
423
|
+
private editorActionsController: EditorActionsController | null = null;
|
|
424
|
+
|
|
425
|
+
state: CypherEditorState = {
|
|
426
|
+
editorActionsContainer: null,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
private editorActionsCallbacks: EditorActionsCallbacks = {
|
|
430
|
+
onMount: (container) =>
|
|
431
|
+
this.setState({ editorActionsContainer: container }),
|
|
432
|
+
onUnmount: () => this.setState({ editorActionsContainer: null }),
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Format Cypher query
|
|
437
|
+
*/
|
|
438
|
+
format() {
|
|
439
|
+
format(this.editorView.current);
|
|
440
|
+
}
|
|
138
441
|
|
|
139
442
|
/**
|
|
140
443
|
* Focus the editor
|
|
@@ -148,8 +451,17 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
148
451
|
* For example, to move the cursor to the end of the editor, use `value.length`
|
|
149
452
|
*/
|
|
150
453
|
updateCursorPosition(position: number) {
|
|
151
|
-
this.editorView.current
|
|
152
|
-
|
|
454
|
+
const view = this.editorView.current;
|
|
455
|
+
if (!view) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Clamp to the doc length; `position` may come from a raw value longer than
|
|
460
|
+
// the doc (e.g. CRLF endings), which would throw "Selection points outside
|
|
461
|
+
// of document".
|
|
462
|
+
const clamped = Math.max(0, Math.min(position, view.state.doc.length));
|
|
463
|
+
view.dispatch({
|
|
464
|
+
selection: { anchor: clamped, head: clamped },
|
|
153
465
|
});
|
|
154
466
|
}
|
|
155
467
|
|
|
@@ -158,13 +470,17 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
158
470
|
*/
|
|
159
471
|
setValueAndFocus(value = '') {
|
|
160
472
|
const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
|
|
473
|
+
const normalizedValue = normalizeLineEndings(value);
|
|
161
474
|
this.editorView.current.dispatch({
|
|
162
475
|
changes: {
|
|
163
476
|
from: 0,
|
|
164
477
|
to: currentCmValue.length,
|
|
165
|
-
insert:
|
|
478
|
+
insert: normalizedValue,
|
|
479
|
+
},
|
|
480
|
+
selection: {
|
|
481
|
+
anchor: normalizedValue.length,
|
|
482
|
+
head: normalizedValue.length,
|
|
166
483
|
},
|
|
167
|
-
selection: { anchor: value.length, head: value.length },
|
|
168
484
|
});
|
|
169
485
|
this.editorView.current?.focus();
|
|
170
486
|
}
|
|
@@ -174,11 +490,24 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
174
490
|
schema: {},
|
|
175
491
|
overrideThemeBackgroundColor: false,
|
|
176
492
|
lineWrap: false,
|
|
493
|
+
showSignatureTooltipBelow: true,
|
|
177
494
|
extraKeybindings: [],
|
|
178
495
|
history: [],
|
|
179
496
|
theme: 'light',
|
|
497
|
+
lineNumbers: true,
|
|
498
|
+
newLineOnEnter: false,
|
|
499
|
+
moveFocusOnTab: false,
|
|
180
500
|
};
|
|
181
501
|
|
|
502
|
+
private debouncedOnChange = this.props.onChange
|
|
503
|
+
? debounce(
|
|
504
|
+
((value, viewUpdate) => {
|
|
505
|
+
this.props.onChange(value, viewUpdate);
|
|
506
|
+
}) satisfies CypherEditorProps['onChange'],
|
|
507
|
+
DEBOUNCE_TIME,
|
|
508
|
+
)
|
|
509
|
+
: undefined;
|
|
510
|
+
|
|
182
511
|
componentDidMount(): void {
|
|
183
512
|
const {
|
|
184
513
|
theme,
|
|
@@ -187,20 +516,50 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
187
516
|
overrideThemeBackgroundColor,
|
|
188
517
|
schema,
|
|
189
518
|
lint,
|
|
190
|
-
|
|
519
|
+
showSignatureTooltipBelow,
|
|
520
|
+
featureFlags,
|
|
191
521
|
onExecute,
|
|
522
|
+
newLineOnEnter,
|
|
192
523
|
} = this.props;
|
|
193
524
|
|
|
194
|
-
this.schemaRef.current = {
|
|
525
|
+
this.schemaRef.current = {
|
|
526
|
+
languageService: new CypherLanguageService(),
|
|
527
|
+
schema,
|
|
528
|
+
lint,
|
|
529
|
+
showSignatureTooltipBelow,
|
|
530
|
+
featureFlags: {
|
|
531
|
+
consoleCommands: true,
|
|
532
|
+
...featureFlags,
|
|
533
|
+
},
|
|
534
|
+
useLightVersion: false,
|
|
535
|
+
setUseLightVersion: (newVal) => {
|
|
536
|
+
if (this.schemaRef.current !== undefined) {
|
|
537
|
+
this.schemaRef.current.useLightVersion = newVal;
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
this.symbolFetcher = new CodemirrorSymbolFetcher(
|
|
543
|
+
this.schemaRef.current.languageService,
|
|
544
|
+
);
|
|
195
545
|
|
|
196
546
|
const themeExtension = getThemeExtension(
|
|
197
547
|
theme,
|
|
198
548
|
overrideThemeBackgroundColor,
|
|
199
549
|
);
|
|
200
550
|
|
|
201
|
-
|
|
551
|
+
this.inlinePanelController = createInlinePanelController();
|
|
552
|
+
this.editorActionsController = createEditorActionsController();
|
|
553
|
+
|
|
554
|
+
const changeListener = this.debouncedOnChange
|
|
202
555
|
? [
|
|
203
556
|
EditorView.updateListener.of((upt: ViewUpdate) => {
|
|
557
|
+
if (upt.docChanged) {
|
|
558
|
+
this.symbolFetcher.queueSymbolJob(
|
|
559
|
+
upt.state.doc.toString(),
|
|
560
|
+
this.schemaRef.current.schema,
|
|
561
|
+
);
|
|
562
|
+
}
|
|
204
563
|
const wasUserEdit = !upt.transactions.some((tr) =>
|
|
205
564
|
tr.annotation(ExternalEdit),
|
|
206
565
|
);
|
|
@@ -208,7 +567,7 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
208
567
|
if (upt.docChanged && wasUserEdit) {
|
|
209
568
|
const doc = upt.state.doc;
|
|
210
569
|
const value = doc.toString();
|
|
211
|
-
|
|
570
|
+
this.debouncedOnChange(value, upt);
|
|
212
571
|
}
|
|
213
572
|
}),
|
|
214
573
|
]
|
|
@@ -217,26 +576,53 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
217
576
|
this.editorState.current = EditorState.create({
|
|
218
577
|
extensions: [
|
|
219
578
|
keyBindingCompartment.of(
|
|
220
|
-
keymap.of([
|
|
579
|
+
keymap.of([
|
|
580
|
+
...executeKeybinding(onExecute, newLineOnEnter, () =>
|
|
581
|
+
this.debouncedOnChange?.flush(),
|
|
582
|
+
),
|
|
583
|
+
...extraKeybindings,
|
|
584
|
+
]),
|
|
221
585
|
),
|
|
586
|
+
richClipboardCopier,
|
|
222
587
|
historyNavigation(this.props),
|
|
223
|
-
basicNeo4jSetup(),
|
|
588
|
+
basicNeo4jSetup(this.props),
|
|
224
589
|
themeCompartment.of(themeExtension),
|
|
225
590
|
changeListener,
|
|
226
591
|
cypher(this.schemaRef.current),
|
|
227
592
|
lineWrap ? EditorView.lineWrapping : [],
|
|
228
593
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
594
|
+
lineNumbersCompartment.of(
|
|
595
|
+
this.props.lineNumbers
|
|
596
|
+
? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
|
|
597
|
+
: [],
|
|
598
|
+
),
|
|
599
|
+
readOnlyCompartment.of(EditorState.readOnly.of(this.props.readonly)),
|
|
600
|
+
placeholderCompartment.of(
|
|
601
|
+
this.props.placeholder ? placeholder(this.props.placeholder) : [],
|
|
602
|
+
),
|
|
603
|
+
domEventHandlerCompartment.of(
|
|
604
|
+
this.props.domEventHandlers
|
|
605
|
+
? EditorView.domEventHandlers(this.props.domEventHandlers)
|
|
606
|
+
: [],
|
|
607
|
+
),
|
|
608
|
+
this.props.ariaLabel
|
|
609
|
+
? EditorView.contentAttributes.of({
|
|
610
|
+
'aria-label': this.props.ariaLabel,
|
|
611
|
+
})
|
|
612
|
+
: [],
|
|
613
|
+
!this.props.moveFocusOnTab
|
|
614
|
+
? EditorView.contentAttributes.of({
|
|
615
|
+
'aria-description':
|
|
616
|
+
'Press Escape to leave the editor and continue tabbing through the page',
|
|
617
|
+
})
|
|
618
|
+
: [],
|
|
619
|
+
this.inlinePanelController.extension,
|
|
620
|
+
diffCompartment.of(
|
|
621
|
+
this.props.diff ? createDiffExtension(this.props.diff) : [],
|
|
622
|
+
),
|
|
623
|
+
this.editorActionsController.extension,
|
|
238
624
|
],
|
|
239
|
-
doc: this.props.value,
|
|
625
|
+
doc: normalizeLineEndings(this.props.value ?? ''),
|
|
240
626
|
});
|
|
241
627
|
|
|
242
628
|
this.editorView.current = new EditorView({
|
|
@@ -249,7 +635,72 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
249
635
|
if (this.props.value) {
|
|
250
636
|
this.updateCursorPosition(this.props.value.length);
|
|
251
637
|
}
|
|
638
|
+
} else if (this.props.offset) {
|
|
639
|
+
this.updateCursorPosition(this.props.offset);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (this.props.inlinePanel) {
|
|
643
|
+
this.openInlinePanel(this.props.inlinePanel);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (this.props.editorActions != null) {
|
|
647
|
+
this.setEditorActions(true);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private setEditorActions(active: boolean): void {
|
|
652
|
+
const view = this.editorView.current;
|
|
653
|
+
const controller = this.editorActionsController;
|
|
654
|
+
if (!view || !controller) {
|
|
655
|
+
return;
|
|
252
656
|
}
|
|
657
|
+
|
|
658
|
+
view.dispatch({
|
|
659
|
+
effects: controller.set(active ? this.editorActionsCallbacks : null),
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private openInlinePanel(
|
|
664
|
+
props: NonNullable<CypherEditorProps['inlinePanel']>,
|
|
665
|
+
): void {
|
|
666
|
+
const view = this.editorView.current;
|
|
667
|
+
const controller = this.inlinePanelController;
|
|
668
|
+
if (!view || !controller) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const pos = Math.max(0, Math.min(props.pos, view.state.doc.length));
|
|
673
|
+
const line = view.state.doc.lineAt(pos);
|
|
674
|
+
controller.updateCallbacks({
|
|
675
|
+
onMount: props.onMount,
|
|
676
|
+
onUnmount: props.onUnmount,
|
|
677
|
+
});
|
|
678
|
+
view.dispatch({
|
|
679
|
+
effects: controller.show({
|
|
680
|
+
pos: props.placement === 'below' ? line.to : line.from,
|
|
681
|
+
placement: props.placement,
|
|
682
|
+
onMount: props.onMount,
|
|
683
|
+
onUnmount: props.onUnmount,
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private updateInlinePanel(
|
|
689
|
+
props: NonNullable<CypherEditorProps['inlinePanel']>,
|
|
690
|
+
): void {
|
|
691
|
+
this.inlinePanelController?.updateCallbacks({
|
|
692
|
+
onMount: props.onMount,
|
|
693
|
+
onUnmount: props.onUnmount,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private closeInlinePanel(): void {
|
|
698
|
+
const view = this.editorView.current;
|
|
699
|
+
const controller = this.inlinePanelController;
|
|
700
|
+
if (!view || !controller) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
view.dispatch({ effects: controller.hide() });
|
|
253
704
|
}
|
|
254
705
|
|
|
255
706
|
componentDidUpdate(prevProps: CypherEditorProps): void {
|
|
@@ -260,12 +711,16 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
260
711
|
// Handle externally set value
|
|
261
712
|
const currentCmValue = this.editorView.current.state?.doc.toString() ?? '';
|
|
262
713
|
|
|
263
|
-
if (
|
|
714
|
+
if (
|
|
715
|
+
this.props.value !== undefined && // If the component becomes uncontolled, we just leave the value as is
|
|
716
|
+
this.props.value !== prevProps.value && // The value prop has changed, we need to update the editor
|
|
717
|
+
this.props.value !== currentCmValue // No need to dispatch an update if the value is the same
|
|
718
|
+
) {
|
|
264
719
|
this.editorView.current.dispatch({
|
|
265
720
|
changes: {
|
|
266
721
|
from: 0,
|
|
267
722
|
to: currentCmValue.length,
|
|
268
|
-
insert: this.props.value ?? '',
|
|
723
|
+
insert: normalizeLineEndings(this.props.value ?? ''),
|
|
269
724
|
},
|
|
270
725
|
annotations: [ExternalEdit.of(true)],
|
|
271
726
|
});
|
|
@@ -288,6 +743,35 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
288
743
|
});
|
|
289
744
|
}
|
|
290
745
|
|
|
746
|
+
if (
|
|
747
|
+
prevProps.lineNumbers !== this.props.lineNumbers ||
|
|
748
|
+
prevProps.prompt !== this.props.prompt
|
|
749
|
+
) {
|
|
750
|
+
this.editorView.current.dispatch({
|
|
751
|
+
effects: lineNumbersCompartment.reconfigure(
|
|
752
|
+
this.props.lineNumbers
|
|
753
|
+
? lineNumbers({ formatNumber: formatLineNumber(this.props.prompt) })
|
|
754
|
+
: [],
|
|
755
|
+
),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (prevProps.readonly !== this.props.readonly) {
|
|
760
|
+
this.editorView.current.dispatch({
|
|
761
|
+
effects: readOnlyCompartment.reconfigure(
|
|
762
|
+
EditorState.readOnly.of(this.props.readonly),
|
|
763
|
+
),
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (prevProps.placeholder !== this.props.placeholder) {
|
|
768
|
+
this.editorView.current.dispatch({
|
|
769
|
+
effects: placeholderCompartment.reconfigure(
|
|
770
|
+
this.props.placeholder ? placeholder(this.props.placeholder) : [],
|
|
771
|
+
),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
291
775
|
if (
|
|
292
776
|
prevProps.extraKeybindings !== this.props.extraKeybindings ||
|
|
293
777
|
prevProps.onExecute !== this.props.onExecute
|
|
@@ -295,13 +779,57 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
295
779
|
this.editorView.current.dispatch({
|
|
296
780
|
effects: keyBindingCompartment.reconfigure(
|
|
297
781
|
keymap.of([
|
|
298
|
-
...executeKeybinding(
|
|
782
|
+
...executeKeybinding(
|
|
783
|
+
this.props.onExecute,
|
|
784
|
+
this.props.newLineOnEnter,
|
|
785
|
+
() => this.debouncedOnChange?.flush(),
|
|
786
|
+
),
|
|
299
787
|
...this.props.extraKeybindings,
|
|
300
788
|
]),
|
|
301
789
|
),
|
|
302
790
|
});
|
|
303
791
|
}
|
|
304
792
|
|
|
793
|
+
const prevPanel = prevProps.inlinePanel;
|
|
794
|
+
const nextPanel = this.props.inlinePanel;
|
|
795
|
+
if (prevPanel !== nextPanel) {
|
|
796
|
+
if (!nextPanel) {
|
|
797
|
+
this.closeInlinePanel();
|
|
798
|
+
} else if (
|
|
799
|
+
!prevPanel ||
|
|
800
|
+
prevPanel.pos !== nextPanel.pos ||
|
|
801
|
+
prevPanel.placement !== nextPanel.placement
|
|
802
|
+
) {
|
|
803
|
+
this.openInlinePanel(nextPanel);
|
|
804
|
+
} else {
|
|
805
|
+
this.updateInlinePanel(nextPanel);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (prevProps.diff?.original !== this.props.diff?.original) {
|
|
810
|
+
this.editorView.current.dispatch({
|
|
811
|
+
effects: diffCompartment.reconfigure(
|
|
812
|
+
this.props.diff ? createDiffExtension(this.props.diff) : [],
|
|
813
|
+
),
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const hadActions = prevProps.editorActions != null;
|
|
818
|
+
const hasActions = this.props.editorActions != null;
|
|
819
|
+
if (hadActions !== hasActions) {
|
|
820
|
+
this.setEditorActions(hasActions);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (prevProps.domEventHandlers !== this.props.domEventHandlers) {
|
|
824
|
+
this.editorView.current.dispatch({
|
|
825
|
+
effects: domEventHandlerCompartment.reconfigure(
|
|
826
|
+
this.props.domEventHandlers
|
|
827
|
+
? EditorView.domEventHandlers(this.props.domEventHandlers)
|
|
828
|
+
: [],
|
|
829
|
+
),
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
305
833
|
// This component rerenders on every keystroke and comparing the
|
|
306
834
|
// full lists of editor strings on every render could be expensive.
|
|
307
835
|
const didChangeHistoryEstimate =
|
|
@@ -321,10 +849,13 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
321
849
|
*/
|
|
322
850
|
this.schemaRef.current.schema = this.props.schema;
|
|
323
851
|
this.schemaRef.current.lint = this.props.lint;
|
|
852
|
+
this.schemaRef.current.featureFlags = this.props.featureFlags;
|
|
324
853
|
}
|
|
325
854
|
|
|
326
855
|
componentWillUnmount(): void {
|
|
327
856
|
this.editorView.current?.destroy();
|
|
857
|
+
this.symbolFetcher?.terminate();
|
|
858
|
+
cleanupWorkers();
|
|
328
859
|
}
|
|
329
860
|
|
|
330
861
|
render(): React.ReactNode {
|
|
@@ -333,11 +864,16 @@ export class CypherEditor extends Component<CypherEditorProps> {
|
|
|
333
864
|
const themeClass =
|
|
334
865
|
typeof theme === 'string' ? `cm-theme-${theme}` : 'cm-theme';
|
|
335
866
|
|
|
867
|
+
const { editorActionsContainer } = this.state;
|
|
868
|
+
|
|
336
869
|
return (
|
|
337
870
|
<div
|
|
338
871
|
ref={this.editorContainer}
|
|
339
872
|
className={`${themeClass}${className ? ` ${className}` : ''}`}
|
|
340
|
-
|
|
873
|
+
>
|
|
874
|
+
{editorActionsContainer &&
|
|
875
|
+
createPortal(this.props.editorActions, editorActionsContainer)}
|
|
876
|
+
</div>
|
|
341
877
|
);
|
|
342
878
|
}
|
|
343
879
|
}
|