@neo4j-cypher/react-codemirror 2.0.0-next.4 → 2.0.0-next.41
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 +370 -0
- package/README.md +3 -2
- package/dist/src/CypherEditor.d.ts +245 -0
- package/dist/src/CypherEditor.js +513 -0
- package/dist/src/CypherEditor.js.map +1 -0
- package/dist/src/CypherEditor.test.js +162 -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 +334 -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 +226 -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 +259 -0
- package/dist/src/editorActions.js.map +1 -0
- package/dist/{types/history-navigation.d.ts → src/historyNavigation.d.ts} +1 -1
- 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 +15 -2
- 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} +14 -3
- 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 +15 -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 +2039 -0
- package/dist/src/lang-cypher/parser-adapter.d.ts +21 -0
- package/dist/src/lang-cypher/parser-adapter.js +115 -0
- package/dist/src/lang-cypher/parser-adapter.js.map +1 -0
- package/dist/src/lang-cypher/prism-global.d.ts +1 -0
- 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 +4 -0
- package/dist/src/lang-cypher/signatureHelp.js +108 -0
- package/dist/src/lang-cypher/signatureHelp.js.map +1 -0
- package/dist/src/lang-cypher/syntaxValidation.d.ts +4 -0
- package/dist/src/lang-cypher/syntaxValidation.js +65 -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 +64 -62
- package/src/CypherEditor.test.tsx +211 -0
- package/src/CypherEditor.tsx +586 -59
- package/src/constants.ts +1 -0
- package/src/diffView.ts +24 -0
- package/src/e2e_tests/autoCompletion.spec.tsx +576 -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} +138 -19
- 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 +442 -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 +334 -0
- package/src/{history-navigation.ts → historyNavigation.ts} +6 -4
- 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 +96 -19
- package/src/lang-cypher/constants.ts +35 -2
- package/src/lang-cypher/contants.test.ts +7 -3
- package/src/lang-cypher/{create-cypher-theme.ts → createCypherTheme.ts} +97 -33
- package/src/lang-cypher/langCypher.ts +44 -0
- package/src/lang-cypher/lintWorker.mjs +2039 -0
- package/src/lang-cypher/parser-adapter.ts +146 -0
- package/src/lang-cypher/prism-global.ts +7 -0
- package/src/lang-cypher/signatureHelp.ts +154 -0
- package/src/lang-cypher/syntaxValidation.ts +81 -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} +79 -18
- package/src/richClipboardCopier.test.ts +67 -0
- package/src/richClipboardCopier.ts +99 -0
- package/src/themes.ts +63 -71
- 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/lang-cypher/syntax-validation.d.ts +0 -3
- 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/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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { StateEffect, StateField, type Extension } from '@codemirror/state';
|
|
2
|
+
import {
|
|
3
|
+
Decoration,
|
|
4
|
+
type DecorationSet,
|
|
5
|
+
EditorView,
|
|
6
|
+
WidgetType,
|
|
7
|
+
} from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
import type { HostPortalCallbacks } from './hostCallbacks';
|
|
10
|
+
|
|
11
|
+
/** Lifecycle callbacks for an inline panel. See {@link HostPortalCallbacks}. */
|
|
12
|
+
export type InlinePanelCallbacks = HostPortalCallbacks;
|
|
13
|
+
|
|
14
|
+
type InlinePanelShowOptions = {
|
|
15
|
+
/** Document position the panel anchors to. */
|
|
16
|
+
pos: number;
|
|
17
|
+
/**
|
|
18
|
+
* Where to render the panel relative to the line at `pos`. `'above'`
|
|
19
|
+
* places it before the line, `'below'` after it.
|
|
20
|
+
*
|
|
21
|
+
* @default 'above'
|
|
22
|
+
*/
|
|
23
|
+
placement?: 'above' | 'below';
|
|
24
|
+
} & InlinePanelCallbacks;
|
|
25
|
+
|
|
26
|
+
type ShowPayload = InlinePanelShowOptions | null;
|
|
27
|
+
|
|
28
|
+
export type InlinePanelController = {
|
|
29
|
+
/** CodeMirror extension to register on the editor. */
|
|
30
|
+
extension: Extension;
|
|
31
|
+
/** Build an effect that mounts the panel with the given options. */
|
|
32
|
+
show: (options: InlinePanelShowOptions) => StateEffect<ShowPayload>;
|
|
33
|
+
/** Build an effect that unmounts the panel. */
|
|
34
|
+
hide: () => StateEffect<ShowPayload>;
|
|
35
|
+
updateCallbacks: (callbacks: InlinePanelCallbacks) => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function createInlinePanelController(): InlinePanelController {
|
|
39
|
+
const showEffect = StateEffect.define<ShowPayload>();
|
|
40
|
+
|
|
41
|
+
let callbacksRef: InlinePanelCallbacks | null = null;
|
|
42
|
+
|
|
43
|
+
class InlinePanelWidget extends WidgetType {
|
|
44
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
45
|
+
|
|
46
|
+
constructor(readonly options: InlinePanelShowOptions) {
|
|
47
|
+
super();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toDOM(view: EditorView): HTMLElement {
|
|
51
|
+
const container = document.createElement('div');
|
|
52
|
+
container.className = 'cm-inline-panel';
|
|
53
|
+
this.resizeObserver = new ResizeObserver(() => view.requestMeasure());
|
|
54
|
+
this.resizeObserver.observe(container);
|
|
55
|
+
callbacksRef?.onMount(container);
|
|
56
|
+
return container;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
destroy(): void {
|
|
60
|
+
this.resizeObserver?.disconnect();
|
|
61
|
+
this.resizeObserver = null;
|
|
62
|
+
callbacksRef?.onUnmount();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
eq(other: InlinePanelWidget): boolean {
|
|
66
|
+
return (
|
|
67
|
+
other.options.pos === this.options.pos &&
|
|
68
|
+
other.options.placement === this.options.placement
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ignoreEvent(): boolean {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const buildDecoration = (options: InlinePanelShowOptions): DecorationSet =>
|
|
78
|
+
Decoration.set([
|
|
79
|
+
Decoration.widget({
|
|
80
|
+
widget: new InlinePanelWidget(options),
|
|
81
|
+
block: true,
|
|
82
|
+
side: options.placement === 'below' ? 1 : -1,
|
|
83
|
+
}).range(options.pos),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const readOptions = (
|
|
87
|
+
decorations: DecorationSet,
|
|
88
|
+
): InlinePanelShowOptions | null => {
|
|
89
|
+
const widget = decorations.iter().value?.spec?.widget;
|
|
90
|
+
return widget instanceof InlinePanelWidget ? widget.options : null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const field = StateField.define<DecorationSet>({
|
|
94
|
+
create() {
|
|
95
|
+
return Decoration.none;
|
|
96
|
+
},
|
|
97
|
+
update(decorations, transaction) {
|
|
98
|
+
for (const effect of transaction.effects) {
|
|
99
|
+
if (effect.is(showEffect)) {
|
|
100
|
+
return effect.value === null
|
|
101
|
+
? Decoration.none
|
|
102
|
+
: buildDecoration(effect.value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Nothing to do when the panel is closed, or when the document is unchanged
|
|
107
|
+
if (decorations.size === 0 || !transaction.docChanged) {
|
|
108
|
+
return decorations.map(transaction.changes);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Re-anchor manually because CodeMirror's default mapping silently drops
|
|
112
|
+
// block-widget decorations when their anchor line is deleted
|
|
113
|
+
const options = readOptions(decorations);
|
|
114
|
+
if (options === null) {
|
|
115
|
+
return decorations.map(transaction.changes);
|
|
116
|
+
}
|
|
117
|
+
const side = options.placement === 'below' ? 1 : -1;
|
|
118
|
+
const lineNumber = Math.min(
|
|
119
|
+
transaction.startState.doc.lineAt(options.pos).number,
|
|
120
|
+
transaction.newDoc.lines,
|
|
121
|
+
);
|
|
122
|
+
const line = transaction.newDoc.line(lineNumber);
|
|
123
|
+
|
|
124
|
+
return buildDecoration({
|
|
125
|
+
...options,
|
|
126
|
+
pos: side === 1 ? line.to : line.from,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
provide: (f) => EditorView.decorations.from(f),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
extension: field,
|
|
134
|
+
show: (options) => showEffect.of(options),
|
|
135
|
+
hide: () => showEffect.of(null),
|
|
136
|
+
updateCallbacks: (callbacks) => {
|
|
137
|
+
callbacksRef = callbacks;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { CompletionSource } from '@codemirror/autocomplete';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import type { Completion, CompletionSource } from '@codemirror/autocomplete';
|
|
2
|
+
import { snippet } from '@codemirror/autocomplete';
|
|
3
|
+
import { shouldAutoCompleteYield } from '@neo4j-cypher/language-support';
|
|
4
|
+
import {
|
|
5
|
+
CompletionItemKind,
|
|
6
|
+
CompletionItemTag,
|
|
7
|
+
} from 'vscode-languageserver-types';
|
|
8
|
+
import type { CompletionItemIcons } from '../icons';
|
|
9
|
+
import type { CypherConfig } from './langCypher';
|
|
10
|
+
import { getDocString } from './utils';
|
|
6
11
|
|
|
7
12
|
const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
8
13
|
const map: Record<CompletionItemKind, CompletionItemIcons> = {
|
|
@@ -28,7 +33,8 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
|
28
33
|
[CompletionItemKind.EnumMember]: 'EnumMember',
|
|
29
34
|
[CompletionItemKind.Constant]: 'Constant',
|
|
30
35
|
[CompletionItemKind.Struct]: 'Struct',
|
|
31
|
-
|
|
36
|
+
// we're miss-using the enum here as there is no `Console` kind in the predefined list
|
|
37
|
+
[CompletionItemKind.Event]: 'Console',
|
|
32
38
|
[CompletionItemKind.Operator]: 'Operator',
|
|
33
39
|
[CompletionItemKind.TypeParameter]: 'TypeParameter',
|
|
34
40
|
};
|
|
@@ -36,30 +42,101 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
|
36
42
|
return map[c];
|
|
37
43
|
};
|
|
38
44
|
|
|
45
|
+
export const completionStyles: (
|
|
46
|
+
completion: Completion & { deprecated?: boolean },
|
|
47
|
+
) => string = (completion) => {
|
|
48
|
+
if (completion.deprecated) {
|
|
49
|
+
return 'cm-deprecated-element';
|
|
50
|
+
} else {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
39
55
|
export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
|
|
40
56
|
(config) => (context) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const triggerCharacters = ['.', ':', '{', '$'];
|
|
44
|
-
const lastCharacter =
|
|
45
|
-
|
|
57
|
+
const documentText = context.state.doc.toString();
|
|
58
|
+
const offset = context.pos;
|
|
59
|
+
const triggerCharacters = ['.', ':', '{', '$', ')', ']', '-', '<'];
|
|
60
|
+
const lastCharacter = documentText.at(offset - 1);
|
|
61
|
+
const yieldTriggered = shouldAutoCompleteYield(documentText, offset);
|
|
46
62
|
const lastWord = context.matchBefore(/\w*/);
|
|
47
63
|
const inWord = lastWord.from !== lastWord.to;
|
|
48
|
-
|
|
49
64
|
const shouldTriggerCompletion =
|
|
50
|
-
inWord ||
|
|
65
|
+
inWord ||
|
|
66
|
+
context.explicit ||
|
|
67
|
+
triggerCharacters.includes(lastCharacter) ||
|
|
68
|
+
yieldTriggered;
|
|
69
|
+
|
|
70
|
+
if (config.useLightVersion && !context.explicit) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
51
73
|
|
|
52
74
|
if (!shouldTriggerCompletion) {
|
|
53
75
|
return null;
|
|
54
76
|
}
|
|
55
77
|
|
|
56
|
-
const options = autocomplete(
|
|
78
|
+
const options = config.languageService.autocomplete(
|
|
79
|
+
documentText,
|
|
80
|
+
config.schema ?? {},
|
|
81
|
+
{
|
|
82
|
+
caretPosition: offset,
|
|
83
|
+
manual: context.explicit,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
57
86
|
|
|
58
87
|
return {
|
|
59
|
-
from: context.matchBefore(/(\w
|
|
60
|
-
options: options.map((o) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
from: context.matchBefore(/(\w)*$/).from,
|
|
89
|
+
options: options.map((o) => {
|
|
90
|
+
let maybeInfo = {};
|
|
91
|
+
let emptyInfo = true;
|
|
92
|
+
const newDiv = document.createElement('div');
|
|
93
|
+
|
|
94
|
+
if (o.signature) {
|
|
95
|
+
const header = document.createElement('p');
|
|
96
|
+
header.setAttribute('class', 'cm-completionInfo-signature');
|
|
97
|
+
header.textContent = o.signature;
|
|
98
|
+
if (header.textContent.length > 0) {
|
|
99
|
+
emptyInfo = false;
|
|
100
|
+
newDiv.appendChild(header);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (o.documentation) {
|
|
105
|
+
const paragraph = document.createElement('p');
|
|
106
|
+
paragraph.textContent = getDocString(o.documentation);
|
|
107
|
+
if (paragraph.textContent.length > 0) {
|
|
108
|
+
emptyInfo = false;
|
|
109
|
+
newDiv.appendChild(paragraph);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!emptyInfo) {
|
|
114
|
+
maybeInfo = {
|
|
115
|
+
info: () => Promise.resolve(newDiv),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const deprecated =
|
|
119
|
+
o.tags?.find((tag) => tag === CompletionItemTag.Deprecated) ?? false;
|
|
120
|
+
// The negative boost moves the deprecation down the list
|
|
121
|
+
// so we offer the user the completions that are
|
|
122
|
+
// deprecated the last
|
|
123
|
+
const maybeDeprecated = deprecated
|
|
124
|
+
? { boost: -99, deprecated: true }
|
|
125
|
+
: {};
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
label: o.insertText ? o.insertText : o.label,
|
|
129
|
+
displayLabel: o.label,
|
|
130
|
+
type: completionKindToCodemirrorIcon(o.kind),
|
|
131
|
+
apply:
|
|
132
|
+
o.kind === CompletionItemKind.Snippet
|
|
133
|
+
? // codemirror requires an empty snippet space to be able to tab out of the completion
|
|
134
|
+
snippet((o.insertText ?? o.label) + '${}')
|
|
135
|
+
: undefined,
|
|
136
|
+
detail: o.detail,
|
|
137
|
+
...maybeDeprecated,
|
|
138
|
+
...maybeInfo,
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
64
141
|
};
|
|
65
142
|
};
|
|
@@ -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({
|
|
@@ -31,8 +32,36 @@ export const cypherTokenTypeToNode = (facet: Facet<unknown>) => ({
|
|
|
31
32
|
none: NodeType.define({ id: 19, name: 'none' }),
|
|
32
33
|
separator: NodeType.define({ id: 20, name: 'separator' }),
|
|
33
34
|
punctuation: NodeType.define({ id: 21, name: 'punctuation' }),
|
|
35
|
+
consoleCommand: NodeType.define({ id: 22, name: 'consoleCommand' }),
|
|
36
|
+
// also include prism token types
|
|
37
|
+
'class-name': NodeType.define({ id: 23, name: 'label' }),
|
|
38
|
+
// this is escaped variables
|
|
39
|
+
identifier: NodeType.define({ id: 24, name: 'variable' }),
|
|
40
|
+
string: NodeType.define({ id: 25, name: 'stringLiteral' }),
|
|
41
|
+
relationship: NodeType.define({ id: 26, name: 'label' }),
|
|
42
|
+
boolean: NodeType.define({ id: 27, name: 'booleanLiteral' }),
|
|
43
|
+
number: NodeType.define({ id: 28, name: 'numberLiteral' }),
|
|
44
|
+
setting: NodeType.define({ id: 29, name: 'setting' }),
|
|
45
|
+
settingValue: NodeType.define({ id: 30, name: 'settingValue' }),
|
|
46
|
+
interpolationDelimiter: NodeType.define({
|
|
47
|
+
id: 31,
|
|
48
|
+
name: 'interpolationDelimiter',
|
|
49
|
+
}),
|
|
34
50
|
});
|
|
35
51
|
|
|
52
|
+
type PrismSpecificTokenType =
|
|
53
|
+
| 'class-name'
|
|
54
|
+
| 'identifier'
|
|
55
|
+
| 'string'
|
|
56
|
+
| 'relationship'
|
|
57
|
+
| 'boolean'
|
|
58
|
+
| 'number';
|
|
59
|
+
|
|
60
|
+
export type CodemirrorParseTokenType =
|
|
61
|
+
| CypherTokenType
|
|
62
|
+
| PrismSpecificTokenType
|
|
63
|
+
| 'topNode';
|
|
64
|
+
|
|
36
65
|
export type HighlightedCypherTokenTypes = Exclude<CypherTokenType, 'none'>;
|
|
37
66
|
export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
38
67
|
comment: tags.comment,
|
|
@@ -44,6 +73,7 @@ export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
|
44
73
|
variable: tags.variableName,
|
|
45
74
|
paramDollar: tags.atom,
|
|
46
75
|
paramValue: tags.atom,
|
|
76
|
+
interpolationDelimiter: tags.atom,
|
|
47
77
|
symbolicName: tags.variableName,
|
|
48
78
|
operator: tags.operator,
|
|
49
79
|
stringLiteral: tags.string,
|
|
@@ -55,6 +85,9 @@ export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
|
55
85
|
bracket: tags.bracket,
|
|
56
86
|
punctuation: tags.punctuation,
|
|
57
87
|
separator: tags.separator,
|
|
88
|
+
consoleCommand: tags.macroName,
|
|
89
|
+
setting: tags.attributeName,
|
|
90
|
+
settingValue: tags.attributeValue,
|
|
58
91
|
};
|
|
59
92
|
|
|
60
93
|
export const parserAdapterNodeSet = (nodes: Record<string, NodeType>) =>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { tags } from '@lezer/highlight';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
highlightSyntax,
|
|
4
|
+
CypherTokenType,
|
|
5
|
+
} from '@neo4j-cypher/language-support';
|
|
6
|
+
import { expect, test } from 'vitest';
|
|
3
7
|
import { tokenTypeToStyleTag } from './constants';
|
|
4
8
|
|
|
5
9
|
const cypherQueryWithAllTokenTypes = `MATCH (variable :Label)-[:REL_TYPE]->()
|
|
@@ -10,7 +14,7 @@ WHERE variable.property = "String"
|
|
|
10
14
|
RETURN variable;`;
|
|
11
15
|
|
|
12
16
|
test('correctly parses all cypher token types to style tags', () => {
|
|
13
|
-
const tokens =
|
|
17
|
+
const tokens = highlightSyntax(cypherQueryWithAllTokenTypes);
|
|
14
18
|
const tokenTypes = tokens.map((token) => token.tokenType);
|
|
15
19
|
expect(tokenTypes).toEqual([
|
|
16
20
|
'keyword',
|
|
@@ -54,7 +58,7 @@ test('correctly parses all cypher token types to style tags', () => {
|
|
|
54
58
|
]);
|
|
55
59
|
|
|
56
60
|
const styleTags = tokenTypes.map((tokenType) => {
|
|
57
|
-
if (tokenType ===
|
|
61
|
+
if (tokenType === CypherTokenType.none) return undefined;
|
|
58
62
|
return tokenTypeToStyleTag[tokenType];
|
|
59
63
|
});
|
|
60
64
|
const correctTags = [
|
|
@@ -1,12 +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
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { CypherTokenType } from '@neo4j-cypher/language-support';
|
|
6
|
+
import type { StyleSpec } from 'style-mod';
|
|
7
|
+
import type { HighlightedCypherTokenTypes } from './constants';
|
|
8
|
+
import { tokenTypeToStyleTag } from './constants';
|
|
10
9
|
import {
|
|
11
10
|
byWordSvg,
|
|
12
11
|
caseSensitiveSvg,
|
|
@@ -15,7 +14,18 @@ import {
|
|
|
15
14
|
replaceAllSvg,
|
|
16
15
|
replaceSvg,
|
|
17
16
|
upArrowSvg,
|
|
18
|
-
} from './
|
|
17
|
+
} from './themeIcons';
|
|
18
|
+
|
|
19
|
+
export interface DiffColors {
|
|
20
|
+
/** Background fill for the whole inserted/changed line. */
|
|
21
|
+
insertedLine: string;
|
|
22
|
+
/** Highlight for the exact inserted text within a changed line. */
|
|
23
|
+
insertedText: string;
|
|
24
|
+
/** Background fill for the whole deleted line widget. */
|
|
25
|
+
deletedLine: string;
|
|
26
|
+
/** Highlight for the exact deleted text within a deleted chunk. */
|
|
27
|
+
deletedText: string;
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
export interface ThemeOptions {
|
|
21
31
|
dark: boolean;
|
|
@@ -39,6 +49,7 @@ export interface ThemeOptions {
|
|
|
39
49
|
};
|
|
40
50
|
highlightStyles: Partial<Record<HighlightedCypherTokenTypes, string>>;
|
|
41
51
|
inheritBgColor?: boolean;
|
|
52
|
+
diffColors?: DiffColors;
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
export const createCypherTheme = ({
|
|
@@ -46,6 +57,7 @@ export const createCypherTheme = ({
|
|
|
46
57
|
editorSettings: settings,
|
|
47
58
|
highlightStyles,
|
|
48
59
|
inheritBgColor,
|
|
60
|
+
diffColors,
|
|
49
61
|
}: ThemeOptions): Extension => {
|
|
50
62
|
const themeOptions: Record<string, StyleSpec> = {
|
|
51
63
|
'&': {
|
|
@@ -53,6 +65,9 @@ export const createCypherTheme = ({
|
|
|
53
65
|
color: settings.foreground,
|
|
54
66
|
fontVariantLigatures: 'none',
|
|
55
67
|
},
|
|
68
|
+
'& .cm-snippetField': {
|
|
69
|
+
backgroundColor: settings.autoCompletionPanel.selectedColor,
|
|
70
|
+
},
|
|
56
71
|
'&.cm-focused': {
|
|
57
72
|
outline: 'none',
|
|
58
73
|
},
|
|
@@ -61,8 +76,9 @@ export const createCypherTheme = ({
|
|
|
61
76
|
color: settings.gutterForeground,
|
|
62
77
|
border: 'none',
|
|
63
78
|
},
|
|
64
|
-
'&.cm-editor
|
|
79
|
+
'&.cm-editor': {
|
|
65
80
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
81
|
+
height: '100%',
|
|
66
82
|
},
|
|
67
83
|
'.cm-content': {
|
|
68
84
|
caretColor: settings.cursor,
|
|
@@ -80,9 +96,13 @@ export const createCypherTheme = ({
|
|
|
80
96
|
'& .cm-selectionMatch': {
|
|
81
97
|
backgroundColor: settings.textMatchingSelection,
|
|
82
98
|
},
|
|
99
|
+
'& .cm-bold': {
|
|
100
|
+
fontWeight: 'bold',
|
|
101
|
+
},
|
|
83
102
|
'& .cm-panels': {
|
|
84
103
|
backgroundColor: settings.searchPanel.background,
|
|
85
104
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
105
|
+
zIndex: 0,
|
|
86
106
|
},
|
|
87
107
|
'& .cm-completionLabel': {
|
|
88
108
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
@@ -93,7 +113,40 @@ export const createCypherTheme = ({
|
|
|
93
113
|
color: settings.autoCompletionPanel.matchingTextColor,
|
|
94
114
|
textDecoration: 'none',
|
|
95
115
|
},
|
|
116
|
+
'& .cm-signature-help-panel': {
|
|
117
|
+
backgroundColor: settings.autoCompletionPanel.backgroundColor,
|
|
118
|
+
maxWidth: '700px',
|
|
119
|
+
maxHeight: '250px',
|
|
120
|
+
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
121
|
+
},
|
|
122
|
+
'& .cm-signature-help-panel-contents': {
|
|
123
|
+
overflow: 'auto',
|
|
124
|
+
maxHeight: '250px',
|
|
125
|
+
},
|
|
126
|
+
'& .cm-signature-help-panel-current-argument': {
|
|
127
|
+
color: settings.autoCompletionPanel.matchingTextColor,
|
|
128
|
+
fontWeight: 'bold',
|
|
129
|
+
},
|
|
130
|
+
'& .cm-signature-help-panel-separator': {
|
|
131
|
+
borderBottom: '1px solid #ccc',
|
|
132
|
+
},
|
|
133
|
+
'& .cm-signature-help-panel-name': {
|
|
134
|
+
padding: '5px',
|
|
135
|
+
},
|
|
136
|
+
'& .cm-signature-help-panel-arg-description': {
|
|
137
|
+
padding: '5px',
|
|
138
|
+
},
|
|
139
|
+
'& .cm-signature-help-panel-description': {
|
|
140
|
+
padding: '5px',
|
|
141
|
+
},
|
|
142
|
+
'.cm-completionInfo-signature': {
|
|
143
|
+
color: 'darkgrey',
|
|
144
|
+
},
|
|
145
|
+
'.cm-deprecated-element': {
|
|
146
|
+
'text-decoration': 'line-through',
|
|
147
|
+
},
|
|
96
148
|
'.cm-tooltip-autocomplete': {
|
|
149
|
+
maxWidth: '430px',
|
|
97
150
|
'& > ul > li[aria-selected]': {
|
|
98
151
|
backgroundColor: settings.autoCompletionPanel.selectedColor,
|
|
99
152
|
color: settings.foreground,
|
|
@@ -118,24 +171,16 @@ export const createCypherTheme = ({
|
|
|
118
171
|
border: 'none',
|
|
119
172
|
verticalAlign: 'middle',
|
|
120
173
|
'&[name=next]::before': {
|
|
121
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
122
|
-
downArrowSvg,
|
|
123
|
-
)}")`,
|
|
174
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(downArrowSvg)}")`,
|
|
124
175
|
},
|
|
125
176
|
'&[name=prev]::before': {
|
|
126
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
127
|
-
upArrowSvg,
|
|
128
|
-
)}")`,
|
|
177
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(upArrowSvg)}")`,
|
|
129
178
|
},
|
|
130
179
|
'&[name=replace]::before': {
|
|
131
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
132
|
-
replaceSvg,
|
|
133
|
-
)}")`,
|
|
180
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(replaceSvg)}")`,
|
|
134
181
|
},
|
|
135
182
|
'&[name=replaceAll]::before': {
|
|
136
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
137
|
-
replaceAllSvg,
|
|
138
|
-
)}")`,
|
|
183
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(replaceAllSvg)}")`,
|
|
139
184
|
},
|
|
140
185
|
width: '20px',
|
|
141
186
|
height: '20px',
|
|
@@ -167,19 +212,13 @@ export const createCypherTheme = ({
|
|
|
167
212
|
borderRadius: '4px',
|
|
168
213
|
|
|
169
214
|
'&[name=case]::before': {
|
|
170
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
171
|
-
caseSensitiveSvg,
|
|
172
|
-
)}")`,
|
|
215
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(caseSensitiveSvg)}")`,
|
|
173
216
|
},
|
|
174
217
|
'&[name=re]::before': {
|
|
175
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
176
|
-
regexSvg,
|
|
177
|
-
)}")`,
|
|
218
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(regexSvg)}")`,
|
|
178
219
|
},
|
|
179
220
|
'&[name=word]::before': {
|
|
180
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
181
|
-
byWordSvg,
|
|
182
|
-
)}")`,
|
|
221
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(byWordSvg)}")`,
|
|
183
222
|
},
|
|
184
223
|
'&:hover': {
|
|
185
224
|
backgroundColor: settings.searchPanel.buttonHoverBackground,
|
|
@@ -190,14 +229,39 @@ export const createCypherTheme = ({
|
|
|
190
229
|
},
|
|
191
230
|
},
|
|
192
231
|
},
|
|
232
|
+
...(diffColors && {
|
|
233
|
+
'&.cm-merge-b .cm-changedLine': {
|
|
234
|
+
backgroundColor: diffColors.insertedLine,
|
|
235
|
+
},
|
|
236
|
+
'&.cm-merge-b .cm-changedText': {
|
|
237
|
+
background: diffColors.insertedText,
|
|
238
|
+
},
|
|
239
|
+
'&.cm-merge-b .cm-deletedChunk': {
|
|
240
|
+
backgroundColor: diffColors.deletedLine,
|
|
241
|
+
},
|
|
242
|
+
'&.cm-merge-b .cm-deletedChunk .cm-deletedText': {
|
|
243
|
+
background: diffColors.deletedText,
|
|
244
|
+
},
|
|
245
|
+
// Hide the empty deletion widget that unifiedMergeView renders
|
|
246
|
+
// when the original document is empty (its only line is an empty <del>).
|
|
247
|
+
'&.cm-merge-b .cm-deletedChunk:has(> .cm-deletedLine:only-child > del > br:only-child)':
|
|
248
|
+
{
|
|
249
|
+
display: 'none',
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
193
252
|
};
|
|
194
253
|
|
|
195
254
|
const themeExtension = EditorView.theme(themeOptions, { dark });
|
|
196
255
|
|
|
197
|
-
const styles =
|
|
198
|
-
(
|
|
256
|
+
const styles = (
|
|
257
|
+
Object.entries(highlightStyles) as Array<
|
|
258
|
+
[HighlightedCypherTokenTypes, string]
|
|
259
|
+
>
|
|
260
|
+
).map(
|
|
261
|
+
([token, color]): TagStyle => ({
|
|
199
262
|
tag: tokenTypeToStyleTag[token],
|
|
200
263
|
color,
|
|
264
|
+
class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined,
|
|
201
265
|
}),
|
|
202
266
|
);
|
|
203
267
|
const highlightStyle = HighlightStyle.define(styles);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { autocompletion } from '@codemirror/autocomplete';
|
|
2
|
+
import {
|
|
3
|
+
defineLanguageFacet,
|
|
4
|
+
Language,
|
|
5
|
+
LanguageSupport,
|
|
6
|
+
} from '@codemirror/language';
|
|
7
|
+
import type { CypherLanguageService } from '@neo4j-cypher/language-support';
|
|
8
|
+
import { type DbSchema } from '@neo4j-cypher/language-support';
|
|
9
|
+
import { completionStyles, cypherAutocomplete } from './autocomplete';
|
|
10
|
+
import { ParserAdapter } from './parser-adapter';
|
|
11
|
+
import { signatureHelpTooltip } from './signatureHelp';
|
|
12
|
+
import { cypherLinter } from './syntaxValidation';
|
|
13
|
+
|
|
14
|
+
const facet = defineLanguageFacet({
|
|
15
|
+
commentTokens: { block: { open: '/*', close: '*/' }, line: '//' },
|
|
16
|
+
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '`'] },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type CypherConfig = {
|
|
20
|
+
languageService: CypherLanguageService;
|
|
21
|
+
lint?: boolean;
|
|
22
|
+
showSignatureTooltipBelow?: boolean;
|
|
23
|
+
featureFlags?: {
|
|
24
|
+
consoleCommands?: boolean;
|
|
25
|
+
};
|
|
26
|
+
schema?: DbSchema;
|
|
27
|
+
useLightVersion: boolean;
|
|
28
|
+
setUseLightVersion?: (useLightVersion: boolean) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function cypher(config: CypherConfig) {
|
|
32
|
+
const parserAdapter = new ParserAdapter(facet, config);
|
|
33
|
+
|
|
34
|
+
const cypherLanguage = new Language(facet, parserAdapter, [], 'cypher');
|
|
35
|
+
|
|
36
|
+
return new LanguageSupport(cypherLanguage, [
|
|
37
|
+
autocompletion({
|
|
38
|
+
override: [cypherAutocomplete(config)],
|
|
39
|
+
optionClass: completionStyles,
|
|
40
|
+
}),
|
|
41
|
+
cypherLinter(config),
|
|
42
|
+
signatureHelpTooltip(config),
|
|
43
|
+
]);
|
|
44
|
+
}
|