examples/xviz-playground/src/auto-complete.js (110 lines of code) (raw):

// Copyright (c) 2019 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /* eslint-disable no-eval */ import {XVIZMetadataBuilder, XVIZBuilder} from '@xviz/builder'; const INLINE_COMMENT_REGEX = /\s*\/\/.*[\n\r]/g; const BLOCK_COMMENT_REGEX = /\s*\/\*(\*(?!\/)|[^*])*\*\//g; const LAST_STMT_REGEX = /\s*([^;]+)\.([^;]*)/im; const dataCompleter = { insertMatch: (editor, data) => { const {filterText} = editor.completer.completions; if (filterText) { const ranges = editor.selection.getAllRanges(); for (let i = 0; i < ranges.length; i++) { const range = ranges[i]; range.start.column -= filterText.length; editor.session.remove(range); } } editor.execCommand('insertstring', data.value); if (data.args.length) { const pos = editor.getCursorPosition(); editor.moveCursorTo(pos.row, pos.column - 1); } } }; export default { getCompletions: (editor, session, pos, prefix, callback) => { const lines = editor.getValue().split('\n'); const code = lines.slice(0, pos.row).join('\n') + lines[pos.row].slice(0, pos.column); const lastStatement = code.slice(code.lastIndexOf(';') + 1).match(LAST_STMT_REGEX); if (!lastStatement) { return null; } const suggestions = autocomplete(lastStatement[1]); if (!suggestions) { return null; } return callback(null, suggestions); }, identifierRegexps: [/^\.(.*)$/] }; const metadataBuilder = new XVIZMetadataBuilder(); const xvizBuilder = new XVIZBuilder(); function autocomplete(code) { const wrappedCode = `function fn(xvizMetadataBuilder, xvizBuilder) { return ${removeComments(code)} }`; let instance; try { const func = eval(`(function() { return ${wrappedCode} })()`); instance = func(metadataBuilder, xvizBuilder); } catch (error) { // ignore } if (instance) { return getAutoCompleteSuggestions(instance); } return null; } function getAutoCompleteSuggestions(object) { object = object && Object.getPrototypeOf(object); if (!object) { return null; } if (object.constructor.hasOwnProperty('_autoCompleteSuggestions')) { return object.constructor._autoCompleteSuggestions; } const publicMethods = getPublicMethods(object); Object.defineProperty(object.constructor, '_autoCompleteSuggestions', { enumerable: false, value: Object.keys(publicMethods).map(name => ({ name, args: publicMethods[name], caption: `${name}(${publicMethods[name].join(', ')})`, value: `.${name}()`, completer: dataCompleter })) }); return object.constructor._autoCompleteSuggestions; } function getPublicMethods(object) { const proto = object && Object.getPrototypeOf(object); if (!proto) { return {}; } if (object.constructor.hasOwnProperty('_publicMethods')) { return object.constructor._publicMethods; } const result = {}; const properties = Object.getOwnPropertyNames(object); for (let i = 0; i < properties.length; i++) { const key = properties[i]; if (key !== 'constructor' && key[0] !== '_' && typeof object[key] === 'function') { result[key] = getArguments(object[key]); } } Object.assign(result, getPublicMethods(proto)); Object.defineProperty(object.constructor, '_publicMethods', { enumerable: false, value: result }); return object.constructor._publicMethods; } function getArguments(func) { const source = func.toString(); const argsList = source.match(/\(([^()]*)\)/); if (argsList && argsList[1]) { return argsList[1].split(/,\s*/); } return []; } function removeComments(code) { return code.replace(INLINE_COMMENT_REGEX, '').replace(BLOCK_COMMENT_REGEX, ''); }