@neo4j-cypher/react-codemirror 1.0.3 → 2.0.0-next.0

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +26 -27
  4. package/dist/cjs/index.cjs +1455 -0
  5. package/dist/cjs/index.cjs.map +7 -0
  6. package/dist/esm/index.mjs +1468 -0
  7. package/dist/esm/index.mjs.map +7 -0
  8. package/dist/types/CypherEditor.d.ts +118 -0
  9. package/dist/types/e2e_tests/auto-completion.spec.d.ts +1 -0
  10. package/dist/types/e2e_tests/e2e-utils.d.ts +12 -0
  11. package/dist/types/e2e_tests/extra-keybindings.spec.d.ts +1 -0
  12. package/dist/types/e2e_tests/history-navigation.spec.d.ts +1 -0
  13. package/dist/types/e2e_tests/mock-data.d.ts +3779 -0
  14. package/dist/types/e2e_tests/performance-test.spec.d.ts +1 -0
  15. package/dist/types/e2e_tests/sanity-checks.spec.d.ts +1 -0
  16. package/dist/types/e2e_tests/syntax-highlighting.spec.d.ts +1 -0
  17. package/dist/types/e2e_tests/syntax-validation.spec.d.ts +1 -0
  18. package/dist/types/icons.d.ts +2 -0
  19. package/dist/types/index.d.ts +5 -0
  20. package/dist/types/lang-cypher/ParserAdapter.d.ts +14 -0
  21. package/dist/types/lang-cypher/autocomplete.d.ts +3 -0
  22. package/dist/types/lang-cypher/constants.d.ts +31 -0
  23. package/dist/types/lang-cypher/contants.test.d.ts +1 -0
  24. package/dist/types/lang-cypher/create-cypher-theme.d.ts +26 -0
  25. package/dist/types/lang-cypher/lang-cypher.d.ts +7 -0
  26. package/dist/types/lang-cypher/syntax-validation.d.ts +3 -0
  27. package/dist/types/lang-cypher/theme-icons.d.ts +7 -0
  28. package/dist/types/ndl-tokens-copy.d.ts +379 -0
  29. package/dist/types/ndl-tokens-copy.test.d.ts +1 -0
  30. package/dist/types/neo4j-setup.d.ts +2 -0
  31. package/dist/types/repl-mode.d.ts +8 -0
  32. package/dist/types/themes.d.ts +11 -0
  33. package/dist/types/tsconfig.tsbuildinfo +1 -0
  34. package/package.json +60 -34
  35. package/src/CypherEditor.tsx +316 -0
  36. package/src/e2e_tests/auto-completion.spec.tsx +232 -0
  37. package/src/e2e_tests/e2e-utils.ts +75 -0
  38. package/src/e2e_tests/extra-keybindings.spec.tsx +57 -0
  39. package/src/e2e_tests/history-navigation.spec.tsx +144 -0
  40. package/src/e2e_tests/mock-data.ts +4310 -0
  41. package/src/e2e_tests/performance-test.spec.tsx +71 -0
  42. package/src/e2e_tests/sanity-checks.spec.tsx +87 -0
  43. package/src/e2e_tests/syntax-highlighting.spec.tsx +198 -0
  44. package/src/e2e_tests/syntax-validation.spec.tsx +157 -0
  45. package/src/icons.ts +87 -0
  46. package/src/index.ts +5 -0
  47. package/src/lang-cypher/ParserAdapter.ts +92 -0
  48. package/src/lang-cypher/autocomplete.ts +65 -0
  49. package/src/lang-cypher/constants.ts +61 -0
  50. package/src/lang-cypher/contants.test.ts +104 -0
  51. package/src/lang-cypher/create-cypher-theme.ts +207 -0
  52. package/src/lang-cypher/lang-cypher.ts +32 -0
  53. package/src/lang-cypher/syntax-validation.ts +24 -0
  54. package/src/lang-cypher/theme-icons.ts +27 -0
  55. package/src/ndl-tokens-copy.test.ts +11 -0
  56. package/src/ndl-tokens-copy.ts +379 -0
  57. package/src/neo4j-setup.tsx +129 -0
  58. package/src/repl-mode.ts +214 -0
  59. package/src/themes.ts +130 -0
  60. package/es/CypherEditor.js +0 -262
  61. package/es/react-codemirror.js +0 -1
  62. package/lib/CypherEditor.js +0 -272
  63. package/lib/react-codemirror.js +0 -13
  64. package/src/CypherEditor.d.ts +0 -310
  65. package/src/react-codemirror.d.ts +0 -18
@@ -0,0 +1,129 @@
1
+ import {
2
+ acceptCompletion,
3
+ autocompletion,
4
+ closeBrackets,
5
+ closeBracketsKeymap,
6
+ completionKeymap,
7
+ } from '@codemirror/autocomplete';
8
+ import {
9
+ defaultKeymap,
10
+ history,
11
+ historyKeymap,
12
+ indentLess,
13
+ indentMore,
14
+ } from '@codemirror/commands';
15
+ import {
16
+ bracketMatching,
17
+ defaultHighlightStyle,
18
+ foldKeymap,
19
+ indentOnInput,
20
+ syntaxHighlighting,
21
+ } from '@codemirror/language';
22
+ import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
23
+ import { EditorState, Extension, StateCommand } from '@codemirror/state';
24
+ import {
25
+ crosshairCursor,
26
+ drawSelection,
27
+ dropCursor,
28
+ EditorView,
29
+ highlightSpecialChars,
30
+ keymap,
31
+ rectangularSelection,
32
+ } from '@codemirror/view';
33
+
34
+ import { lintKeymap } from '@codemirror/lint';
35
+ import { getIconForType } from './icons';
36
+
37
+ const insertTab: StateCommand = (cmd) => {
38
+ // if there is a selection we should indent the selected text, but if not insert
39
+ // two spaces as per the cypher style guide
40
+ if (cmd.state.selection.main.from === cmd.state.selection.main.to) {
41
+ cmd.dispatch(
42
+ cmd.state.update({
43
+ changes: {
44
+ from: cmd.state.selection.main.to,
45
+ to: cmd.state.selection.main.to,
46
+ insert: ' ',
47
+ },
48
+ selection: { anchor: cmd.state.selection.main.to + 2 },
49
+ }),
50
+ );
51
+ } else {
52
+ indentMore(cmd);
53
+ }
54
+ return true;
55
+ };
56
+
57
+ export const basicNeo4jSetup = (): Extension[] => {
58
+ const keymaps = [
59
+ closeBracketsKeymap,
60
+ defaultKeymap,
61
+ searchKeymap,
62
+ historyKeymap,
63
+ foldKeymap,
64
+ completionKeymap,
65
+ lintKeymap,
66
+ {
67
+ key: 'Tab',
68
+ preventDefault: true,
69
+ run: acceptCompletion,
70
+ },
71
+ {
72
+ key: 'Tab',
73
+ preventDefault: true,
74
+ run: insertTab,
75
+ },
76
+ {
77
+ key: 'Shift-Tab',
78
+ preventDefault: true,
79
+ run: indentLess,
80
+ },
81
+ ].flat();
82
+
83
+ const extensions: Extension[] = [];
84
+
85
+ extensions.push(highlightSpecialChars());
86
+ extensions.push(history());
87
+
88
+ extensions.push(drawSelection());
89
+ extensions.push(dropCursor());
90
+ extensions.push(EditorState.allowMultipleSelections.of(true));
91
+ extensions.push(indentOnInput());
92
+ extensions.push(
93
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
94
+ );
95
+
96
+ extensions.push(bracketMatching());
97
+ extensions.push(closeBrackets());
98
+ extensions.push(
99
+ autocompletion({
100
+ icons: false,
101
+ interactionDelay: 5,
102
+ addToOptions: [
103
+ {
104
+ render(completion, state) {
105
+ const isDarkTheme = state.facet(EditorView.darkTheme);
106
+ const icon = document.createElement('span');
107
+
108
+ icon.innerHTML = getIconForType(completion.type, isDarkTheme);
109
+
110
+ const svgElement = icon.children[0] as SVGElement;
111
+
112
+ svgElement.style.display = 'inline';
113
+ svgElement.style.marginRight = '5px';
114
+ return icon;
115
+ },
116
+ position: 20,
117
+ },
118
+ ],
119
+ }),
120
+ );
121
+
122
+ extensions.push(rectangularSelection());
123
+ extensions.push(crosshairCursor());
124
+ extensions.push(highlightSelectionMatches());
125
+
126
+ extensions.push(keymap.of(keymaps));
127
+
128
+ return extensions;
129
+ };
@@ -0,0 +1,214 @@
1
+ import { Extension, StateEffect } from '@codemirror/state';
2
+ import { EditorView, keymap } from '@codemirror/view';
3
+
4
+ import { StateField } from '@codemirror/state';
5
+
6
+ type HistoryState = {
7
+ history: string[];
8
+ index: number;
9
+ draft: string;
10
+ documentUpdate: string | null;
11
+ };
12
+ const DRAFT_ENTRY_INDEX = -1;
13
+ const historyInitialState = {
14
+ history: [],
15
+ index: DRAFT_ENTRY_INDEX,
16
+ documentUpdate: null,
17
+ draft: '',
18
+ };
19
+
20
+ const historyState = StateField.define<HistoryState>({
21
+ create() {
22
+ return historyInitialState;
23
+ },
24
+ toJSON(value) {
25
+ return JSON.stringify(value);
26
+ },
27
+ update(value, transaction) {
28
+ for (const effect of transaction.effects) {
29
+ if (effect.is(moveInHistory)) {
30
+ if (value.history.length === 0) {
31
+ return {
32
+ ...value,
33
+ documentUpdate: null,
34
+ };
35
+ }
36
+
37
+ const currentHistoryIndex = value.index;
38
+ if (effect.value === 'BACK') {
39
+ const newHistoryIndex = currentHistoryIndex + 1;
40
+
41
+ if (newHistoryIndex === value.history.length) {
42
+ return {
43
+ ...value,
44
+ documentUpdate: null,
45
+ };
46
+ }
47
+
48
+ let draft = value.draft;
49
+ if (currentHistoryIndex === DRAFT_ENTRY_INDEX) {
50
+ draft = transaction.state.doc.toString();
51
+ }
52
+
53
+ return {
54
+ ...value,
55
+ draft,
56
+ index: newHistoryIndex,
57
+ documentUpdate: value.history[newHistoryIndex],
58
+ };
59
+ } else if (effect.value === 'FORWARDS') {
60
+ const newHistoryIndex = currentHistoryIndex - 1;
61
+
62
+ if (currentHistoryIndex === DRAFT_ENTRY_INDEX) {
63
+ return { ...value, documentUpdate: null };
64
+ }
65
+
66
+ if (newHistoryIndex === DRAFT_ENTRY_INDEX) {
67
+ return {
68
+ ...value,
69
+ index: newHistoryIndex,
70
+ documentUpdate: value.draft,
71
+ };
72
+ } else {
73
+ return {
74
+ ...value,
75
+ index: newHistoryIndex,
76
+ documentUpdate: value.history[newHistoryIndex],
77
+ };
78
+ }
79
+ }
80
+ } else if (effect.is(pushToHistory)) {
81
+ return {
82
+ ...value,
83
+ index: DRAFT_ENTRY_INDEX,
84
+ history: [effect.value, ...value.history],
85
+ };
86
+ }
87
+ }
88
+ return value;
89
+ },
90
+ });
91
+
92
+ type HistoryNavigation = 'BACK' | 'FORWARDS';
93
+ const moveInHistory = StateEffect.define<HistoryNavigation>();
94
+ const pushToHistory = StateEffect.define<string>();
95
+
96
+ function navigateHistory(view: EditorView, direction: HistoryNavigation) {
97
+ view.dispatch(
98
+ view.state.update({
99
+ effects: moveInHistory.of(direction),
100
+ selection: { anchor: view.state.doc.length },
101
+ }),
102
+ );
103
+
104
+ const updatedHistory = view.state.field<HistoryState>(historyState, false);
105
+ if (updatedHistory.documentUpdate !== null) {
106
+ view.dispatch(
107
+ view.state.update({
108
+ changes: {
109
+ from: 0,
110
+ to: view.state.doc.length,
111
+ insert: updatedHistory.documentUpdate,
112
+ },
113
+ }),
114
+ );
115
+ }
116
+ }
117
+
118
+ type ReplProps = {
119
+ onExecute: (cmd: string) => void;
120
+ initialHistory?: string[];
121
+ onNewHistoryEntry?: (historyEntry: string) => void;
122
+ };
123
+
124
+ export const replMode = ({
125
+ onExecute,
126
+ onNewHistoryEntry,
127
+ initialHistory,
128
+ }: ReplProps): Extension[] => {
129
+ return [
130
+ historyState.init(() => ({
131
+ ...historyInitialState,
132
+ history: initialHistory,
133
+ })),
134
+ keymap.of([
135
+ {
136
+ key: 'Ctrl-Enter',
137
+ mac: 'Mod-Enter',
138
+ preventDefault: true,
139
+ run: (view) => {
140
+ const doc = view.state.doc.toString();
141
+ if (doc.trim() !== '') {
142
+ onExecute?.(doc);
143
+ onNewHistoryEntry?.(doc);
144
+ view.dispatch({
145
+ effects: pushToHistory.of(doc),
146
+ });
147
+ }
148
+
149
+ return true;
150
+ },
151
+ },
152
+ {
153
+ key: 'ArrowUp',
154
+ preventDefault: true,
155
+ run: (view) => {
156
+ // If text is selected or cursor is not at top of document, do nothing
157
+ const { empty, head } = view.state.selection.main;
158
+ if (!empty || head !== 0) {
159
+ return false;
160
+ }
161
+
162
+ navigateHistory(view, 'BACK');
163
+ return true;
164
+ },
165
+ },
166
+ {
167
+ mac: 'Cmd-ArrowUp',
168
+ win: 'Ctrl-ArrowUp',
169
+ linux: 'Ctrl-ArrowUp',
170
+ preventDefault: true,
171
+ run: (view) => {
172
+ // If text is selected or cursor is not at top of document, do nothing
173
+ const { empty, head } = view.state.selection.main;
174
+ if (!empty || head !== 0) {
175
+ return false;
176
+ }
177
+
178
+ navigateHistory(view, 'BACK');
179
+ return true;
180
+ },
181
+ },
182
+ {
183
+ key: 'ArrowDown',
184
+ preventDefault: true,
185
+ run: (view) => {
186
+ const { empty, head } = view.state.selection.main;
187
+ // If text is selected or cursor is not at end of document, do nothing
188
+ if (!empty || head !== view.state.doc.length) {
189
+ return false;
190
+ }
191
+ navigateHistory(view, 'FORWARDS');
192
+
193
+ return true;
194
+ },
195
+ },
196
+ {
197
+ mac: 'Cmd-ArrowDown',
198
+ win: 'Ctrl-ArrowDown',
199
+ linux: 'Ctrl-ArrowDown',
200
+ preventDefault: true,
201
+ run: (view) => {
202
+ const { empty, head } = view.state.selection.main;
203
+ // If text is selected or cursor is not at end of document, do nothing
204
+ if (!empty || head !== view.state.doc.length) {
205
+ return false;
206
+ }
207
+ navigateHistory(view, 'FORWARDS');
208
+
209
+ return true;
210
+ },
211
+ },
212
+ ]),
213
+ ];
214
+ };
package/src/themes.ts ADDED
@@ -0,0 +1,130 @@
1
+ import { Extension } from '@codemirror/state';
2
+ import { light, mirage } from 'ayu';
3
+ import {
4
+ createCypherTheme,
5
+ ThemeOptions,
6
+ } from './lang-cypher/create-cypher-theme';
7
+ import { tokens } from './ndl-tokens-copy';
8
+
9
+ /* ndl exports most tokens as hex colors but some tokens are exported as rgb colors, in the form of "10, 20, 30"
10
+ This should be fixed in version 2 of ndl.
11
+ Meanwhile we can use this function */
12
+ const convertToHex = (color: string) => {
13
+ if (color.startsWith('#')) {
14
+ return color;
15
+ }
16
+
17
+ const rgb = color.match(/\d+/g);
18
+ if (!rgb) {
19
+ return color;
20
+ }
21
+ const [r, g, b] = rgb;
22
+ return `#${Number(r).toString(16)}${Number(g).toString(16)}${Number(
23
+ b,
24
+ ).toString(16)}`;
25
+ };
26
+
27
+ export const lightThemeConstants: ThemeOptions = {
28
+ dark: false,
29
+ editorSettings: {
30
+ background: light.editor.bg.hex(),
31
+ foreground: light.editor.fg.hex(),
32
+ gutterForeground: light.editor.gutter.normal.hex(),
33
+ selection: light.editor.selection.active.hex(),
34
+ textMatchingSelection: light.editor.findMatch.active.hex(),
35
+ cursor: '#000000',
36
+ autoCompletionPanel: {
37
+ selectedColor: '#cce2ff',
38
+ matchingTextColor: '#0066bf',
39
+ backgroundColor: '#F3F4F5',
40
+ },
41
+ searchPanel: {
42
+ background: tokens.palette.light.neutral.bg.default,
43
+ text: tokens.palette.light.neutral.text.default,
44
+ buttonHoverBackground: tokens.palette.light.neutral.bg.strong,
45
+ },
46
+ },
47
+ highlightStyles: {
48
+ comment: light.syntax.comment.hex(),
49
+ keyword: light.syntax.keyword.hex(),
50
+ keywordLiteral: light.syntax.keyword.hex(),
51
+ label: light.syntax.markup.hex(),
52
+ predicateFunction: light.syntax.func.hex(),
53
+ function: light.syntax.func.hex(),
54
+ procedure: light.syntax.func.hex(),
55
+ stringLiteral: light.syntax.string.hex(),
56
+ numberLiteral: light.syntax.constant.hex(),
57
+ booleanLiteral: light.syntax.constant.hex(),
58
+ operator: light.syntax.operator.hex(),
59
+ property: light.syntax.tag.hex(),
60
+ paramDollar: light.syntax.regexp.hex(),
61
+ paramValue: light.syntax.regexp.hex(),
62
+ namespace: light.syntax.special.hex(),
63
+ },
64
+ };
65
+
66
+ export const darkThemeConstants: ThemeOptions = {
67
+ dark: true,
68
+ editorSettings: {
69
+ background: mirage.editor.bg.hex(),
70
+ foreground: mirage.editor.fg.hex(),
71
+ gutterForeground: mirage.editor.gutter.normal.hex(),
72
+ selection: mirage.editor.selection.active.hex(),
73
+ textMatchingSelection: mirage.editor.findMatch.active.hex(),
74
+ cursor: '#ffffff',
75
+ autoCompletionPanel: {
76
+ selectedColor: '#062f4a',
77
+ matchingTextColor: '#0097fb',
78
+ backgroundColor: '#1C212B',
79
+ },
80
+ searchPanel: {
81
+ background: convertToHex(tokens.palette.dark.neutral.bg.default),
82
+ text: convertToHex(tokens.palette.dark.neutral.text.default),
83
+ buttonHoverBackground: convertToHex(
84
+ tokens.palette.dark.neutral.bg.strong,
85
+ ),
86
+ },
87
+ },
88
+ highlightStyles: {
89
+ comment: mirage.syntax.comment.hex(),
90
+ keyword: mirage.syntax.keyword.hex(),
91
+ keywordLiteral: mirage.syntax.keyword.hex(),
92
+ label: mirage.syntax.markup.hex(),
93
+ predicateFunction: mirage.syntax.func.hex(),
94
+ function: mirage.syntax.func.hex(),
95
+ procedure: mirage.syntax.func.hex(),
96
+ stringLiteral: mirage.syntax.string.hex(),
97
+ numberLiteral: mirage.syntax.constant.hex(),
98
+ booleanLiteral: mirage.syntax.constant.hex(),
99
+ operator: mirage.syntax.operator.hex(),
100
+ property: mirage.syntax.tag.hex(),
101
+ paramDollar: mirage.syntax.regexp.hex(),
102
+ paramValue: mirage.syntax.regexp.hex(),
103
+ namespace: mirage.syntax.special.hex(),
104
+ },
105
+ };
106
+
107
+ type ExtraThemeOptions = { inheritBgColor?: boolean };
108
+ export const ayuLightTheme = ({ inheritBgColor }: ExtraThemeOptions) => {
109
+ return createCypherTheme({ ...lightThemeConstants, inheritBgColor });
110
+ };
111
+
112
+ export const ayuDarkTheme = ({ inheritBgColor }: ExtraThemeOptions) => {
113
+ return createCypherTheme({ ...darkThemeConstants, inheritBgColor });
114
+ };
115
+
116
+ export function getThemeExtension(
117
+ theme: 'light' | 'dark' | 'none' | Extension,
118
+ inheritBgColor?: boolean,
119
+ ): Extension | Extension[] {
120
+ switch (theme) {
121
+ case 'light':
122
+ return ayuLightTheme({ inheritBgColor });
123
+ case 'dark':
124
+ return ayuDarkTheme({ inheritBgColor });
125
+ case 'none':
126
+ return [];
127
+ default:
128
+ return theme;
129
+ }
130
+ }