UNPKG

@neo4j-cypher/editor-support

Version:

Core functionality to support Cypher integration into editors

265 lines (260 loc) 9.48 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.KEYWORD_ITEMS = exports.AutoCompletion = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _fuse = _interopRequireDefault(require("fuse.js")); var _lodash = _interopRequireDefault(require("lodash.find")); var CypherTypes = _interopRequireWildcard(require("../lang/CypherTypes")); var CompletionTypes = _interopRequireWildcard(require("./CompletionTypes")); var _CypherKeywords = _interopRequireDefault(require("../lang/CypherKeywords")); var _TreeUtils = require("../util/TreeUtils"); var _ecsapeCypher = require("../util/ecsapeCypher"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const KEYWORD_ITEMS = _CypherKeywords.default.map(keyword => ({ type: CompletionTypes.KEYWORD, view: keyword, content: keyword, postfix: null })); exports.KEYWORD_ITEMS = KEYWORD_ITEMS; const fuzzySearch = (list, text, key) => { const fuse = new _fuse.default(list, { keys: [key] }); return fuse.search(text).map(({ item }) => item); }; class AbstractCachingCompletion { constructor(cache = {}) { (0, _defineProperty2.default)(this, "cache", {}); this.cache = cache; } // eslint-disable-next-line class-methods-use-this, no-unused-vars calculateItems(type, query = null) { return []; } complete(types, query) { return types.map(typeData => { const cached = this.cache[typeData.type]; if (cached != null) { return cached; } return this.calculateItems(typeData, query); }).reduce((acc, items) => [...acc, ...items], []); } } class SchemaBasedCompletion extends AbstractCachingCompletion { constructor(schema = {}) { super({ [CompletionTypes.KEYWORD]: KEYWORD_ITEMS, [CompletionTypes.LABEL]: (schema.labels || []).map(label => ({ type: CompletionTypes.LABEL, view: label, content: (0, _ecsapeCypher.ecsapeCypher)(label), postfix: null })), [CompletionTypes.RELATIONSHIP_TYPE]: (schema.relationshipTypes || []).map(relType => ({ type: CompletionTypes.RELATIONSHIP_TYPE, view: relType, content: (0, _ecsapeCypher.ecsapeCypher)(relType), postfix: null })), [CompletionTypes.PROPERTY_KEY]: (schema.propertyKeys || []).map(propKey => ({ type: CompletionTypes.PROPERTY_KEY, view: propKey, content: (0, _ecsapeCypher.ecsapeCypher)(propKey), postfix: null })), [CompletionTypes.FUNCTION_NAME]: (schema.functions || []).map(({ name, signature }) => ({ type: CompletionTypes.FUNCTION_NAME, view: name, content: (0, _ecsapeCypher.ecsapeCypher)(name), postfix: signature })), [CompletionTypes.PROCEDURE_NAME]: (schema.procedures || []).map(({ name, signature }) => ({ type: CompletionTypes.PROCEDURE_NAME, view: name, content: name, postfix: signature })), [CompletionTypes.CONSOLE_COMMAND_NAME]: (schema.consoleCommands || []).map(consoleCommandName => ({ type: CompletionTypes.CONSOLE_COMMAND_NAME, view: consoleCommandName.name, content: consoleCommandName.name, postfix: consoleCommandName.description || null })), [CompletionTypes.PARAMETER]: (schema.parameters || []).map(parameter => ({ type: CompletionTypes.PARAMETER, view: parameter, content: parameter, postfix: null })) }); (0, _defineProperty2.default)(this, "schema", {}); this.schema = schema; } calculateItems(typeData) { return (SchemaBasedCompletion.providers[typeData.type] || (() => []))(this.schema, typeData); } } (0, _defineProperty2.default)(SchemaBasedCompletion, "providers", { [CompletionTypes.PROCEDURE_OUTPUT]: (schema, typeData) => { const findByName = e => e.name === typeData.name && e.returnItems !== []; const procedure = (0, _lodash.default)(schema.procedures, findByName); if (procedure) { return procedure.returnItems.map(({ name, signature }) => ({ type: CompletionTypes.PROCEDURE_OUTPUT, view: name, content: name, postfix: ` :: ${signature}` })); } return []; }, [CompletionTypes.CONSOLE_COMMAND_SUBCOMMAND]: (schema, typeData) => { const { filterLastElement, path } = typeData; const length = filterLastElement ? path.length - 1 : path.length; let currentLevel = schema.consoleCommands; for (let i = 0; i < length; i += 1) { const foundCommand = (0, _lodash.default)(currentLevel, ["name", path[i]]); if (foundCommand) { currentLevel = foundCommand.commands || []; } else { return []; } } return currentLevel.map(({ name, description }) => ({ type: CompletionTypes.CONSOLE_COMMAND_SUBCOMMAND, view: name, content: name, postfix: description || null })); } }); class QueryBasedCompletion extends AbstractCachingCompletion { constructor(referenceProviders = {}) { super(); (0, _defineProperty2.default)(this, "providers", {}); (0, _defineProperty2.default)(this, "emptyProvider", { getNames: () => [] }); this.providers = { [CompletionTypes.VARIABLE]: query => (referenceProviders.get(CypherTypes.VARIABLE_CONTEXT) || this.emptyProvider).getNames(query).map(name => ({ type: CompletionTypes.VARIABLE, view: name, content: name, postfix: null })) }; } calculateItems(typeData, query) { return (this.providers[typeData.type] || (() => []))(query); } } class AutoCompletion { constructor() { (0, _defineProperty2.default)(this, "queryBased", null); (0, _defineProperty2.default)(this, "schemaBased", null); this.updateSchema({}); } getItems(types, { query = null, filter = "" }) { const text = filter.toLowerCase(); const filteredText = AutoCompletion.filterText(text); const completionItemFilter = () => true; const list = [this.queryBased, this.schemaBased].filter(s => s != null).map(t => t.complete(types, query)).reduce((acc, items) => [...acc, ...items], []).filter(completionItemFilter); if (filteredText) { return fuzzySearch(list, filteredText, "view"); } if (text) { return fuzzySearch(list, text, "view"); } return list; } updateSchema(schema) { this.schemaBased = new SchemaBasedCompletion(schema); } updateReferenceProviders(referenceProviders) { this.queryBased = new QueryBasedCompletion(referenceProviders); } /** * Define whether element should be replaced or not. */ static shouldBeReplaced(element) { if (element == null) { return false; } const text = element.getText(); const parent = element.parentCtx; // If element is whitespace if (/^\s+$/.test(text)) { return false; } // If element is opening bracket (e.g. start of relationship pattern) if (text === "[") { return false; } // If element is opening brace (e.g. start of node pattern) if (text === "(") { return false; } if (text === ".") { return false; } if (text === "{") { return false; } if (text === "$") { return false; } if (text === ":" && parent != null && parent instanceof CypherTypes.MAP_LITERAL_ENTRY) { return false; } return true; } static filterText(text) { if (text.startsWith("$")) { return text.slice(1); } return text; } // eslint-disable-next-line no-unused-vars static calculateSmartReplaceRange(element, start, stop) { // If we are in relationship type or label and we have error nodes in there. // This means that we typed in just ':' and Antlr consumed other tokens in element // In this case replace only ':' if (element instanceof CypherTypes.RELATIONSHIP_TYPE_CONTEXT || element instanceof CypherTypes.NODE_LABEL_CONTEXT) { if (_TreeUtils.TreeUtils.hasErrorNode(element)) { return { filterText: ":", start, stop: start }; } } return null; } } exports.AutoCompletion = AutoCompletion;