UNPKG

@neo4j-cypher/editor-support

Version:

Core functionality to support Cypher integration into editors

194 lines (192 loc) 7.38 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import { CompletionTypeResolver } from "./completion/CompletionTypeResolver"; import { AutoCompletion } from "./completion/AutoCompletion"; import * as CypherTypes from "./lang/CypherTypes"; import { CypherSyntaxHighlight } from "./highlight/CypherSyntaxHighlight"; import { TreeUtils } from "./util/TreeUtils"; import { PositionConverter } from "./util/PositionConverter"; import { retryOperation } from "./util/retryOperation"; import { parse } from "./util/parse"; export class CypherEditorSupport { constructor(input = "") { _defineProperty(this, "schema", {}); _defineProperty(this, "input", null); _defineProperty(this, "positionConverter", new PositionConverter("")); _defineProperty(this, "parseTree", null); _defineProperty(this, "parseErrors", []); _defineProperty(this, "referencesProviders", new Map()); _defineProperty(this, "completion", new AutoCompletion()); _defineProperty(this, "queriesAndCommands", []); _defineProperty(this, "statements", []); _defineProperty(this, "listeners", []); _defineProperty(this, "version", 0); _defineProperty(this, "ensureVersion", (version, delay = 30, times = 5) => retryOperation(() => new Promise((resolve, reject) => { if (version === this.version) { return resolve(); } return reject(); }), delay, times)); this.update(input); } on(eventName, cb) { this.listeners[eventName] = Array.isArray(this.listeners[eventName]) ? this.listeners[eventName].concat([cb]) : this.listeners[eventName] = [cb]; } off(eventName, cb) { if (!this.listeners[eventName]) return; const index = this.listeners[eventName].indexOf(cb); if (index > -1) { this.listeners[eventName].splice(index, 1); } } trigger(eventName, args = []) { if (!this.listeners[eventName]) return; this.listeners[eventName].forEach(cb => cb(...args)); } update(input = "", version) { this.trigger("update"); if (input === this.input) { this.version = version || this.version; this.trigger("updated", [{ queriesAndCommands: this.queriesAndCommands, referencesProviders: this.referencesProviders }]); return; } this.positionConverter = new PositionConverter(input); this.input = input; // const startTime = new Date(); const { parseTree, referencesListener, errorListener, referencesProviders } = parse(input); this.parseTree = parseTree; // console.log('updated parse tree: ', input, version, 'time: ' + (new Date() - startTime)); this.parseErrors = errorListener.errors; const { queriesAndCommands, statements } = referencesListener; this.statements = statements; this.queriesAndCommands = queriesAndCommands; this.referencesProviders = referencesProviders; this.completion.updateReferenceProviders(this.referencesProviders); this.version = version || this.version; this.trigger("updated", [{ queriesAndCommands: this.queriesAndCommands, referencesProviders: this.referencesProviders }]); } setSchema(schema) { this.schema = schema; this.completion.updateSchema(this.schema); } getElement(line, column) { const abs = this.positionConverter.toAbsolute(line, column); function getElement(pt) { const pos = TreeUtils.getPosition(pt); if (pos != null && (abs < pos.start || abs > pos.stop)) { return null; } const c = pt.getChildCount(); if (c === 0 && pos != null) { return pt; } for (let i = 0; i < c; i += 1) { const e = getElement(pt.getChild(i)); if (e != null) { return e; } } return pos != null ? pt : null; } return getElement(this.parseTree); } getReferences(line, column) { const e = TreeUtils.findAnyParent(this.getElement(line, column), CypherTypes.SYMBOLIC_CONTEXTS); if (e == null) { return []; } const query = e instanceof CypherTypes.VARIABLE_CONTEXT ? TreeUtils.findAnyParent(e, [CypherTypes.QUERY_CONTEXT]) : null; return this.referencesProviders.get(e).getReferences(e.getText(), query); } getCompletionInfo(line, column) { const element = this.getElementForCompletion(line, column); const query = TreeUtils.findAnyParent(element, [CypherTypes.QUERY_CONTEXT]); const { found, types } = CompletionTypeResolver.getTypes(element); return { element, query, found, types }; } getElementForCompletion(line, column) { const e = this.getElement(line, column); return TreeUtils.findAnyParent(e, CypherTypes.COMPLETION_CANDIDATES) || e; } getCompletion(line, column, doFilter = true) { let info = this.getCompletionInfo(line, column); // Shift by one symbol back and try again. if (!info.found && column > 0) { const prevInfo = this.getCompletionInfo(line, column - 1); if (prevInfo.found) { info = prevInfo; } } const { element, query, found, types } = info; const replaceRange = { from: { line, column }, to: { line, column } }; let filter = null; const shouldBeReplaced = AutoCompletion.shouldBeReplaced(element); if (found && shouldBeReplaced) { // There are number of situations where we need to be smarter than default behavior const { start, stop } = TreeUtils.getPosition(element); const smartReplaceRange = AutoCompletion.calculateSmartReplaceRange(element, start, stop); if (smartReplaceRange) { replaceRange.from = this.positionConverter.toRelative(smartReplaceRange.start); replaceRange.to = this.positionConverter.toRelative(smartReplaceRange.stop + 1); if (smartReplaceRange.filterText) { filter = smartReplaceRange.filterText; } } else { replaceRange.from = this.positionConverter.toRelative(start); replaceRange.to = this.positionConverter.toRelative(stop + 1); } } if (filter === null) { filter = doFilter && found && shouldBeReplaced ? element.getText() : ""; } return _objectSpread({ items: this.completion.getItems(types, { filter, query }) }, replaceRange); } applyHighlighthing(callback) { CypherSyntaxHighlight.process(this.parseTree, callback); } }