@neo4j-cypher/react-codemirror 2.0.0-alpha.0 → 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 (59) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +24 -4
  4. package/dist/{index.cjs → cjs/index.cjs} +187 -62
  5. package/dist/cjs/index.cjs.map +7 -0
  6. package/{esm → dist/esm}/index.mjs +196 -64
  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 +48 -15
  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
@@ -3,9 +3,17 @@ import { CypherParser, parse } from "@neo4j-cypher/language-support";
3
3
  import * as ReactCodemirror from "@uiw/react-codemirror";
4
4
 
5
5
  // src/CypherEditor.tsx
6
- import { EditorView as EditorView4, keymap as keymap3 } from "@codemirror/view";
7
- import CodeEditor from "@uiw/react-codemirror";
8
- import React from "react";
6
+ import {
7
+ Annotation,
8
+ Compartment,
9
+ EditorState as EditorState2
10
+ } from "@codemirror/state";
11
+ import {
12
+ EditorView as EditorView4,
13
+ keymap as keymap3,
14
+ lineNumbers
15
+ } from "@codemirror/view";
16
+ import { Component, createRef } from "react";
9
17
 
10
18
  // src/lang-cypher/lang-cypher.ts
11
19
  import {
@@ -47,8 +55,7 @@ var completionKindToCodemirrorIcon = (c) => {
47
55
  };
48
56
  return map[c];
49
57
  };
50
- var emptySchema = {};
51
- var cypherAutocomplete = (schema) => (context) => {
58
+ var cypherAutocomplete = (config) => (context) => {
52
59
  const textUntilCursor = context.state.doc.toString().slice(0, context.pos);
53
60
  const triggerCharacters = [".", ":", "{", "$"];
54
61
  const lastCharacter = textUntilCursor.slice(-1);
@@ -58,7 +65,7 @@ var cypherAutocomplete = (schema) => (context) => {
58
65
  if (!shouldTriggerCompletion) {
59
66
  return null;
60
67
  }
61
- const options = autocomplete(textUntilCursor, schema ?? emptySchema);
68
+ const options = autocomplete(textUntilCursor, config.schema ?? {});
62
69
  return {
63
70
  from: context.matchBefore(/(\w|\$)*$/).from,
64
71
  options: options.map((o) => ({
@@ -202,17 +209,18 @@ var ParserAdapter = class extends Parser {
202
209
  import { linter } from "@codemirror/lint";
203
210
  import { validateSyntax } from "@neo4j-cypher/language-support";
204
211
  import { DiagnosticSeverity } from "vscode-languageserver-types";
205
- var cypherLinter = (schema) => linter((view) => {
206
- const diagnostics = [];
207
- validateSyntax(view.state.doc.toString(), schema).forEach((diagnostic) => {
208
- diagnostics.push({
212
+ var cypherLinter = (config) => linter((view) => {
213
+ if (!config.lint) {
214
+ return [];
215
+ }
216
+ return validateSyntax(view.state.doc.toString(), config.schema).map(
217
+ (diagnostic) => ({
209
218
  from: diagnostic.offsets.start,
210
219
  to: diagnostic.offsets.end,
211
220
  severity: diagnostic.severity === DiagnosticSeverity.Error ? "error" : "warning",
212
221
  message: diagnostic.message
213
- });
214
- });
215
- return diagnostics;
222
+ })
223
+ );
216
224
  });
217
225
 
218
226
  // src/lang-cypher/lang-cypher.ts
@@ -222,12 +230,12 @@ var facet = defineLanguageFacet({
222
230
  });
223
231
  var parserAdapter = new ParserAdapter(facet);
224
232
  var cypherLanguage = new Language(facet, parserAdapter, [], "cypher");
225
- function cypher({ lint, schema = {} }) {
233
+ function cypher(config) {
226
234
  return new LanguageSupport(cypherLanguage, [
227
235
  cypherLanguage.data.of({
228
- autocomplete: cypherAutocomplete(schema)
236
+ autocomplete: cypherAutocomplete(config)
229
237
  }),
230
- ...lint ? [cypherLinter(schema)] : []
238
+ cypherLinter(config)
231
239
  ]);
232
240
  }
233
241
 
@@ -262,7 +270,6 @@ import {
262
270
  EditorView,
263
271
  highlightSpecialChars,
264
272
  keymap,
265
- lineNumbers,
266
273
  rectangularSelection
267
274
  } from "@codemirror/view";
268
275
  import { lintKeymap } from "@codemirror/lint";
@@ -346,7 +353,7 @@ var insertTab = (cmd) => {
346
353
  }
347
354
  return true;
348
355
  };
349
- var basicNeo4jSetup = (prompt) => {
356
+ var basicNeo4jSetup = () => {
350
357
  const keymaps = [
351
358
  closeBracketsKeymap,
352
359
  defaultKeymap,
@@ -372,16 +379,6 @@ var basicNeo4jSetup = (prompt) => {
372
379
  }
373
380
  ].flat();
374
381
  const extensions = [];
375
- extensions.push(
376
- lineNumbers({
377
- formatNumber(a, state) {
378
- if (state.doc.lines === 1 && prompt !== void 0) {
379
- return prompt;
380
- }
381
- return a.toString();
382
- }
383
- })
384
- );
385
382
  extensions.push(highlightSpecialChars());
386
383
  extensions.push(history());
387
384
  extensions.push(drawSelection());
@@ -396,6 +393,7 @@ var basicNeo4jSetup = (prompt) => {
396
393
  extensions.push(
397
394
  autocompletion({
398
395
  icons: false,
396
+ interactionDelay: 5,
399
397
  addToOptions: [
400
398
  {
401
399
  render(completion, state) {
@@ -538,8 +536,7 @@ var replMode = ({
538
536
  onExecute?.(doc);
539
537
  onNewHistoryEntry?.(doc);
540
538
  view.dispatch({
541
- effects: pushToHistory.of(doc),
542
- changes: { from: 0, to: view.state.doc.length, insert: "" }
539
+ effects: pushToHistory.of(doc)
543
540
  });
544
541
  }
545
542
  return true;
@@ -1286,45 +1283,179 @@ function getThemeExtension(theme, inheritBgColor) {
1286
1283
 
1287
1284
  // src/CypherEditor.tsx
1288
1285
  import { jsx } from "react/jsx-runtime";
1289
- var CypherEditor = React.forwardRef((props, ref) => {
1290
- const {
1291
- theme = "light",
1292
- extensions = [],
1293
- prompt,
1294
- onExecute,
1295
- initialHistory = [],
1296
- onNewHistoryEntry,
1297
- extraKeybindings = [],
1298
- lineWrap = false,
1299
- overrideThemeBackgroundColor = false,
1300
- schema = {},
1301
- lint = true,
1302
- ...rest
1303
- } = props;
1304
- const maybeReplMode = onExecute ? replMode({
1305
- onExecute,
1306
- initialHistory,
1307
- onNewHistoryEntry
1308
- }) : [];
1309
- return /* @__PURE__ */ jsx(
1310
- CodeEditor,
1311
- {
1312
- ref,
1313
- theme: getThemeExtension(theme, overrideThemeBackgroundColor),
1286
+ var themeCompartment = new Compartment();
1287
+ var keyBindingCompartment = new Compartment();
1288
+ var ExternalEdit = Annotation.define();
1289
+ var CypherEditor = class extends Component {
1290
+ /**
1291
+ * The codemirror editor container.
1292
+ */
1293
+ editorContainer = createRef();
1294
+ /**
1295
+ * The codemirror editor state.
1296
+ */
1297
+ editorState = createRef();
1298
+ /**
1299
+ * The codemirror editor view.
1300
+ */
1301
+ editorView = createRef();
1302
+ schemaRef = createRef();
1303
+ /**
1304
+ * Focus the editor
1305
+ */
1306
+ focus() {
1307
+ this.editorView.current?.focus();
1308
+ }
1309
+ /**
1310
+ * Move the cursor to the supplied position.
1311
+ * For example, to move the cursor to the end of the editor, use `value.length`
1312
+ */
1313
+ updateCursorPosition(position) {
1314
+ this.editorView.current?.dispatch({
1315
+ selection: { anchor: position, head: position }
1316
+ });
1317
+ }
1318
+ /**
1319
+ * Externally set the editor value and focus the editor.
1320
+ */
1321
+ setValueAndFocus(value = "") {
1322
+ const currentCmValue = this.editorView.current.state?.doc.toString() ?? "";
1323
+ this.editorView.current.dispatch({
1324
+ changes: {
1325
+ from: 0,
1326
+ to: currentCmValue.length,
1327
+ insert: value
1328
+ },
1329
+ selection: { anchor: value.length, head: value.length }
1330
+ });
1331
+ this.editorView.current?.focus();
1332
+ }
1333
+ static defaultProps = {
1334
+ lint: true,
1335
+ schema: {},
1336
+ overrideThemeBackgroundColor: false,
1337
+ lineWrap: false,
1338
+ extraKeybindings: [],
1339
+ initialHistory: [],
1340
+ theme: "light"
1341
+ };
1342
+ componentDidMount() {
1343
+ const {
1344
+ theme,
1345
+ onExecute,
1346
+ initialHistory,
1347
+ onNewHistoryEntry,
1348
+ extraKeybindings,
1349
+ lineWrap,
1350
+ overrideThemeBackgroundColor,
1351
+ schema,
1352
+ lint,
1353
+ onChange
1354
+ } = this.props;
1355
+ this.schemaRef.current = { schema, lint };
1356
+ const maybeReplMode = onExecute ? replMode({
1357
+ onExecute,
1358
+ initialHistory,
1359
+ onNewHistoryEntry
1360
+ }) : [];
1361
+ const themeExtension = getThemeExtension(
1362
+ theme,
1363
+ overrideThemeBackgroundColor
1364
+ );
1365
+ const changeListener = onChange ? [
1366
+ EditorView4.updateListener.of((upt) => {
1367
+ const wasUserEdit = !upt.transactions.some(
1368
+ (tr) => tr.annotation(ExternalEdit)
1369
+ );
1370
+ if (upt.docChanged && wasUserEdit) {
1371
+ const doc = upt.state.doc;
1372
+ const value = doc.toString();
1373
+ onChange(value, upt);
1374
+ }
1375
+ })
1376
+ ] : [];
1377
+ this.editorState.current = EditorState2.create({
1314
1378
  extensions: [
1315
- cypher({ lint, schema }),
1316
- keymap3.of(extraKeybindings),
1317
1379
  maybeReplMode,
1318
- basicNeo4jSetup(prompt),
1380
+ basicNeo4jSetup(),
1381
+ themeCompartment.of(themeExtension),
1382
+ changeListener,
1383
+ cypher(this.schemaRef.current),
1384
+ keyBindingCompartment.of(keymap3.of(extraKeybindings)),
1319
1385
  lineWrap ? EditorView4.lineWrapping : [],
1320
- ...extensions
1386
+ lineNumbers({
1387
+ formatNumber: (a, state) => {
1388
+ if (state.doc.lines === 1 && this.props.prompt !== void 0) {
1389
+ return this.props.prompt;
1390
+ }
1391
+ return a.toString();
1392
+ }
1393
+ })
1321
1394
  ],
1322
- basicSetup: false,
1323
- indentWithTab: false,
1324
- ...rest
1395
+ doc: this.props.value
1396
+ });
1397
+ this.editorView.current = new EditorView4({
1398
+ state: this.editorState.current,
1399
+ parent: this.editorContainer.current
1400
+ });
1401
+ if (this.props.autofocus) {
1402
+ this.focus();
1403
+ if (this.props.value) {
1404
+ this.updateCursorPosition(this.props.value.length);
1405
+ }
1325
1406
  }
1326
- );
1327
- });
1407
+ }
1408
+ componentDidUpdate(prevProps) {
1409
+ if (!this.editorView.current) {
1410
+ return;
1411
+ }
1412
+ const currentCmValue = this.editorView.current.state?.doc.toString() ?? "";
1413
+ if (this.props.value !== void 0 && currentCmValue !== this.props.value) {
1414
+ this.editorView.current.dispatch({
1415
+ changes: {
1416
+ from: 0,
1417
+ to: currentCmValue.length,
1418
+ insert: this.props.value ?? ""
1419
+ },
1420
+ annotations: [ExternalEdit.of(true)]
1421
+ });
1422
+ }
1423
+ const didChangeTheme = prevProps.theme !== this.props.theme || prevProps.overrideThemeBackgroundColor !== this.props.overrideThemeBackgroundColor;
1424
+ if (didChangeTheme) {
1425
+ this.editorView.current.dispatch({
1426
+ effects: themeCompartment.reconfigure(
1427
+ getThemeExtension(
1428
+ this.props.theme,
1429
+ this.props.overrideThemeBackgroundColor
1430
+ )
1431
+ )
1432
+ });
1433
+ }
1434
+ if (prevProps.extraKeybindings !== this.props.extraKeybindings) {
1435
+ this.editorView.current.dispatch({
1436
+ effects: keyBindingCompartment.reconfigure(
1437
+ keymap3.of(this.props.extraKeybindings)
1438
+ )
1439
+ });
1440
+ }
1441
+ this.schemaRef.current.schema = this.props.schema;
1442
+ this.schemaRef.current.lint = this.props.lint;
1443
+ }
1444
+ componentWillUnmount() {
1445
+ this.editorView.current?.destroy();
1446
+ }
1447
+ render() {
1448
+ const { className, theme } = this.props;
1449
+ const themeClass = typeof theme === "string" ? `cm-theme-${theme}` : "cm-theme";
1450
+ return /* @__PURE__ */ jsx(
1451
+ "div",
1452
+ {
1453
+ ref: this.editorContainer,
1454
+ className: `${themeClass}${className ? ` ${className}` : ""}`
1455
+ }
1456
+ );
1457
+ }
1458
+ };
1328
1459
  export {
1329
1460
  CypherEditor,
1330
1461
  CypherParser,
@@ -1334,3 +1465,4 @@ export {
1334
1465
  lightThemeConstants,
1335
1466
  parse
1336
1467
  };
1468
+ //# sourceMappingURL=index.mjs.map