@neo4j-cypher/react-codemirror 2.0.0-next.36 → 2.0.0-next.37
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 +9 -0
- package/dist/src/CypherEditor.d.ts +6 -0
- package/dist/src/CypherEditor.js +8 -0
- package/dist/src/CypherEditor.js.map +1 -1
- 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/syntaxValidation.spec.js +0 -3
- package/dist/src/e2e_tests/syntaxValidation.spec.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/inlinePanel.js +30 -17
- package/dist/src/inlinePanel.js.map +1 -1
- package/dist/src/lang-cypher/createCypherTheme.d.ts +12 -1
- package/dist/src/lang-cypher/createCypherTheme.js +20 -1
- package/dist/src/lang-cypher/createCypherTheme.js.map +1 -1
- package/dist/src/lang-cypher/lintWorker.mjs +18 -18
- package/dist/src/themes.js +14 -0
- package/dist/src/themes.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/CypherEditor.tsx +18 -0
- package/src/diffView.ts +24 -0
- package/src/e2e_tests/syntaxValidation.spec.tsx +0 -15
- package/src/index.ts +1 -0
- package/src/inlinePanel.ts +41 -16
- package/src/lang-cypher/createCypherTheme.ts +33 -0
- package/src/lang-cypher/lintWorker.mjs +18 -18
- package/src/themes.ts +17 -0
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.37",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"codemirror",
|
|
6
6
|
"codemirror 6",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@codemirror/commands": "^6.8.1",
|
|
35
35
|
"@codemirror/language": "^6.11.2",
|
|
36
36
|
"@codemirror/lint": "^6.8.5",
|
|
37
|
+
"@codemirror/merge": "^6.12.1",
|
|
37
38
|
"@codemirror/search": "^6.5.11",
|
|
38
39
|
"@codemirror/state": "^6.5.2",
|
|
39
40
|
"@codemirror/view": "^6.38.1",
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"style-mod": "^4.1.2",
|
|
48
49
|
"vscode-languageserver-types": "^3.17.3",
|
|
49
50
|
"workerpool": "^9.3.3",
|
|
50
|
-
"@neo4j-cypher/language-support": "2.0.0-next.
|
|
51
|
-
"@neo4j-cypher/lint-worker": "1.10.1-next.
|
|
51
|
+
"@neo4j-cypher/language-support": "2.0.0-next.34",
|
|
52
|
+
"@neo4j-cypher/lint-worker": "1.10.1-next.11"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@neo4j-ndl/base": "^3.2.10",
|
package/src/CypherEditor.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type InlinePanelCallbacks,
|
|
19
19
|
type InlinePanelController,
|
|
20
20
|
} from './inlinePanel';
|
|
21
|
+
import { createDiffExtension, type DiffProps } from './diffView';
|
|
21
22
|
import {
|
|
22
23
|
formatQuery,
|
|
23
24
|
CypherLanguageService,
|
|
@@ -190,6 +191,11 @@ export interface CypherEditorProps {
|
|
|
190
191
|
* The widget DOM is only rebuilt when `pos` or `placement` change
|
|
191
192
|
*/
|
|
192
193
|
inlinePanel?: InlinePanelProps | null;
|
|
194
|
+
/**
|
|
195
|
+
* Render a unified diff of the current document against `diff.original`.
|
|
196
|
+
* Deleted lines are shown as uneditable widgets.
|
|
197
|
+
*/
|
|
198
|
+
diff?: DiffProps | null;
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
export type InlinePanelProps = {
|
|
@@ -291,6 +297,7 @@ const lineNumbersCompartment = new Compartment();
|
|
|
291
297
|
const readOnlyCompartment = new Compartment();
|
|
292
298
|
const placeholderCompartment = new Compartment();
|
|
293
299
|
const domEventHandlerCompartment = new Compartment();
|
|
300
|
+
const diffCompartment = new Compartment();
|
|
294
301
|
|
|
295
302
|
const formatLineNumber =
|
|
296
303
|
(prompt?: string) => (a: number, state: EditorState) => {
|
|
@@ -565,6 +572,9 @@ export class CypherEditor extends Component<
|
|
|
565
572
|
})
|
|
566
573
|
: [],
|
|
567
574
|
this.inlinePanelController.extension,
|
|
575
|
+
diffCompartment.of(
|
|
576
|
+
this.props.diff ? createDiffExtension(this.props.diff) : [],
|
|
577
|
+
),
|
|
568
578
|
],
|
|
569
579
|
doc: this.props.value,
|
|
570
580
|
});
|
|
@@ -734,6 +744,14 @@ export class CypherEditor extends Component<
|
|
|
734
744
|
}
|
|
735
745
|
}
|
|
736
746
|
|
|
747
|
+
if (prevProps.diff?.original !== this.props.diff?.original) {
|
|
748
|
+
this.editorView.current.dispatch({
|
|
749
|
+
effects: diffCompartment.reconfigure(
|
|
750
|
+
this.props.diff ? createDiffExtension(this.props.diff) : [],
|
|
751
|
+
),
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
737
755
|
if (prevProps.domEventHandlers !== this.props.domEventHandlers) {
|
|
738
756
|
this.editorView.current.dispatch({
|
|
739
757
|
effects: domEventHandlerCompartment.reconfigure(
|
package/src/diffView.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { unifiedMergeView } from '@codemirror/merge';
|
|
2
|
+
import type { Extension } from '@codemirror/state';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for rendering an inline diff in the editor.
|
|
6
|
+
*
|
|
7
|
+
* The diff is computed between {@link DiffProps.original} and the *current*
|
|
8
|
+
* editor document, so streaming/external updates to the document re-diff
|
|
9
|
+
* automatically against the same original.
|
|
10
|
+
*/
|
|
11
|
+
export type DiffProps = {
|
|
12
|
+
/** The baseline document the current editor content is compared against. */
|
|
13
|
+
original: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function createDiffExtension({ original }: DiffProps): Extension {
|
|
17
|
+
return unifiedMergeView({
|
|
18
|
+
original,
|
|
19
|
+
highlightChanges: true,
|
|
20
|
+
syntaxHighlightDeletions: true,
|
|
21
|
+
mergeControls: false,
|
|
22
|
+
gutter: true,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -131,11 +131,6 @@ test('Semantic errors are correctly accumulated', async ({ page, mount }) => {
|
|
|
131
131
|
|
|
132
132
|
await mount(<CypherEditor value={query} />);
|
|
133
133
|
|
|
134
|
-
await editorPage.checkErrorMessage(
|
|
135
|
-
'MATCH (n)',
|
|
136
|
-
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
|
|
137
|
-
);
|
|
138
|
-
|
|
139
134
|
await editorPage.checkErrorMessage(
|
|
140
135
|
'-1',
|
|
141
136
|
"Invalid input. '-1' is not a valid value. Must be a positive integer.",
|
|
@@ -151,11 +146,6 @@ test('Multiline errors are correctly placed', async ({ page, mount }) => {
|
|
|
151
146
|
|
|
152
147
|
await mount(<CypherEditor value={query} />);
|
|
153
148
|
|
|
154
|
-
await editorPage.checkErrorMessage(
|
|
155
|
-
'MATCH (n)',
|
|
156
|
-
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
|
|
157
|
-
);
|
|
158
|
-
|
|
159
149
|
await editorPage.checkErrorMessage(
|
|
160
150
|
'-1',
|
|
161
151
|
"Invalid input. '-1' is not a valid value. Must be a positive integer.",
|
|
@@ -171,11 +161,6 @@ test('Validation errors are correctly overlapped', async ({ page, mount }) => {
|
|
|
171
161
|
|
|
172
162
|
await mount(<CypherEditor value={query} />);
|
|
173
163
|
|
|
174
|
-
await editorPage.checkErrorMessage(
|
|
175
|
-
'-1',
|
|
176
|
-
'Query cannot conclude with CALL (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
|
|
177
|
-
);
|
|
178
|
-
|
|
179
164
|
await editorPage.checkErrorMessage(
|
|
180
165
|
'-1',
|
|
181
166
|
"Invalid input. '-1' is not a valid value. Must be a positive integer.",
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * as LanguageSupport from '@neo4j-cypher/language-support';
|
|
2
2
|
export { CypherEditor } from './CypherEditor';
|
|
3
3
|
export type { InlinePanelProps } from './CypherEditor';
|
|
4
|
+
export type { DiffProps } from './diffView';
|
|
4
5
|
export { cypher } from './lang-cypher/langCypher';
|
|
5
6
|
export { darkThemeConstants, lightThemeConstants } from './themes';
|
package/src/inlinePanel.ts
CHANGED
|
@@ -79,32 +79,57 @@ export function createInlinePanelController(): InlinePanelController {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
const buildDecoration = (options: InlinePanelShowOptions): DecorationSet =>
|
|
83
|
+
Decoration.set([
|
|
84
|
+
Decoration.widget({
|
|
85
|
+
widget: new InlinePanelWidget(options),
|
|
86
|
+
block: true,
|
|
87
|
+
side: options.placement === 'below' ? 1 : -1,
|
|
88
|
+
}).range(options.pos),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const readOptions = (
|
|
92
|
+
decorations: DecorationSet,
|
|
93
|
+
): InlinePanelShowOptions | null => {
|
|
94
|
+
const widget = decorations.iter().value?.spec?.widget;
|
|
95
|
+
return widget instanceof InlinePanelWidget ? widget.options : null;
|
|
96
|
+
};
|
|
97
|
+
|
|
82
98
|
const field = StateField.define<DecorationSet>({
|
|
83
99
|
create() {
|
|
84
100
|
return Decoration.none;
|
|
85
101
|
},
|
|
86
102
|
update(decorations, transaction) {
|
|
87
|
-
let next: DecorationSet | null = null;
|
|
88
103
|
for (const effect of transaction.effects) {
|
|
89
104
|
if (effect.is(showEffect)) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const side = effect.value.placement === 'below' ? 1 : -1;
|
|
94
|
-
next = Decoration.set([
|
|
95
|
-
Decoration.widget({
|
|
96
|
-
widget: new InlinePanelWidget(effect.value),
|
|
97
|
-
block: true,
|
|
98
|
-
side,
|
|
99
|
-
}).range(effect.value.pos),
|
|
100
|
-
]);
|
|
101
|
-
}
|
|
105
|
+
return effect.value === null
|
|
106
|
+
? Decoration.none
|
|
107
|
+
: buildDecoration(effect.value);
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
|
|
111
|
+
// Nothing to do when the panel is closed, or when the document is unchanged
|
|
112
|
+
if (decorations.size === 0 || !transaction.docChanged) {
|
|
113
|
+
return decorations.map(transaction.changes);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Re-anchor manually because CodeMirror's default mapping silently drops
|
|
117
|
+
// block-widget decorations when their anchor line is deleted
|
|
118
|
+
const options = readOptions(decorations);
|
|
119
|
+
if (options === null) {
|
|
120
|
+
return decorations.map(transaction.changes);
|
|
106
121
|
}
|
|
107
|
-
|
|
122
|
+
const side = options.placement === 'below' ? 1 : -1;
|
|
123
|
+
const lineNumber = Math.min(
|
|
124
|
+
transaction.startState.doc.lineAt(options.pos).number,
|
|
125
|
+
transaction.newDoc.lines,
|
|
126
|
+
);
|
|
127
|
+
const line = transaction.newDoc.line(lineNumber);
|
|
128
|
+
|
|
129
|
+
return buildDecoration({
|
|
130
|
+
...options,
|
|
131
|
+
pos: side === 1 ? line.to : line.from,
|
|
132
|
+
});
|
|
108
133
|
},
|
|
109
134
|
provide: (f) => EditorView.decorations.from(f),
|
|
110
135
|
});
|
|
@@ -18,6 +18,17 @@ import {
|
|
|
18
18
|
upArrowSvg,
|
|
19
19
|
} from './themeIcons';
|
|
20
20
|
|
|
21
|
+
export interface DiffColors {
|
|
22
|
+
/** Background fill for the whole inserted/changed line. */
|
|
23
|
+
insertedLine: string;
|
|
24
|
+
/** Highlight for the exact inserted text within a changed line. */
|
|
25
|
+
insertedText: string;
|
|
26
|
+
/** Background fill for the whole deleted line widget. */
|
|
27
|
+
deletedLine: string;
|
|
28
|
+
/** Highlight for the exact deleted text within a deleted chunk. */
|
|
29
|
+
deletedText: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export interface ThemeOptions {
|
|
22
33
|
dark: boolean;
|
|
23
34
|
editorSettings: {
|
|
@@ -40,6 +51,7 @@ export interface ThemeOptions {
|
|
|
40
51
|
};
|
|
41
52
|
highlightStyles: Partial<Record<HighlightedCypherTokenTypes, string>>;
|
|
42
53
|
inheritBgColor?: boolean;
|
|
54
|
+
diffColors?: DiffColors;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
export const createCypherTheme = ({
|
|
@@ -47,6 +59,7 @@ export const createCypherTheme = ({
|
|
|
47
59
|
editorSettings: settings,
|
|
48
60
|
highlightStyles,
|
|
49
61
|
inheritBgColor,
|
|
62
|
+
diffColors,
|
|
50
63
|
}: ThemeOptions): Extension => {
|
|
51
64
|
const themeOptions: Record<string, StyleSpec> = {
|
|
52
65
|
'&': {
|
|
@@ -218,6 +231,26 @@ export const createCypherTheme = ({
|
|
|
218
231
|
},
|
|
219
232
|
},
|
|
220
233
|
},
|
|
234
|
+
...(diffColors && {
|
|
235
|
+
'&.cm-merge-b .cm-changedLine': {
|
|
236
|
+
backgroundColor: diffColors.insertedLine,
|
|
237
|
+
},
|
|
238
|
+
'&.cm-merge-b .cm-changedText': {
|
|
239
|
+
background: diffColors.insertedText,
|
|
240
|
+
},
|
|
241
|
+
'&.cm-merge-b .cm-deletedChunk': {
|
|
242
|
+
backgroundColor: diffColors.deletedLine,
|
|
243
|
+
},
|
|
244
|
+
'&.cm-merge-b .cm-deletedChunk .cm-deletedText': {
|
|
245
|
+
background: diffColors.deletedText,
|
|
246
|
+
},
|
|
247
|
+
// Hide the empty deletion widget that unifiedMergeView renders
|
|
248
|
+
// when the original document is empty (its only line is an empty <del>).
|
|
249
|
+
'&.cm-merge-b .cm-deletedChunk:has(> .cm-deletedLine:only-child > del > br:only-child)':
|
|
250
|
+
{
|
|
251
|
+
display: 'none',
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
221
254
|
};
|
|
222
255
|
|
|
223
256
|
const themeExtension = EditorView.theme(themeOptions, { dark });
|