@neo4j-cypher/react-codemirror
Advanced tools
Comparing version 2.0.0-canary-21699b7 to 2.0.0-canary-2a6f081
import { EditorState, Extension } from '@codemirror/state'; | ||
import { EditorView, KeyBinding, ViewUpdate } from '@codemirror/view'; | ||
import type { DbSchema } from '@neo4j-cypher/language-support'; | ||
import { type DbSchema } from '@neo4j-cypher/language-support'; | ||
import { Component } from 'react'; | ||
@@ -25,2 +25,9 @@ type DomEventHandlers = Parameters<typeof EditorView.domEventHandlers>[0]; | ||
/** | ||
* If true, pressing enter will add a new line to the editor and cmd/ctrl + enter will execute the query. | ||
* Otherwise pressing enter on a single line will execute the query. | ||
* | ||
* @default false | ||
*/ | ||
newLineOnEnter?: boolean; | ||
/** | ||
* The editor history navigable via up/down arrow keys. Order newest to oldest. | ||
@@ -60,2 +67,17 @@ * Add to this list with the `onExecute` callback for REPL style history. | ||
/** | ||
* Whether the signature help tooltip should be shown below the text. | ||
* If false, it will be shown above. | ||
* | ||
* @default true | ||
*/ | ||
showSignatureTooltipBelow?: boolean; | ||
/** | ||
* Internal feature flags for the editor. Don't use in production | ||
* | ||
*/ | ||
featureFlags?: { | ||
consoleCommands?: boolean; | ||
signatureInfoOnAutoCompletions?: boolean; | ||
}; | ||
/** | ||
* The schema to use for autocompletion and linting. | ||
@@ -117,2 +139,6 @@ * | ||
readonly?: boolean; | ||
/** | ||
* String value to assign to the aria-label attribute of the editor | ||
*/ | ||
ariaLabel?: string; | ||
} | ||
@@ -136,2 +162,3 @@ type CypherEditorState = { | ||
private schemaRef; | ||
private latestDispatchedValue; | ||
/** | ||
@@ -138,0 +165,0 @@ * Focus the editor |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { insertNewline } from '@codemirror/commands'; | ||
import { Annotation, Compartment, EditorState, } from '@codemirror/state'; | ||
@@ -11,6 +12,20 @@ import { EditorView, keymap, lineNumbers, placeholder, } from '@codemirror/view'; | ||
import { getThemeExtension } from './themes'; | ||
const executeKeybinding = (onExecute) => onExecute | ||
? [ | ||
{ | ||
const executeKeybinding = (onExecute, newLineOnEnter) => { | ||
const keybindings = { | ||
'Shift-Enter': { | ||
key: 'Shift-Enter', | ||
run: insertNewline, | ||
}, | ||
'Ctrl-Enter': { | ||
key: 'Ctrl-Enter', | ||
run: () => true, | ||
}, | ||
Enter: { | ||
key: 'Enter', | ||
run: insertNewline, | ||
}, | ||
}; | ||
if (onExecute) { | ||
keybindings['Ctrl-Enter'] = { | ||
key: 'Ctrl-Enter', | ||
mac: 'Mod-Enter', | ||
@@ -25,5 +40,24 @@ preventDefault: true, | ||
}, | ||
}, | ||
] | ||
: []; | ||
}; | ||
if (!newLineOnEnter) { | ||
keybindings['Enter'] = { | ||
key: 'Enter', | ||
preventDefault: true, | ||
run: (view) => { | ||
const doc = view.state.doc.toString(); | ||
if (doc.includes('\n')) { | ||
// Returning false means the event will mark the event | ||
// as not handled and the default behavior will be executed | ||
return false; | ||
} | ||
if (doc.trim() !== '') { | ||
onExecute(doc); | ||
} | ||
return true; | ||
}, | ||
}; | ||
} | ||
} | ||
return Object.values(keybindings); | ||
}; | ||
const themeCompartment = new Compartment(); | ||
@@ -56,2 +90,3 @@ const keyBindingCompartment = new Compartment(); | ||
schemaRef = createRef(); | ||
latestDispatchedValue; | ||
/** | ||
@@ -92,2 +127,3 @@ * Focus the editor | ||
lineWrap: false, | ||
showSignatureTooltipBelow: true, | ||
extraKeybindings: [], | ||
@@ -97,11 +133,20 @@ history: [], | ||
lineNumbers: true, | ||
newLineOnEnter: false, | ||
}; | ||
debouncedOnChange = this.props.onChange | ||
? debounce(this.props.onChange, 200) | ||
? debounce(((value, viewUpdate) => { | ||
this.latestDispatchedValue = value; | ||
this.props.onChange(value, viewUpdate); | ||
}), 200) | ||
: undefined; | ||
componentDidMount() { | ||
const { theme, extraKeybindings, lineWrap, overrideThemeBackgroundColor, schema, lint, onExecute, } = this.props; | ||
const { theme, extraKeybindings, lineWrap, overrideThemeBackgroundColor, schema, lint, showSignatureTooltipBelow, featureFlags, onExecute, newLineOnEnter, } = this.props; | ||
this.schemaRef.current = { | ||
schema, | ||
lint, | ||
showSignatureTooltipBelow, | ||
featureFlags: { | ||
consoleCommands: true, | ||
...featureFlags, | ||
}, | ||
useLightVersion: false, | ||
@@ -129,3 +174,6 @@ setUseLightVersion: (newVal) => { | ||
extensions: [ | ||
keyBindingCompartment.of(keymap.of([...executeKeybinding(onExecute), ...extraKeybindings])), | ||
keyBindingCompartment.of(keymap.of([ | ||
...executeKeybinding(onExecute, newLineOnEnter), | ||
...extraKeybindings, | ||
])), | ||
historyNavigation(this.props), | ||
@@ -145,2 +193,7 @@ basicNeo4jSetup(), | ||
: []), | ||
this.props.ariaLabel | ||
? EditorView.contentAttributes.of({ | ||
'aria-label': this.props.ariaLabel, | ||
}) | ||
: [], | ||
], | ||
@@ -169,3 +222,5 @@ doc: this.props.value, | ||
const currentCmValue = this.editorView.current.state?.doc.toString() ?? ''; | ||
if (this.props.value !== undefined && currentCmValue !== this.props.value) { | ||
if (this.props.value !== undefined && | ||
this.props.value !== this.latestDispatchedValue) { | ||
this.debouncedOnChange?.cancel(); | ||
this.editorView.current.dispatch({ | ||
@@ -211,3 +266,3 @@ changes: { | ||
effects: keyBindingCompartment.reconfigure(keymap.of([ | ||
...executeKeybinding(this.props.onExecute), | ||
...executeKeybinding(this.props.onExecute, this.props.newLineOnEnter), | ||
...this.props.extraKeybindings, | ||
@@ -240,2 +295,3 @@ ])), | ||
this.schemaRef.current.lint = this.props.lint; | ||
this.schemaRef.current.featureFlags = this.props.featureFlags; | ||
} | ||
@@ -242,0 +298,0 @@ componentWillUnmount() { |
@@ -5,3 +5,2 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { CypherEditor } from '../CypherEditor'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('hello world end 2 end test', async ({ mount }) => { | ||
@@ -134,2 +133,119 @@ const component = await mount(_jsx(CypherEditor, { value: "hello world" })); | ||
}); | ||
async function getInfoTooltip(page, methodName) { | ||
const infoTooltip = page.locator('.cm-completionInfo'); | ||
const firstOption = page.locator('li[aria-selected="true"]'); | ||
let selectedOption = firstOption; | ||
while (!(await infoTooltip.textContent()).includes(methodName)) { | ||
await page.keyboard.press('ArrowDown'); | ||
const currentSelected = page.locator('li[aria-selected="true"]'); | ||
expect(currentSelected).not.toBe(selectedOption); | ||
expect(currentSelected).not.toBe(firstOption); | ||
selectedOption = currentSelected; | ||
} | ||
return infoTooltip; | ||
} | ||
test('shows signature help information on auto-completion for procedures', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: testData.mockSchema, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const procName = 'apoc.periodic.iterate'; | ||
const procedure = testData.mockSchema.procedures[procName]; | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('CALL apoc.periodic.'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
const infoTooltip = await getInfoTooltip(page, procName); | ||
await expect(infoTooltip).toContainText(procedure.signature); | ||
await expect(infoTooltip).toContainText(procedure.description); | ||
}); | ||
test('shows signature help information on auto-completion for functions', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: testData.mockSchema, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const fnName = 'apoc.coll.combinations'; | ||
const fn = testData.mockSchema.functions[fnName]; | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('RETURN apoc.coll.'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
const infoTooltip = await getInfoTooltip(page, fnName); | ||
await expect(infoTooltip).toContainText(fn.signature); | ||
await expect(infoTooltip).toContainText(fn.description); | ||
}); | ||
test('shows deprecated procedures as strikethrough on auto-completion', async ({ page, mount, }) => { | ||
const procName = 'apoc.trigger.resume'; | ||
await mount(_jsx(CypherEditor, { schema: { | ||
procedures: { [procName]: testData.mockSchema.procedures[procName] }, | ||
}, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('CALL apoc.trigger.'); | ||
// We need to assert on the element having the right class | ||
// and trusting the CSS is making this truly strikethrough | ||
await expect(page.locator('.cm-deprecated-completion')).toBeVisible(); | ||
}); | ||
test('shows deprecated function as strikethrough on auto-completion', async ({ page, mount, }) => { | ||
const fnName = 'apoc.create.uuid'; | ||
await mount(_jsx(CypherEditor, { schema: { | ||
functions: { [fnName]: testData.mockSchema.functions[fnName] }, | ||
}, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('RETURN apoc.create.'); | ||
// We need to assert on the element having the right class | ||
// and trusting the CSS is making this truly strikethrough | ||
await expect(page.locator('.cm-deprecated-completion')).toBeVisible(); | ||
}); | ||
test('does not signature help information on auto-completion if flag not enabled explicitly', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: testData.mockSchema })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('CALL apoc.periodic.'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
await expect(page.locator('.cm-completionInfo')).not.toBeVisible(); | ||
}); | ||
test('does not signature help information on auto-completion if docs and signature are empty', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: testData.mockSchema, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('C'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
await expect(page.locator('.cm-completionInfo')).not.toBeVisible(); | ||
}); | ||
test('shows signature help information on auto-completion if description is not empty, signature is', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: { | ||
procedures: { | ||
'db.ping': { | ||
...testData.emptyProcedure, | ||
description: 'foo', | ||
signature: '', | ||
name: 'db.ping', | ||
}, | ||
}, | ||
}, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('CALL db.'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
await expect(page.locator('.cm-completionInfo')).toBeVisible(); | ||
}); | ||
test('shows signature help information on auto-completion if signature is not empty, description is', async ({ page, mount, }) => { | ||
await mount(_jsx(CypherEditor, { schema: { | ||
procedures: { | ||
'db.ping': { | ||
...testData.emptyProcedure, | ||
description: '', | ||
signature: 'foo', | ||
name: 'db.ping', | ||
}, | ||
}, | ||
}, featureFlags: { | ||
signatureInfoOnAutoCompletions: true, | ||
} })); | ||
const textField = page.getByRole('textbox'); | ||
await textField.fill('CALL db.'); | ||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible(); | ||
await expect(page.locator('.cm-completionInfo')).toBeVisible(); | ||
}); | ||
//# sourceMappingURL=autoCompletion.spec.js.map |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { expect, test } from '@playwright/experimental-ct-react'; | ||
import { CypherEditor } from '../CypherEditor'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('prompt shows up', async ({ mount, page }) => { | ||
@@ -73,2 +72,13 @@ const component = await mount(_jsx(CypherEditor, { prompt: "neo4j>" })); | ||
}); | ||
test('aria-label is not set by default', async ({ mount, page }) => { | ||
await mount(_jsx(CypherEditor, {})); | ||
const textField = page.getByRole('textbox'); | ||
expect(await textField.getAttribute('aria-label')).toBeNull(); | ||
}); | ||
test('can set aria-label', async ({ mount, page }) => { | ||
const ariaLabel = 'Cypher Editor'; | ||
await mount(_jsx(CypherEditor, { ariaLabel: ariaLabel })); | ||
const textField = page.getByRole('textbox'); | ||
expect(await textField.getAttribute('aria-label')).toEqual(ariaLabel); | ||
}); | ||
//# sourceMappingURL=configuration.spec.js.map |
@@ -5,3 +5,2 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { CypherEditorPage } from './e2eUtils'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('can add extra keybinding statically', async ({ mount, page }) => { | ||
@@ -8,0 +7,0 @@ const editorPage = new CypherEditorPage(page); |
@@ -5,3 +5,2 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { CypherEditorPage } from './e2eUtils'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('respects preloaded history', async ({ page, mount }) => { | ||
@@ -19,3 +18,5 @@ const editorPage = new CypherEditorPage(page); | ||
await expect(page.getByText('second')).toBeVisible(); | ||
// First arrow down is to get to end of line | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await expect(page.getByText('first')).toBeVisible(); | ||
@@ -25,4 +26,48 @@ await editorPage.getEditor().press('ArrowDown'); | ||
}); | ||
test('can execute queries and see them in history', async ({ page, mount }) => { | ||
test('can add new lines without onExecute', async ({ page, mount }) => { | ||
const editorPage = new CypherEditorPage(page); | ||
const editorComponent = await mount(_jsx(CypherEditor, {})); | ||
// Ctrl-Enter does nothing when onExecute is false | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await expect(editorComponent).toHaveText('1\n', { | ||
useInnerText: true, | ||
}); | ||
// Enter adds new lines | ||
await editorPage.getEditor().fill('Brock'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await expect(editorComponent).toHaveText('1\n2\n3\nBrock', { | ||
useInnerText: true, | ||
}); | ||
// Shift-Enter adds new lines | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', { | ||
useInnerText: true, | ||
}); | ||
}); | ||
test('can add new lines with newLineOnEnter and without onExecute', async ({ page, mount, }) => { | ||
const editorPage = new CypherEditorPage(page); | ||
const editorComponent = await mount(_jsx(CypherEditor, { newLineOnEnter: true })); | ||
// Ctrl-Enter does nothing when onExecute is false | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await expect(editorComponent).toHaveText('1\n', { | ||
useInnerText: true, | ||
}); | ||
// Enter adds new lines | ||
await editorPage.getEditor().fill('Brock'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await expect(editorComponent).toHaveText('1\n2\n3\nBrock', { | ||
useInnerText: true, | ||
}); | ||
// Shift-Enter adds new lines | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await expect(editorComponent).toHaveText('1\n2\n3\n4\n5\nBrock', { | ||
useInnerText: true, | ||
}); | ||
}); | ||
test('can execute queries and see them in history with newLineOnEnter', async ({ page, mount, }) => { | ||
const editorPage = new CypherEditorPage(page); | ||
const initialValue = `MATCH (n) | ||
@@ -34,3 +79,3 @@ RETURN n;`; | ||
}; | ||
const editor = await mount(_jsx(CypherEditor, { value: initialValue, history: history, onExecute: onExecute })); | ||
const editor = await mount(_jsx(CypherEditor, { value: initialValue, history: history, onExecute: onExecute, newLineOnEnter: true })); | ||
// Execute initial query | ||
@@ -45,3 +90,3 @@ await editorPage.getEditor().press('Control+Enter'); | ||
expect(history.length).toBe(1); | ||
// Ensure only enter doesn't execute query | ||
// Ensure cmd+enter is required in multiline | ||
await editorPage.getEditor().fill('multiline'); | ||
@@ -51,3 +96,3 @@ await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await page.keyboard.type('entry'); | ||
@@ -74,2 +119,4 @@ expect(history.length).toBe(1); | ||
// arrow movements don't matter until bottom is hit | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowUp'); | ||
@@ -79,2 +126,5 @@ await editorPage.getEditor().press('ArrowUp'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
// editor still multiline | ||
@@ -86,2 +136,43 @@ await expect(page.getByText('multiline')).toBeVisible(); | ||
}); | ||
test('can execute queries without newLineOnEnter', async ({ page, mount }) => { | ||
const editorPage = new CypherEditorPage(page); | ||
const initialValue = 'Brock'; | ||
const history = []; | ||
const onExecute = (cmd) => { | ||
history.unshift(cmd); | ||
}; | ||
const editorComponent = await mount(_jsx(CypherEditor, { value: initialValue, onExecute: onExecute })); | ||
// Cmd/Control still executes initial query | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await editorPage.getEditor().press('Meta+Enter'); | ||
expect(history.length).toBe(1); | ||
// Ensure query execution doesn't fire if the query is only whitespace | ||
await editorPage.getEditor().fill(' '); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await editorPage.getEditor().press('Meta+Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
expect(history.length).toBe(1); | ||
// Ensure enter executes query when in single line | ||
await editorPage.getEditor().fill('Misty'); | ||
await editorPage.getEditor().press('Enter'); | ||
expect(history.length).toBe(2); | ||
expect(history).toEqual(['Misty', 'Brock']); | ||
// Ensure cmd+enter is required in multiline | ||
await editorPage.getEditor().fill('multiline'); | ||
await editorPage.getEditor().press('Shift+Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('A'); | ||
// line numbers and the text | ||
await expect(editorComponent).toHaveText('1\n2\n3\nmultiline\nA', { | ||
useInnerText: true, | ||
}); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
await editorPage.getEditor().press('Enter'); | ||
expect(history.length).toBe(2); | ||
await editorPage.getEditor().press('Control+Enter'); | ||
await editorPage.getEditor().press('Meta+Enter'); | ||
expect(history.length).toBe(3); | ||
}); | ||
test('can navigate with cmd+up as well', async ({ page, mount }) => { | ||
@@ -94,2 +185,3 @@ const editorPage = new CypherEditorPage(page); | ||
await mount(_jsx(CypherEditor, { value: initialValue, history: [ | ||
'first', | ||
`one | ||
@@ -99,3 +191,2 @@ multiline | ||
.`, | ||
'second', | ||
], onExecute: () => { | ||
@@ -105,18 +196,18 @@ /* needed to turn on history movements */ | ||
await editorPage.getEditor().press(metaUp); | ||
await expect(page.getByText('first')).toBeVisible(); | ||
// move in history | ||
await editorPage.getEditor().press(metaUp); | ||
await expect(page.getByText('multiline')).toBeVisible(); | ||
// Single meta up moves all the way to top of editor on mac | ||
// Single meta down moves all the way to top of editor on mac | ||
if (isMac) { | ||
await editorPage.getEditor().press(metaUp); | ||
await editorPage.getEditor().press(metaDown); | ||
} | ||
else { | ||
await editorPage.getEditor().press('ArrowUp'); | ||
await editorPage.getEditor().press('ArrowUp'); | ||
await editorPage.getEditor().press('ArrowUp'); | ||
await editorPage.getEditor().press('ArrowUp'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
await editorPage.getEditor().press('ArrowDown'); | ||
} | ||
// move in history | ||
await editorPage.getEditor().press(metaUp); | ||
await expect(page.getByText('second')).toBeVisible(); | ||
await editorPage.getEditor().press(metaDown); | ||
await expect(page.getByText('multiline')).toBeVisible(); | ||
await expect(page.getByText('first')).toBeVisible(); | ||
}); | ||
@@ -123,0 +214,0 @@ test('test onExecute', async ({ page, mount }) => { |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { expect, test } from '@playwright/experimental-ct-react'; | ||
import { CypherEditor } from '../CypherEditor'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('can mount the editor with text', async ({ mount }) => { | ||
@@ -6,0 +5,0 @@ const component = await mount(_jsx(CypherEditor, { value: "MATCH (n) RETURN n;" })); |
@@ -152,2 +152,29 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
}); | ||
test('Signature help is shown below the text by default', async ({ page, mount, }) => { | ||
// We need to introduce new lines to make sure there's | ||
// enough space to show the tooltip above | ||
const query = '\n\n\n\n\n\n\nRETURN abs('; | ||
await mount(_jsx(CypherEditor, { value: query, schema: testData.mockSchema, autofocus: true })); | ||
await expect(page.locator('.cm-signature-help-panel.cm-tooltip-below')).toBeVisible({ | ||
timeout: 2000, | ||
}); | ||
}); | ||
test('Setting showSignatureTooltipBelow to true shows the signature help above the text', async ({ page, mount, }) => { | ||
// We need to introduce new lines to make sure there's | ||
// enough space to show the tooltip above | ||
const query = '\n\n\n\n\n\n\nRETURN abs('; | ||
await mount(_jsx(CypherEditor, { value: query, schema: testData.mockSchema, showSignatureTooltipBelow: true, autofocus: true })); | ||
await expect(page.locator('.cm-signature-help-panel.cm-tooltip-below')).toBeVisible({ | ||
timeout: 2000, | ||
}); | ||
}); | ||
test('Setting showSignatureTooltipBelow to false shows the signature help above the text', async ({ page, mount, }) => { | ||
// We need to introduce new lines to make sure there's | ||
// enough space to show the tooltip above | ||
const query = '\n\n\n\n\n\n\nRETURN abs('; | ||
await mount(_jsx(CypherEditor, { value: query, schema: testData.mockSchema, showSignatureTooltipBelow: false, autofocus: true })); | ||
await expect(page.locator('.cm-signature-help-panel.cm-tooltip-above')).toBeVisible({ | ||
timeout: 2000, | ||
}); | ||
}); | ||
//# sourceMappingURL=signatureHelp.spec.js.map |
import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { expect, test } from '@playwright/experimental-ct-react'; | ||
import { CypherEditor } from '../CypherEditor'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('can complete pattern snippet', async ({ page, mount }) => { | ||
@@ -6,0 +5,0 @@ await mount(_jsx(CypherEditor, {})); |
@@ -6,3 +6,2 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
import { CypherEditorPage } from './e2eUtils'; | ||
test.use({ viewport: { width: 500, height: 500 } }); | ||
test('light theme highlighting', async ({ page, mount }) => { | ||
@@ -9,0 +8,0 @@ const editorPage = new CypherEditorPage(page); |
@@ -84,3 +84,3 @@ import { StateEffect } from '@codemirror/state'; | ||
effects: moveInHistory.of(direction), | ||
selection: { anchor: view.state.doc.length }, | ||
selection: { anchor: direction === 'BACK' ? 0 : view.state.doc.length }, | ||
})); | ||
@@ -87,0 +87,0 @@ const updatedHistory = view.state.field(historyState, false); |
@@ -1,4 +0,4 @@ | ||
export { CypherParser } from '@neo4j-cypher/language-support'; | ||
export { CypherParser, _internalFeatureFlags, } from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
export { cypher } from './lang-cypher/langCypher'; | ||
export { darkThemeConstants, lightThemeConstants } from './themes'; |
@@ -1,2 +0,2 @@ | ||
export { CypherParser } from '@neo4j-cypher/language-support'; | ||
export { CypherParser, _internalFeatureFlags, } from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
@@ -3,0 +3,0 @@ export { cypher } from './lang-cypher/langCypher'; |
@@ -1,3 +0,6 @@ | ||
import { CompletionSource } from '@codemirror/autocomplete'; | ||
import { Completion, CompletionSource } from '@codemirror/autocomplete'; | ||
import type { CypherConfig } from './langCypher'; | ||
export declare const completionStyles: (completion: Completion & { | ||
deprecated?: boolean; | ||
}) => string; | ||
export declare const cypherAutocomplete: (config: CypherConfig) => CompletionSource; |
@@ -1,4 +0,5 @@ | ||
import { snippet } from '@codemirror/autocomplete'; | ||
import { snippet, } from '@codemirror/autocomplete'; | ||
import { autocomplete } from '@neo4j-cypher/language-support'; | ||
import { CompletionItemKind } from 'vscode-languageserver-types'; | ||
import { CompletionItemKind, CompletionItemTag, } from 'vscode-languageserver-types'; | ||
import { getDocString } from './utils'; | ||
const completionKindToCodemirrorIcon = (c) => { | ||
@@ -35,6 +36,14 @@ const map = { | ||
}; | ||
export const completionStyles = (completion) => { | ||
if (completion.deprecated) { | ||
return 'cm-deprecated-completion'; | ||
} | ||
else { | ||
return null; | ||
} | ||
}; | ||
export const cypherAutocomplete = (config) => (context) => { | ||
const textUntilCursor = context.state.doc.toString().slice(0, context.pos); | ||
const documentText = context.state.doc.toString(); | ||
const triggerCharacters = ['.', ':', '{', '$', ')']; | ||
const lastCharacter = textUntilCursor.slice(-1); | ||
const lastCharacter = documentText.at(context.pos - 1); | ||
const lastWord = context.matchBefore(/\w*/); | ||
@@ -49,16 +58,69 @@ const inWord = lastWord.from !== lastWord.to; | ||
} | ||
const options = autocomplete(textUntilCursor, config.schema ?? {}, undefined, context.explicit); | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => ({ | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
})), | ||
}; | ||
const options = autocomplete(documentText, config.schema ?? {}, context.pos, context.explicit); | ||
if (config.featureFlags?.signatureInfoOnAutoCompletions) { | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => { | ||
let maybeInfo = {}; | ||
let emptyInfo = true; | ||
const newDiv = document.createElement('div'); | ||
if (o.signature) { | ||
const header = document.createElement('p'); | ||
header.setAttribute('class', 'cm-completionInfo-signature'); | ||
header.textContent = o.signature; | ||
if (header.textContent.length > 0) { | ||
emptyInfo = false; | ||
newDiv.appendChild(header); | ||
} | ||
} | ||
if (o.documentation) { | ||
const paragraph = document.createElement('p'); | ||
paragraph.textContent = getDocString(o.documentation); | ||
if (paragraph.textContent.length > 0) { | ||
emptyInfo = false; | ||
newDiv.appendChild(paragraph); | ||
} | ||
} | ||
if (!emptyInfo) { | ||
maybeInfo = { | ||
info: () => Promise.resolve(newDiv), | ||
}; | ||
} | ||
const deprecated = o.tags?.find((tag) => tag === CompletionItemTag.Deprecated) ?? | ||
false; | ||
// The negative boost moves the deprecation down the list | ||
// so we offer the user the completions that are | ||
// deprecated the last | ||
const maybeDeprecated = deprecated | ||
? { boost: -99, deprecated: true } | ||
: {}; | ||
return { | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
...maybeDeprecated, | ||
...maybeInfo, | ||
}; | ||
}), | ||
}; | ||
} | ||
else { | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => ({ | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
})), | ||
}; | ||
} | ||
}; | ||
//# sourceMappingURL=autocomplete.js.map |
import { tags } from '@lezer/highlight'; | ||
import { applySyntaxColouring } from '@neo4j-cypher/language-support'; | ||
import { applySyntaxColouring, CypherTokenType, } from '@neo4j-cypher/language-support'; | ||
import { tokenTypeToStyleTag } from './constants'; | ||
@@ -54,3 +54,3 @@ const cypherQueryWithAllTokenTypes = `MATCH (variable :Label)-[:REL_TYPE]->() | ||
const styleTags = tokenTypes.map((tokenType) => { | ||
if (tokenType === 'none') | ||
if (tokenType === CypherTokenType.none) | ||
return undefined; | ||
@@ -57,0 +57,0 @@ return tokenTypeToStyleTag[tokenType]; |
import { HighlightStyle, syntaxHighlighting, } from '@codemirror/language'; | ||
import { EditorView } from '@codemirror/view'; | ||
import { CypherTokenType } from '@neo4j-cypher/language-support'; | ||
import { tokenTypeToStyleTag } from './constants'; | ||
@@ -81,2 +82,8 @@ import { byWordSvg, caseSensitiveSvg, downArrowSvg, regexSvg, replaceAllSvg, replaceSvg, upArrowSvg, } from './themeIcons'; | ||
}, | ||
'.cm-completionInfo-signature': { | ||
color: 'darkgrey', | ||
}, | ||
'.cm-deprecated-completion': { | ||
'text-decoration': 'line-through', | ||
}, | ||
'.cm-tooltip-autocomplete': { | ||
@@ -167,3 +174,3 @@ maxWidth: '430px', | ||
color, | ||
class: token === 'consoleCommand' ? 'cm-bold' : undefined, | ||
class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined, | ||
})); | ||
@@ -170,0 +177,0 @@ const highlightStyle = HighlightStyle.define(styles); |
@@ -5,2 +5,7 @@ import { LanguageSupport } from '@codemirror/language'; | ||
lint?: boolean; | ||
showSignatureTooltipBelow?: boolean; | ||
featureFlags?: { | ||
signatureInfoOnAutoCompletions?: boolean; | ||
consoleCommands?: boolean; | ||
}; | ||
schema?: DbSchema; | ||
@@ -7,0 +12,0 @@ useLightVersion: boolean; |
@@ -0,4 +1,5 @@ | ||
import { autocompletion } from '@codemirror/autocomplete'; | ||
import { defineLanguageFacet, Language, LanguageSupport, } from '@codemirror/language'; | ||
import { setConsoleCommandsEnabled, } from '@neo4j-cypher/language-support'; | ||
import { cypherAutocomplete } from './autocomplete'; | ||
import { _internalFeatureFlags, } from '@neo4j-cypher/language-support'; | ||
import { completionStyles, cypherAutocomplete } from './autocomplete'; | ||
import { ParserAdapter } from './parser-adapter'; | ||
@@ -12,8 +13,13 @@ import { signatureHelpTooltip } from './signatureHelp'; | ||
export function cypher(config) { | ||
setConsoleCommandsEnabled(true); | ||
const featureFlags = config.featureFlags; | ||
// We allow to override the consoleCommands feature flag | ||
if (featureFlags.consoleCommands !== undefined) { | ||
_internalFeatureFlags.consoleCommands = featureFlags.consoleCommands; | ||
} | ||
const parserAdapter = new ParserAdapter(facet, config); | ||
const cypherLanguage = new Language(facet, parserAdapter, [], 'cypher'); | ||
return new LanguageSupport(cypherLanguage, [ | ||
cypherLanguage.data.of({ | ||
autocomplete: cypherAutocomplete(config), | ||
autocompletion({ | ||
override: [cypherAutocomplete(config)], | ||
optionClass: completionStyles, | ||
}), | ||
@@ -20,0 +26,0 @@ cypherLinter(config), |
import { StateField } from '@codemirror/state'; | ||
import { showTooltip } from '@codemirror/view'; | ||
import { signatureHelp } from '@neo4j-cypher/language-support'; | ||
import { getDocString } from './utils'; | ||
function getTriggerCharacter(query, caretPosition) { | ||
@@ -16,3 +17,3 @@ let i = caretPosition - 1; | ||
const parameters = signature.parameters; | ||
const doc = signature.documentation.toString(); | ||
const doc = getDocString(signature.documentation); | ||
const dom = document.createElement('div'); | ||
@@ -69,6 +70,7 @@ dom.className = 'cm-signature-help-panel'; | ||
const signature = signatures[activeSignature]; | ||
const showSignatureTooltipBelow = config.showSignatureTooltipBelow ?? true; | ||
result = [ | ||
{ | ||
pos: caretPosition, | ||
above: true, | ||
above: !showSignatureTooltipBelow, | ||
arrow: true, | ||
@@ -75,0 +77,0 @@ create: createSignatureHelpElement({ signature, activeParameter }), |
@@ -20,3 +20,3 @@ { | ||
], | ||
"version": "2.0.0-canary-21699b7", | ||
"version": "2.0.0-canary-2a6f081", | ||
"main": "./dist/index.js", | ||
@@ -46,12 +46,12 @@ "types": "./dist/index.d.ts", | ||
"dependencies": { | ||
"@codemirror/autocomplete": "^6.5.1", | ||
"@codemirror/commands": "^6.2.2", | ||
"@codemirror/language": "^6.6.0", | ||
"@codemirror/lint": "^6.2.2", | ||
"@codemirror/search": "^6.5.0", | ||
"@codemirror/state": "^6.2.1", | ||
"@codemirror/view": "^6.13.2", | ||
"@codemirror/autocomplete": "^6.17.0", | ||
"@codemirror/commands": "^6.6.0", | ||
"@codemirror/language": "^6.10.2", | ||
"@codemirror/lint": "^6.8.1", | ||
"@codemirror/search": "^6.5.6", | ||
"@codemirror/state": "^6.4.1", | ||
"@codemirror/view": "^6.28.4", | ||
"@lezer/common": "^1.0.2", | ||
"@lezer/highlight": "^1.1.3", | ||
"@neo4j-cypher/language-support": "2.0.0-canary-21699b7", | ||
"@neo4j-cypher/language-support": "2.0.0-canary-2a6f081", | ||
"@types/prismjs": "^1.26.3", | ||
@@ -58,0 +58,0 @@ "@types/workerpool": "^6.4.7", |
@@ -100,3 +100,3 @@ import { Extension, StateEffect } from '@codemirror/state'; | ||
effects: moveInHistory.of(direction), | ||
selection: { anchor: view.state.doc.length }, | ||
selection: { anchor: direction === 'BACK' ? 0 : view.state.doc.length }, | ||
}), | ||
@@ -103,0 +103,0 @@ ); |
@@ -1,4 +0,7 @@ | ||
export { CypherParser } from '@neo4j-cypher/language-support'; | ||
export { | ||
CypherParser, | ||
_internalFeatureFlags, | ||
} from '@neo4j-cypher/language-support'; | ||
export { CypherEditor } from './CypherEditor'; | ||
export { cypher } from './lang-cypher/langCypher'; | ||
export { darkThemeConstants, lightThemeConstants } from './themes'; |
@@ -1,6 +0,14 @@ | ||
import { CompletionSource, snippet } from '@codemirror/autocomplete'; | ||
import { | ||
Completion, | ||
CompletionSource, | ||
snippet, | ||
} from '@codemirror/autocomplete'; | ||
import { autocomplete } from '@neo4j-cypher/language-support'; | ||
import { CompletionItemKind } from 'vscode-languageserver-types'; | ||
import { | ||
CompletionItemKind, | ||
CompletionItemTag, | ||
} from 'vscode-languageserver-types'; | ||
import { CompletionItemIcons } from '../icons'; | ||
import type { CypherConfig } from './langCypher'; | ||
import { getDocString } from './utils'; | ||
@@ -40,8 +48,18 @@ const completionKindToCodemirrorIcon = (c: CompletionItemKind) => { | ||
export const completionStyles: ( | ||
completion: Completion & { deprecated?: boolean }, | ||
) => string = (completion) => { | ||
if (completion.deprecated) { | ||
return 'cm-deprecated-completion'; | ||
} else { | ||
return null; | ||
} | ||
}; | ||
export const cypherAutocomplete: (config: CypherConfig) => CompletionSource = | ||
(config) => (context) => { | ||
const textUntilCursor = context.state.doc.toString().slice(0, context.pos); | ||
const documentText = context.state.doc.toString(); | ||
const triggerCharacters = ['.', ':', '{', '$', ')']; | ||
const lastCharacter = textUntilCursor.slice(-1); | ||
const lastCharacter = documentText.at(context.pos - 1); | ||
@@ -63,21 +81,79 @@ const lastWord = context.matchBefore(/\w*/); | ||
const options = autocomplete( | ||
textUntilCursor, | ||
documentText, | ||
config.schema ?? {}, | ||
undefined, | ||
context.pos, | ||
context.explicit, | ||
); | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => ({ | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: | ||
o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
})), | ||
}; | ||
if (config.featureFlags?.signatureInfoOnAutoCompletions) { | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => { | ||
let maybeInfo = {}; | ||
let emptyInfo = true; | ||
const newDiv = document.createElement('div'); | ||
if (o.signature) { | ||
const header = document.createElement('p'); | ||
header.setAttribute('class', 'cm-completionInfo-signature'); | ||
header.textContent = o.signature; | ||
if (header.textContent.length > 0) { | ||
emptyInfo = false; | ||
newDiv.appendChild(header); | ||
} | ||
} | ||
if (o.documentation) { | ||
const paragraph = document.createElement('p'); | ||
paragraph.textContent = getDocString(o.documentation); | ||
if (paragraph.textContent.length > 0) { | ||
emptyInfo = false; | ||
newDiv.appendChild(paragraph); | ||
} | ||
} | ||
if (!emptyInfo) { | ||
maybeInfo = { | ||
info: () => Promise.resolve(newDiv), | ||
}; | ||
} | ||
const deprecated = | ||
o.tags?.find((tag) => tag === CompletionItemTag.Deprecated) ?? | ||
false; | ||
// The negative boost moves the deprecation down the list | ||
// so we offer the user the completions that are | ||
// deprecated the last | ||
const maybeDeprecated = deprecated | ||
? { boost: -99, deprecated: true } | ||
: {}; | ||
return { | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: | ||
o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
...maybeDeprecated, | ||
...maybeInfo, | ||
}; | ||
}), | ||
}; | ||
} else { | ||
return { | ||
from: context.matchBefore(/(\w|\$)*$/).from, | ||
options: options.map((o) => ({ | ||
label: o.label, | ||
type: completionKindToCodemirrorIcon(o.kind), | ||
apply: | ||
o.kind === CompletionItemKind.Snippet | ||
? // codemirror requires an empty snippet space to be able to tab out of the completion | ||
snippet((o.insertText ?? o.label) + '${}') | ||
: undefined, | ||
detail: o.detail, | ||
})), | ||
}; | ||
} | ||
}; |
import { tags } from '@lezer/highlight'; | ||
import { applySyntaxColouring } from '@neo4j-cypher/language-support'; | ||
import { | ||
applySyntaxColouring, | ||
CypherTokenType, | ||
} from '@neo4j-cypher/language-support'; | ||
import { tokenTypeToStyleTag } from './constants'; | ||
@@ -57,3 +60,3 @@ | ||
const styleTags = tokenTypes.map((tokenType) => { | ||
if (tokenType === 'none') return undefined; | ||
if (tokenType === CypherTokenType.none) return undefined; | ||
return tokenTypeToStyleTag[tokenType]; | ||
@@ -60,0 +63,0 @@ }); |
@@ -8,2 +8,3 @@ import { | ||
import { EditorView } from '@codemirror/view'; | ||
import { CypherTokenType } from '@neo4j-cypher/language-support'; | ||
import { StyleSpec } from 'style-mod'; | ||
@@ -127,3 +128,8 @@ import { HighlightedCypherTokenTypes, tokenTypeToStyleTag } from './constants'; | ||
}, | ||
'.cm-completionInfo-signature': { | ||
color: 'darkgrey', | ||
}, | ||
'.cm-deprecated-completion': { | ||
'text-decoration': 'line-through', | ||
}, | ||
'.cm-tooltip-autocomplete': { | ||
@@ -235,3 +241,3 @@ maxWidth: '430px', | ||
color, | ||
class: token === 'consoleCommand' ? 'cm-bold' : undefined, | ||
class: token === CypherTokenType.consoleCommand ? 'cm-bold' : undefined, | ||
}), | ||
@@ -238,0 +244,0 @@ ); |
@@ -0,1 +1,2 @@ | ||
import { autocompletion } from '@codemirror/autocomplete'; | ||
import { | ||
@@ -7,6 +8,6 @@ defineLanguageFacet, | ||
import { | ||
setConsoleCommandsEnabled, | ||
_internalFeatureFlags, | ||
type DbSchema, | ||
} from '@neo4j-cypher/language-support'; | ||
import { cypherAutocomplete } from './autocomplete'; | ||
import { completionStyles, cypherAutocomplete } from './autocomplete'; | ||
import { ParserAdapter } from './parser-adapter'; | ||
@@ -23,2 +24,7 @@ import { signatureHelpTooltip } from './signatureHelp'; | ||
lint?: boolean; | ||
showSignatureTooltipBelow?: boolean; | ||
featureFlags?: { | ||
signatureInfoOnAutoCompletions?: boolean; | ||
consoleCommands?: boolean; | ||
}; | ||
schema?: DbSchema; | ||
@@ -30,3 +36,8 @@ useLightVersion: boolean; | ||
export function cypher(config: CypherConfig) { | ||
setConsoleCommandsEnabled(true); | ||
const featureFlags = config.featureFlags; | ||
// We allow to override the consoleCommands feature flag | ||
if (featureFlags.consoleCommands !== undefined) { | ||
_internalFeatureFlags.consoleCommands = featureFlags.consoleCommands; | ||
} | ||
const parserAdapter = new ParserAdapter(facet, config); | ||
@@ -37,4 +48,5 @@ | ||
return new LanguageSupport(cypherLanguage, [ | ||
cypherLanguage.data.of({ | ||
autocomplete: cypherAutocomplete(config), | ||
autocompletion({ | ||
override: [cypherAutocomplete(config)], | ||
optionClass: completionStyles, | ||
}), | ||
@@ -41,0 +53,0 @@ cypherLinter(config), |
@@ -6,2 +6,3 @@ import { EditorState, StateField } from '@codemirror/state'; | ||
import { CypherConfig } from './langCypher'; | ||
import { getDocString } from './utils'; | ||
@@ -31,3 +32,3 @@ function getTriggerCharacter(query: string, caretPosition: number) { | ||
const parameters = signature.parameters; | ||
const doc = signature.documentation.toString(); | ||
const doc = getDocString(signature.documentation); | ||
const dom = document.createElement('div'); | ||
@@ -105,2 +106,4 @@ dom.className = 'cm-signature-help-panel'; | ||
const signature = signatures[activeSignature]; | ||
const showSignatureTooltipBelow = | ||
config.showSignatureTooltipBelow ?? true; | ||
@@ -110,3 +113,3 @@ result = [ | ||
pos: caretPosition, | ||
above: true, | ||
above: !showSignatureTooltipBelow, | ||
arrow: true, | ||
@@ -113,0 +116,0 @@ create: createSignatureHelpElement({ signature, activeParameter }), |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
633211
125
8428
1
5526
+ Added@neo4j-cypher/language-support@2.0.0-canary-2a6f081(transitive)
- Removed@neo4j-cypher/language-support@2.0.0-canary-21699b7(transitive)
Updated@codemirror/commands@^6.6.0
Updated@codemirror/language@^6.10.2
Updated@codemirror/lint@^6.8.1
Updated@codemirror/search@^6.5.6
Updated@codemirror/state@^6.4.1
Updated@codemirror/view@^6.28.4