@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
|
@@ -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,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
Completion,
|
|
3
|
+
CompletionSource,
|
|
4
|
+
snippet,
|
|
5
|
+
} from '@codemirror/autocomplete';
|
|
6
|
+
import { shouldAutoCompleteYield } from '@neo4j-cypher/language-support';
|
|
7
|
+
import {
|
|
8
|
+
CompletionItemKind,
|
|
9
|
+
CompletionItemTag,
|
|
10
|
+
} from 'vscode-languageserver-types';
|
|
4
11
|
import { CompletionItemIcons } from '../icons';
|
|
5
|
-
import type { CypherConfig } from './
|
|
12
|
+
import type { CypherConfig } from './langCypher';
|
|
13
|
+
import { getDocString } from './utils';
|
|
6
14
|
|
|
7
15
|
const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
8
16
|
const map: Record<CompletionItemKind, CompletionItemIcons> = {
|
|
@@ -28,7 +36,8 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
|
28
36
|
[CompletionItemKind.EnumMember]: 'EnumMember',
|
|
29
37
|
[CompletionItemKind.Constant]: 'Constant',
|
|
30
38
|
[CompletionItemKind.Struct]: 'Struct',
|
|
31
|
-
|
|
39
|
+
// we're miss-using the enum here as there is no `Console` kind in the predefined list
|
|
40
|
+
[CompletionItemKind.Event]: 'Console',
|
|
32
41
|
[CompletionItemKind.Operator]: 'Operator',
|
|
33
42
|
[CompletionItemKind.TypeParameter]: 'TypeParameter',
|
|
34
43
|
};
|
|
@@ -36,30 +45,101 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => {
|
|
|
36
45
|
return map[c];
|
|
37
46
|
};
|
|
38
47
|
|
|
48
|
+
export const completionStyles: (
|
|
49
|
+
completion: Completion & { deprecated?: boolean },
|
|
50
|
+
) => string = (completion) => {
|
|
51
|
+
if (completion.deprecated) {
|
|
52
|
+
return 'cm-deprecated-element';
|
|
53
|
+
} else {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
39
58
|
export const cypherAutocomplete: (config: CypherConfig) => CompletionSource =
|
|
40
59
|
(config) => (context) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const triggerCharacters = ['.', ':', '{', '$'];
|
|
44
|
-
const lastCharacter =
|
|
45
|
-
|
|
60
|
+
const documentText = context.state.doc.toString();
|
|
61
|
+
const offset = context.pos;
|
|
62
|
+
const triggerCharacters = ['.', ':', '{', '$', ')', ']', '-', '<'];
|
|
63
|
+
const lastCharacter = documentText.at(offset - 1);
|
|
64
|
+
const yieldTriggered = shouldAutoCompleteYield(documentText, offset);
|
|
46
65
|
const lastWord = context.matchBefore(/\w*/);
|
|
47
66
|
const inWord = lastWord.from !== lastWord.to;
|
|
48
|
-
|
|
49
67
|
const shouldTriggerCompletion =
|
|
50
|
-
inWord ||
|
|
68
|
+
inWord ||
|
|
69
|
+
context.explicit ||
|
|
70
|
+
triggerCharacters.includes(lastCharacter) ||
|
|
71
|
+
yieldTriggered;
|
|
72
|
+
|
|
73
|
+
if (config.useLightVersion && !context.explicit) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
51
76
|
|
|
52
77
|
if (!shouldTriggerCompletion) {
|
|
53
78
|
return null;
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
const options = autocomplete(
|
|
81
|
+
const options = config.languageService.autocomplete(
|
|
82
|
+
documentText,
|
|
83
|
+
config.schema ?? {},
|
|
84
|
+
{
|
|
85
|
+
caretPosition: offset,
|
|
86
|
+
manual: context.explicit,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
57
89
|
|
|
58
90
|
return {
|
|
59
|
-
from: context.matchBefore(/(\w
|
|
60
|
-
options: options.map((o) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
from: context.matchBefore(/(\w)*$/).from,
|
|
92
|
+
options: options.map((o) => {
|
|
93
|
+
let maybeInfo = {};
|
|
94
|
+
let emptyInfo = true;
|
|
95
|
+
const newDiv = document.createElement('div');
|
|
96
|
+
|
|
97
|
+
if (o.signature) {
|
|
98
|
+
const header = document.createElement('p');
|
|
99
|
+
header.setAttribute('class', 'cm-completionInfo-signature');
|
|
100
|
+
header.textContent = o.signature;
|
|
101
|
+
if (header.textContent.length > 0) {
|
|
102
|
+
emptyInfo = false;
|
|
103
|
+
newDiv.appendChild(header);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (o.documentation) {
|
|
108
|
+
const paragraph = document.createElement('p');
|
|
109
|
+
paragraph.textContent = getDocString(o.documentation);
|
|
110
|
+
if (paragraph.textContent.length > 0) {
|
|
111
|
+
emptyInfo = false;
|
|
112
|
+
newDiv.appendChild(paragraph);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!emptyInfo) {
|
|
117
|
+
maybeInfo = {
|
|
118
|
+
info: () => Promise.resolve(newDiv),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const deprecated =
|
|
122
|
+
o.tags?.find((tag) => tag === CompletionItemTag.Deprecated) ?? false;
|
|
123
|
+
// The negative boost moves the deprecation down the list
|
|
124
|
+
// so we offer the user the completions that are
|
|
125
|
+
// deprecated the last
|
|
126
|
+
const maybeDeprecated = deprecated
|
|
127
|
+
? { boost: -99, deprecated: true }
|
|
128
|
+
: {};
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
label: o.insertText ? o.insertText : o.label,
|
|
132
|
+
displayLabel: o.label,
|
|
133
|
+
type: completionKindToCodemirrorIcon(o.kind),
|
|
134
|
+
apply:
|
|
135
|
+
o.kind === CompletionItemKind.Snippet
|
|
136
|
+
? // codemirror requires an empty snippet space to be able to tab out of the completion
|
|
137
|
+
snippet((o.insertText ?? o.label) + '${}')
|
|
138
|
+
: undefined,
|
|
139
|
+
detail: o.detail,
|
|
140
|
+
...maybeDeprecated,
|
|
141
|
+
...maybeInfo,
|
|
142
|
+
};
|
|
143
|
+
}),
|
|
64
144
|
};
|
|
65
145
|
};
|
|
@@ -31,8 +31,36 @@ export const cypherTokenTypeToNode = (facet: Facet<unknown>) => ({
|
|
|
31
31
|
none: NodeType.define({ id: 19, name: 'none' }),
|
|
32
32
|
separator: NodeType.define({ id: 20, name: 'separator' }),
|
|
33
33
|
punctuation: NodeType.define({ id: 21, name: 'punctuation' }),
|
|
34
|
+
consoleCommand: NodeType.define({ id: 22, name: 'consoleCommand' }),
|
|
35
|
+
// also include prism token types
|
|
36
|
+
'class-name': NodeType.define({ id: 23, name: 'label' }),
|
|
37
|
+
// this is escaped variables
|
|
38
|
+
identifier: NodeType.define({ id: 24, name: 'variable' }),
|
|
39
|
+
string: NodeType.define({ id: 25, name: 'stringLiteral' }),
|
|
40
|
+
relationship: NodeType.define({ id: 26, name: 'label' }),
|
|
41
|
+
boolean: NodeType.define({ id: 27, name: 'booleanLiteral' }),
|
|
42
|
+
number: NodeType.define({ id: 28, name: 'numberLiteral' }),
|
|
43
|
+
setting: NodeType.define({ id: 29, name: 'setting' }),
|
|
44
|
+
settingValue: NodeType.define({ id: 30, name: 'settingValue' }),
|
|
45
|
+
interpolationDelimiter: NodeType.define({
|
|
46
|
+
id: 31,
|
|
47
|
+
name: 'interpolationDelimiter',
|
|
48
|
+
}),
|
|
34
49
|
});
|
|
35
50
|
|
|
51
|
+
type PrismSpecificTokenType =
|
|
52
|
+
| 'class-name'
|
|
53
|
+
| 'identifier'
|
|
54
|
+
| 'string'
|
|
55
|
+
| 'relationship'
|
|
56
|
+
| 'boolean'
|
|
57
|
+
| 'number';
|
|
58
|
+
|
|
59
|
+
export type CodemirrorParseTokenType =
|
|
60
|
+
| CypherTokenType
|
|
61
|
+
| PrismSpecificTokenType
|
|
62
|
+
| 'topNode';
|
|
63
|
+
|
|
36
64
|
export type HighlightedCypherTokenTypes = Exclude<CypherTokenType, 'none'>;
|
|
37
65
|
export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
38
66
|
comment: tags.comment,
|
|
@@ -44,6 +72,7 @@ export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
|
44
72
|
variable: tags.variableName,
|
|
45
73
|
paramDollar: tags.atom,
|
|
46
74
|
paramValue: tags.atom,
|
|
75
|
+
interpolationDelimiter: tags.atom,
|
|
47
76
|
symbolicName: tags.variableName,
|
|
48
77
|
operator: tags.operator,
|
|
49
78
|
stringLiteral: tags.string,
|
|
@@ -55,6 +84,9 @@ export const tokenTypeToStyleTag: Record<HighlightedCypherTokenTypes, Tag> = {
|
|
|
55
84
|
bracket: tags.bracket,
|
|
56
85
|
punctuation: tags.punctuation,
|
|
57
86
|
separator: tags.separator,
|
|
87
|
+
consoleCommand: tags.macroName,
|
|
88
|
+
setting: tags.attributeName,
|
|
89
|
+
settingValue: tags.attributeValue,
|
|
58
90
|
};
|
|
59
91
|
|
|
60
92
|
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 = [
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '@codemirror/language';
|
|
6
6
|
import { Extension } from '@codemirror/state';
|
|
7
7
|
import { EditorView } from '@codemirror/view';
|
|
8
|
+
import { CypherTokenType } from '@neo4j-cypher/language-support';
|
|
8
9
|
import { StyleSpec } from 'style-mod';
|
|
9
10
|
import { HighlightedCypherTokenTypes, tokenTypeToStyleTag } from './constants';
|
|
10
11
|
import {
|
|
@@ -15,7 +16,18 @@ import {
|
|
|
15
16
|
replaceAllSvg,
|
|
16
17
|
replaceSvg,
|
|
17
18
|
upArrowSvg,
|
|
18
|
-
} from './
|
|
19
|
+
} from './themeIcons';
|
|
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
|
+
}
|
|
19
31
|
|
|
20
32
|
export interface ThemeOptions {
|
|
21
33
|
dark: boolean;
|
|
@@ -39,6 +51,7 @@ export interface ThemeOptions {
|
|
|
39
51
|
};
|
|
40
52
|
highlightStyles: Partial<Record<HighlightedCypherTokenTypes, string>>;
|
|
41
53
|
inheritBgColor?: boolean;
|
|
54
|
+
diffColors?: DiffColors;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
export const createCypherTheme = ({
|
|
@@ -46,6 +59,7 @@ export const createCypherTheme = ({
|
|
|
46
59
|
editorSettings: settings,
|
|
47
60
|
highlightStyles,
|
|
48
61
|
inheritBgColor,
|
|
62
|
+
diffColors,
|
|
49
63
|
}: ThemeOptions): Extension => {
|
|
50
64
|
const themeOptions: Record<string, StyleSpec> = {
|
|
51
65
|
'&': {
|
|
@@ -53,6 +67,9 @@ export const createCypherTheme = ({
|
|
|
53
67
|
color: settings.foreground,
|
|
54
68
|
fontVariantLigatures: 'none',
|
|
55
69
|
},
|
|
70
|
+
'& .cm-snippetField': {
|
|
71
|
+
backgroundColor: settings.autoCompletionPanel.selectedColor,
|
|
72
|
+
},
|
|
56
73
|
'&.cm-focused': {
|
|
57
74
|
outline: 'none',
|
|
58
75
|
},
|
|
@@ -61,8 +78,9 @@ export const createCypherTheme = ({
|
|
|
61
78
|
color: settings.gutterForeground,
|
|
62
79
|
border: 'none',
|
|
63
80
|
},
|
|
64
|
-
'&.cm-editor
|
|
81
|
+
'&.cm-editor': {
|
|
65
82
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
83
|
+
height: '100%',
|
|
66
84
|
},
|
|
67
85
|
'.cm-content': {
|
|
68
86
|
caretColor: settings.cursor,
|
|
@@ -80,9 +98,13 @@ export const createCypherTheme = ({
|
|
|
80
98
|
'& .cm-selectionMatch': {
|
|
81
99
|
backgroundColor: settings.textMatchingSelection,
|
|
82
100
|
},
|
|
101
|
+
'& .cm-bold': {
|
|
102
|
+
fontWeight: 'bold',
|
|
103
|
+
},
|
|
83
104
|
'& .cm-panels': {
|
|
84
105
|
backgroundColor: settings.searchPanel.background,
|
|
85
106
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
107
|
+
zIndex: 0,
|
|
86
108
|
},
|
|
87
109
|
'& .cm-completionLabel': {
|
|
88
110
|
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
@@ -93,7 +115,40 @@ export const createCypherTheme = ({
|
|
|
93
115
|
color: settings.autoCompletionPanel.matchingTextColor,
|
|
94
116
|
textDecoration: 'none',
|
|
95
117
|
},
|
|
118
|
+
'& .cm-signature-help-panel': {
|
|
119
|
+
backgroundColor: settings.autoCompletionPanel.backgroundColor,
|
|
120
|
+
maxWidth: '700px',
|
|
121
|
+
maxHeight: '250px',
|
|
122
|
+
fontFamily: 'Fira Code, Menlo, Monaco, Lucida Console, monospace',
|
|
123
|
+
},
|
|
124
|
+
'& .cm-signature-help-panel-contents': {
|
|
125
|
+
overflow: 'auto',
|
|
126
|
+
maxHeight: '250px',
|
|
127
|
+
},
|
|
128
|
+
'& .cm-signature-help-panel-current-argument': {
|
|
129
|
+
color: settings.autoCompletionPanel.matchingTextColor,
|
|
130
|
+
fontWeight: 'bold',
|
|
131
|
+
},
|
|
132
|
+
'& .cm-signature-help-panel-separator': {
|
|
133
|
+
borderBottom: '1px solid #ccc',
|
|
134
|
+
},
|
|
135
|
+
'& .cm-signature-help-panel-name': {
|
|
136
|
+
padding: '5px',
|
|
137
|
+
},
|
|
138
|
+
'& .cm-signature-help-panel-arg-description': {
|
|
139
|
+
padding: '5px',
|
|
140
|
+
},
|
|
141
|
+
'& .cm-signature-help-panel-description': {
|
|
142
|
+
padding: '5px',
|
|
143
|
+
},
|
|
144
|
+
'.cm-completionInfo-signature': {
|
|
145
|
+
color: 'darkgrey',
|
|
146
|
+
},
|
|
147
|
+
'.cm-deprecated-element': {
|
|
148
|
+
'text-decoration': 'line-through',
|
|
149
|
+
},
|
|
96
150
|
'.cm-tooltip-autocomplete': {
|
|
151
|
+
maxWidth: '430px',
|
|
97
152
|
'& > ul > li[aria-selected]': {
|
|
98
153
|
backgroundColor: settings.autoCompletionPanel.selectedColor,
|
|
99
154
|
color: settings.foreground,
|
|
@@ -118,24 +173,16 @@ export const createCypherTheme = ({
|
|
|
118
173
|
border: 'none',
|
|
119
174
|
verticalAlign: 'middle',
|
|
120
175
|
'&[name=next]::before': {
|
|
121
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
122
|
-
downArrowSvg,
|
|
123
|
-
)}")`,
|
|
176
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(downArrowSvg)}")`,
|
|
124
177
|
},
|
|
125
178
|
'&[name=prev]::before': {
|
|
126
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
127
|
-
upArrowSvg,
|
|
128
|
-
)}")`,
|
|
179
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(upArrowSvg)}")`,
|
|
129
180
|
},
|
|
130
181
|
'&[name=replace]::before': {
|
|
131
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
132
|
-
replaceSvg,
|
|
133
|
-
)}")`,
|
|
182
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(replaceSvg)}")`,
|
|
134
183
|
},
|
|
135
184
|
'&[name=replaceAll]::before': {
|
|
136
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
137
|
-
replaceAllSvg,
|
|
138
|
-
)}")`,
|
|
185
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(replaceAllSvg)}")`,
|
|
139
186
|
},
|
|
140
187
|
width: '20px',
|
|
141
188
|
height: '20px',
|
|
@@ -167,19 +214,13 @@ export const createCypherTheme = ({
|
|
|
167
214
|
borderRadius: '4px',
|
|
168
215
|
|
|
169
216
|
'&[name=case]::before': {
|
|
170
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
171
|
-
caseSensitiveSvg,
|
|
172
|
-
)}")`,
|
|
217
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(caseSensitiveSvg)}")`,
|
|
173
218
|
},
|
|
174
219
|
'&[name=re]::before': {
|
|
175
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
176
|
-
regexSvg,
|
|
177
|
-
)}")`,
|
|
220
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(regexSvg)}")`,
|
|
178
221
|
},
|
|
179
222
|
'&[name=word]::before': {
|
|
180
|
-
content: `url("data:image/svg+xml;base64,${window.btoa(
|
|
181
|
-
byWordSvg,
|
|
182
|
-
)}")`,
|
|
223
|
+
content: `url("data:image/svg+xml;base64,${window.btoa(byWordSvg)}")`,
|
|
183
224
|
},
|
|
184
225
|
'&:hover': {
|
|
185
226
|
backgroundColor: settings.searchPanel.buttonHoverBackground,
|
|
@@ -190,6 +231,26 @@ export const createCypherTheme = ({
|
|
|
190
231
|
},
|
|
191
232
|
},
|
|
192
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
|
+
}),
|
|
193
254
|
};
|
|
194
255
|
|
|
195
256
|
const themeExtension = EditorView.theme(themeOptions, { dark });
|
|
@@ -198,6 +259,7 @@ export const createCypherTheme = ({
|
|
|
198
259
|
([token, color]: [HighlightedCypherTokenTypes, string]): TagStyle => ({
|
|
199
260
|
tag: tokenTypeToStyleTag[token],
|
|
200
261
|
color,
|
|
262
|
+
class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined,
|
|
201
263
|
}),
|
|
202
264
|
);
|
|
203
265
|
const highlightStyle = HighlightStyle.define(styles);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { autocompletion } from '@codemirror/autocomplete';
|
|
2
|
+
import {
|
|
3
|
+
defineLanguageFacet,
|
|
4
|
+
Language,
|
|
5
|
+
LanguageSupport,
|
|
6
|
+
} from '@codemirror/language';
|
|
7
|
+
import {
|
|
8
|
+
CypherLanguageService,
|
|
9
|
+
type DbSchema,
|
|
10
|
+
} from '@neo4j-cypher/language-support';
|
|
11
|
+
import { completionStyles, cypherAutocomplete } from './autocomplete';
|
|
12
|
+
import { ParserAdapter } from './parser-adapter';
|
|
13
|
+
import { signatureHelpTooltip } from './signatureHelp';
|
|
14
|
+
import { cypherLinter } from './syntaxValidation';
|
|
15
|
+
|
|
16
|
+
const facet = defineLanguageFacet({
|
|
17
|
+
commentTokens: { block: { open: '/*', close: '*/' }, line: '//' },
|
|
18
|
+
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '`'] },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type CypherConfig = {
|
|
22
|
+
languageService: CypherLanguageService;
|
|
23
|
+
lint?: boolean;
|
|
24
|
+
showSignatureTooltipBelow?: boolean;
|
|
25
|
+
featureFlags?: {
|
|
26
|
+
consoleCommands?: boolean;
|
|
27
|
+
};
|
|
28
|
+
schema?: DbSchema;
|
|
29
|
+
useLightVersion: boolean;
|
|
30
|
+
setUseLightVersion?: (useLightVersion: boolean) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function cypher(config: CypherConfig) {
|
|
34
|
+
const parserAdapter = new ParserAdapter(facet, config);
|
|
35
|
+
|
|
36
|
+
const cypherLanguage = new Language(facet, parserAdapter, [], 'cypher');
|
|
37
|
+
|
|
38
|
+
return new LanguageSupport(cypherLanguage, [
|
|
39
|
+
autocompletion({
|
|
40
|
+
override: [cypherAutocomplete(config)],
|
|
41
|
+
optionClass: completionStyles,
|
|
42
|
+
}),
|
|
43
|
+
cypherLinter(config),
|
|
44
|
+
signatureHelpTooltip(config),
|
|
45
|
+
]);
|
|
46
|
+
}
|