From 12d0c6a25e6f6767898d271353c11f5b15c3fcdf Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 5 Jul 2022 14:23:48 +0200 Subject: [PATCH] higlight provider --- apps/remix-ide/src/app.js | 2 +- apps/remix-ide/src/app/panels/tab-proxy.js | 1 - .../remix-ide/src/app/plugins/code-parser.tsx | 900 ------------------ .../src/app/plugins/file-decorator.ts | 1 - .../src/app/plugins/parser/code-parser.tsx | 564 +++++++++++ .../services/code-parser-antlr-service.ts | 160 ++++ .../parser/services/code-parser-compiler.ts | 198 ++++ .../services/code-parser-gas-service.ts | 72 ++ .../services/code-parser-node-helper.ts | 8 + .../src/lib/compiler-content-imports.ts | 1 - .../src/lib/editor-context-listener.ts | 39 - .../src/source/sourceMappingDecoder.ts | 1 - .../src/lib/providers/codeLensProvider.ts | 46 - .../src/lib/providers/completionProvider.ts | 2 +- .../src/lib/providers/definitionProvider.ts | 57 ++ .../src/lib/providers/highlightProvider.ts | 45 + .../editor/src/lib/providers/hoverProvider.ts | 6 +- .../src/lib/providers/referenceProvider.ts | 18 +- .../editor/src/lib/remix-ui-editor.tsx | 38 +- libs/remix-ui/editor/src/types/monaco.ts | 2 +- libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 2 - 21 files changed, 1127 insertions(+), 1036 deletions(-) delete mode 100644 apps/remix-ide/src/app/plugins/code-parser.tsx create mode 100644 apps/remix-ide/src/app/plugins/parser/code-parser.tsx create mode 100644 apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts create mode 100644 apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts create mode 100644 apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts create mode 100644 apps/remix-ide/src/app/plugins/parser/services/code-parser-node-helper.ts delete mode 100644 libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts create mode 100644 libs/remix-ui/editor/src/lib/providers/definitionProvider.ts create mode 100644 libs/remix-ui/editor/src/lib/providers/highlightProvider.ts diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index f821712f57..271324c107 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -14,7 +14,7 @@ import { MainPanel } from './app/components/main-panel' import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin' import { AstWalker } from '@remix-project/remix-astwalker' import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin' -import { CodeParser } from './app/plugins/code-parser' +import { CodeParser } from './app/plugins/parser/code-parser' import { FileDecorator } from './app/plugins/file-decorator' import { WalkthroughService } from './walkthroughService' diff --git a/apps/remix-ide/src/app/panels/tab-proxy.js b/apps/remix-ide/src/app/panels/tab-proxy.js index daa3575ddb..3821788e18 100644 --- a/apps/remix-ide/src/app/panels/tab-proxy.js +++ b/apps/remix-ide/src/app/panels/tab-proxy.js @@ -322,7 +322,6 @@ export class TabProxy extends Plugin { } renderComponent () { - console.log('rendering tabs') const onSelect = (index) => { if (this.loadedTabs[index]) { const name = this.loadedTabs[index].name diff --git a/apps/remix-ide/src/app/plugins/code-parser.tsx b/apps/remix-ide/src/app/plugins/code-parser.tsx deleted file mode 100644 index efdf54e2e9..0000000000 --- a/apps/remix-ide/src/app/plugins/code-parser.tsx +++ /dev/null @@ -1,900 +0,0 @@ -'use strict' -import { Plugin } from '@remixproject/engine' -import { sourceMappingDecoder } from '@remix-project/remix-debug' -import { CompilerAbstract } from '@remix-project/remix-solidity' -import { Compiler } from '@remix-project/remix-solidity' - -import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' -import { helper } from '@remix-project/remix-solidity' - -import React from 'react' -import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' -import { lineText } from '@remix-ui/editor' -// eslint-disable-next-line - - -const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || [] - -const profile = { - name: 'codeParser', - methods: ['nodesAtPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], - events: [], - version: '0.0.1' -} - -export function isNodeDefinition(node: any) { - return node.nodeType === 'ContractDefinition' || - node.nodeType === 'FunctionDefinition' || - node.nodeType === 'ModifierDefinition' || - node.nodeType === 'VariableDeclaration' || - node.nodeType === 'StructDefinition' || - node.nodeType === 'EventDefinition' -} - -export class CodeParser extends Plugin { - - currentFileAST: any // contains the simple parsed AST for the current file - compiler: any // used to compile the current file seperately from the main compiler - lastCompilationResult: any - currentFile: any - _index: any - astWalker: any - errorState: boolean = false - onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise - gastEstimateTimeOut: any - - constructor(astWalker) { - super(profile) - this.astWalker = astWalker - this._index = { - Declarations: {}, - FlatReferences: {} - } - } - - async onActivation() { - this.on('editor', 'didChangeFile', async (file) => { - console.log('contentChanged', file) - await this.call('editor', 'discardLineTexts') - await this.getCurrentFileAST() - await this.compile() - }) - - this.on('filePanel', 'setWorkspace', async () => { - await this.call('fileDecorator', 'setFileDecorators', []) - }) - - - this.on('fileManager', 'currentFileChanged', async () => { - await this.call('editor', 'discardLineTexts') - await this.getCurrentFileAST() - await this.compile() - }) - - this.on('solidity', 'loadingCompiler', async (url) => { - console.log('loading compiler', url) - this.compiler.loadVersion(true, url) - this.compiler.event.register('compilerLoaded', async () => { - console.log('compiler loaded') - }) - }) - - /** - * - processes compilation results - * - calls the editor to add markers on the errors - * - builds the flat index of nodes - */ - this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => { - console.log('compile success', success, data, this) - this.call('editor', 'clearAnnotations') - this.errorState = true - const checkIfFatalError = (error: CompilationError) => { - // Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround - const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning' - if (isValidError) { - console.log(error) - } - } - const result = new CompilerAbstract('soljson', data, source, input) - - if (data.error) checkIfFatalError(data.error) - if (data.errors) data.errors.forEach((err) => checkIfFatalError(err)) - const allErrors = [] - if (data.errors) { - const sources = result.getSourceCode().sources - for (const error of data.errors) { - const pos = helper.getPositionDetails(error.formattedMessage) - const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) - const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', - { - start: error.sourceLocation.start, - length: error.sourceLocation.end - error.sourceLocation.start - }, - filePosition, - result.getSourceCode().sources, - null) - allErrors.push({ error, lineColumn }) - } - console.log('allErrors', allErrors) - await this.call('editor', 'addErrorMarker', allErrors) - - const errorsPerFiles = {} - for (const error of allErrors) { - if (!errorsPerFiles[error.error.sourceLocation.file]) { - errorsPerFiles[error.error.sourceLocation.file] = [] - } - errorsPerFiles[error.error.sourceLocation.file].push(error.error) - } - - const errorPriority = { - 'error': 0, - 'warning': 1, - } - - // sort errorPerFiles by error priority - const sortedErrorsPerFiles = {} - for (const fileName in errorsPerFiles) { - const errors = errorsPerFiles[fileName] - errors.sort((a, b) => { - return errorPriority[a.severity] - errorPriority[b.severity] - } - ) - sortedErrorsPerFiles[fileName] = errors - } - console.log('sortedErrorsPerFiles', sortedErrorsPerFiles) - - const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName]) - - console.log('filesWithOutErrors', filesWithOutErrors) - // add decorators - const decorators: fileDecoration[] = [] - for (const fileName in sortedErrorsPerFiles) { - const errors = sortedErrorsPerFiles[fileName] - const decorator: fileDecoration = { - path: fileName, - isDirectory: false, - fileStateType: errors[0].severity === 'error' ? fileDecorationType.Error : fileDecorationType.Warning, - fileStateLabelClass: errors[0].severity === 'error' ? 'text-danger' : 'text-warning', - fileStateIconClass: '', - fileStateIcon: '', - text: errors.length, - owner: 'code-parser', - bubble: true, - commment: errors.map((error) => error.message), - } - decorators.push(decorator) - } - for (const fileName of filesWithOutErrors) { - const decorator: fileDecoration = { - path: fileName, - isDirectory: false, - fileStateType: fileDecorationType.None, - fileStateLabelClass: '', - fileStateIconClass: '', - fileStateIcon: '', - text: '', - owner: 'code-parser', - bubble: false - } - decorators.push(decorator) - } - console.log(decorators) - await this.call('fileDecorator', 'setFileDecorators', decorators) - await this.call('editor', 'clearErrorMarkers', filesWithOutErrors) - } else { - await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) - const decorators: fileDecoration[] = [] - for (const fileName of Object.keys(result.getSourceCode().sources)) { - const decorator: fileDecoration = { - path: fileName, - isDirectory: false, - fileStateType: fileDecorationType.None, - fileStateLabelClass: '', - fileStateIconClass: '', - fileStateIcon: '', - text: '', - owner: 'code-parser', - bubble: false - } - decorators.push(decorator) - } - console.log(decorators) - - await this.call('fileDecorator', 'setFileDecorators', decorators) - - } - - - if (!data.sources) return - if (data.sources && Object.keys(data.sources).length === 0) return - this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input) - - this.errorState = false - this._index = { - Declarations: {}, - FlatReferences: {}, - NodesPerFile: {}, - } - this._buildIndex(data, source) - - if (this.gastEstimateTimeOut) { - window.clearTimeout(this.gastEstimateTimeOut) - } - - this.gastEstimateTimeOut = window.setTimeout(async () => { - this.setGasEstimates() - }, 500) - - console.log("INDEX", this._index) - this.emit('astFinished') - } - - this.compiler = new Compiler((url, cb) => this.call('contentImport', 'resolveAndSave', url, undefined, true).then((result) => cb(null, result)).catch((error) => cb(error.message))) - this.compiler.event.register('compilationFinished', this.onAstFinished) - } - - // COMPILER - - /** - * - * @returns - */ - async compile() { - try { - const state = await this.call('solidity', 'getCompilerState') - this.compiler.set('optimize', state.optimize) - this.compiler.set('evmVersion', state.evmVersion) - this.compiler.set('language', state.language) - this.compiler.set('runs', state.runs) - this.compiler.set('useFileConfiguration', state.useFileConfiguration) - this.currentFile = await this.call('fileManager', 'file') - console.log(this.currentFile) - if (!this.currentFile) return - const content = await this.call('fileManager', 'readFile', this.currentFile) - const sources = { [this.currentFile]: { content } } - this.compiler.compile(sources, this.currentFile) - } catch (e) { - console.log(e) - } - } - - /** - * - * @returns - */ - async getLastCompilationResult() { - return this.lastCompilationResult - } - - /* - * simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports - */ - - async parseSolidity(text: string) { - const t0 = performance.now(); - const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) - const t1 = performance.now(); - console.log(`Call to doSomething took ${t1 - t0} milliseconds.`); - console.log('AST PARSE SUCCESS', ast) - return ast - } - - /** - * Tries to parse the current file or the given text and returns the AST - * If the parsing fails it returns the last successful AST for this file - * @param text - * @returns - */ - async getCurrentFileAST(text: string | null = null) { - this.currentFile = await this.call('fileManager', 'file') - if (!this.currentFile) return - const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile) - try { - const ast = await this.parseSolidity(fileContent) - this.currentFileAST = ast - } catch (e) { - console.log(e) - } - return this.currentFileAST - } - - /** - * Builds a flat index and declarations of all the nodes in the compilation result - * @param compilationResult - * @param source - */ - _buildIndex(compilationResult, source) { - if (compilationResult && compilationResult.sources) { - const callback = (node) => { - if (node && node.referencedDeclaration) { - if (!this._index.Declarations[node.referencedDeclaration]) { - this._index.Declarations[node.referencedDeclaration] = [] - } - this._index.Declarations[node.referencedDeclaration].push(node) - } - this._index.FlatReferences[node.id] = node - } - for (const s in compilationResult.sources) { - this.astWalker.walkFull(compilationResult.sources[s].ast, callback) - } - - } - } - - // NODE HELPERS - - _getInputParams(node) { - const params = [] - const target = node.parameters - if (target) { - const children = target.parameters - for (const j in children) { - if (children[j].nodeType === 'VariableDeclaration') { - params.push(children[j].typeDescriptions.typeString) - } - } - } - return '(' + params.toString() + ')' - } - - - _flatNodeList(node: any, contractName: string, fileName: string, compilatioResult: any) { - const index = {} - const callback = (node) => { - node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) - node.functionName = node.name + this._getInputParams(node) - index[node.id] = node - } - this.astWalker.walkFull(node, callback) - return index - } - - _extractFileNodes(fileName: string, compilatioResult: any) { - const source = compilatioResult.data.sources[fileName] - const nodesByContract = [] - this.astWalker.walkFull(source.ast, (node) => { - if (node.nodeType === 'ContractDefinition') { - const flatNodes = this._flatNodeList(node, node.name, fileName, compilatioResult) - node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilatioResult) - nodesByContract[node.name] = { contractDefinition: node, contractNodes: flatNodes } - } - }) - return nodesByContract - } - - _getContractGasEstimate(node: any, contractName: string, fileName: string, compilationResult: any) { - - const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] - for (const name in contracts) { - if (name === contractName) { - const contract = contracts[name] - const estimationObj = contract.evm && contract.evm.gasEstimates - if (node.nodeType === 'ContractDefinition') { - return { - creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, - codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, - } - } - let executionCost = null - if (node.nodeType === 'FunctionDefinition') { - const visibility = node.visibility - if (node.kind !== 'constructor') { - const fnName = node.name - const fn = fnName + this._getInputParams(node) - - if (visibility === 'public' || visibility === 'external') { - executionCost = estimationObj === null ? '-' : estimationObj.external[fn] - } else if (visibility === 'private' || visibility === 'internal') { - executionCost = estimationObj === null ? '-' : estimationObj.internal[fn] - } - return { executionCost } - } - } - } - } - } - - - - - /** - * Returns the block surrounding the given position - * For example if the position is in the middle of a function, it will return the function - * @param {position} position - * @param {string} text // optional - * @return {any} - * */ - async getBlockAtPosition(position: any, text: string = null) { - await this.getCurrentFileAST(text) - const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] - - const walkAst = (node) => { - console.log(node) - if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) { - const children = node.children || node.subNodes - if (children && allowedTypes.indexOf(node.type) !== -1) { - for (const child of children) { - const result = walkAst(child) - if (result) return result - } - } - return node - } - return null - } - if (!this.currentFileAST) return - return walkAst(this.currentFileAST) - } - - /** - * Lists the AST nodes from the current file parser - * These nodes need to be changed to match the node types returned by the compiler - * @returns - */ - async listAstNodes() { - await this.getCurrentFileAST(); - const nodes = []; - (SolidityParser as any).visit(this.currentFileAST, { - StateVariableDeclaration: (node) => { - if (node.variables) { - for (const variable of node.variables) { - nodes.push({ ...variable, nodeType: 'VariableDeclaration' }) - } - } - }, - VariableDeclaration: (node) => { - nodes.push({ ...node, nodeType: node.type }) - }, - UserDefinedTypeName: (node) => { - nodes.push({ ...node, nodeType: node.type }) - }, - FunctionDefinition: (node) => { - nodes.push({ ...node, nodeType: node.type }) - }, - ContractDefinition: (node) => { - nodes.push({ ...node, nodeType: node.type }) - }, - MemberAccess: function (node) { - nodes.push({ ...node, nodeType: node.type }) - }, - Identifier: function (node) { - nodes.push({ ...node, nodeType: node.type }) - }, - EventDefinition: function (node) { - nodes.push({ ...node, nodeType: node.type }) - }, - ModifierDefinition: function (node) { - nodes.push({ ...node, nodeType: node.type }) - }, - InvalidNode: function (node) { - nodes.push({ ...node, nodeType: node.type }) - } - }) - console.log("LIST NODES", nodes) - return nodes - } - - /** - * Nodes at position where position is a number, offset - * @param position - * @param type - * @returns - */ - async nodesAtPosition(position: number, type = '') { - const lastCompilationResult = this.lastCompilationResult - if (!lastCompilationResult) return false - const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) - console.log('URL FROM PATH', urlFromPath) - if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { - const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) - return nodes - } - return [] - } - - /** - * - * @param id - * @returns - */ - async getNodeById(id: any) { - for (const key in this._index.FlatReferences) { - if (this._index.FlatReferences[key].id === id) { - return this._index.FlatReferences[key] - } - } - } - - /** - * - * @param id - * @returns - */ - async getDeclaration(id: any) { - if (this._index.Declarations && this._index.Declarations[id]) return this._index.Declarations[id] - } - - /** - * - * @param scope - * @returns - */ - async getNodesWithScope(scope: number) { - const nodes = [] - for (const node of Object.values(this._index.FlatReferences) as any[]) { - if (node.scope === scope) nodes.push(node) - } - return nodes - } - - /** - * - * @param name - * @returns - */ - async getNodesWithName(name: string) { - const nodes = [] - for (const node of Object.values(this._index.FlatReferences) as any[]) { - if (node.name === name) nodes.push(node) - } - return nodes - } - /** - * - * @param node - * @returns - */ - declarationOf(node: AstNode) { - if (node && node.referencedDeclaration) { - return this._index.FlatReferences[node.referencedDeclaration] - } else { - // console.log(this._index.FlatReferences) - } - return null - } - - /** - * - * @param position - * @returns - */ - async definitionAtPosition(position: number) { - const nodes = await this.nodesAtPosition(position) - console.log('nodes at position', nodes, position) - console.log(this._index.FlatReferences) - let nodeDefinition: any - let node: any - if (nodes && nodes.length && !this.errorState) { - node = nodes[nodes.length - 1] - nodeDefinition = node - if (!isNodeDefinition(node)) { - nodeDefinition = await this.declarationOf(node) || node - } - if (node.nodeType === 'ImportDirective') { - for (const key in this._index.FlatReferences) { - if (this._index.FlatReferences[key].id === node.sourceUnit) { - nodeDefinition = this._index.FlatReferences[key] - } - } - } - return nodeDefinition - } else { - const astNodes = await this.listAstNodes() - for (const node of astNodes) { - if (node.range[0] <= position && node.range[1] >= position) { - if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) { - nodeDefinition = node - } - if (!nodeDefinition) nodeDefinition = node - } - } - if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { - const nodeForIdentifier = await this.findIdentifier(nodeDefinition) - if (nodeForIdentifier) nodeDefinition = nodeForIdentifier - } - return nodeDefinition - } - - } - - /** - * - * @param identifierNode - * @returns - */ - async findIdentifier(identifierNode: any) { - const astNodes = await this.listAstNodes() - for (const node of astNodes) { - if (node.name === identifierNode.name && node.nodeType !== 'Identifier') { - return node - } - } - } - - /** - * - * @param node - * @returns - */ - async positionOfDefinition(node: any): Promise { - if (node) { - if (node.src) { - console.log('positionOfDefinition', node) - const position = sourceMappingDecoder.decode(node.src) - if (position) { - return position - } - } - } - return null - } - - /** - * - * @param node - * @param imported - * @returns - */ - async resolveImports(node, imported = {}) { - if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { - console.log('IMPORTING', node) - const importNode = await this.getNodeById(node.sourceUnit) - imported[importNode.id] = importNode - if (importNode.nodes) { - for (const child of importNode.nodes) { - imported = await this.resolveImports(child, imported) - } - } - } - console.log(imported) - return imported - } - - /** - * - * @param ast - * @returns - */ - async getLastNodeInLine(ast: string) { - let lastNode - const checkLastNode = (node) => { - if (lastNode && lastNode.range && lastNode.range[1]) { - if (node.range[1] > lastNode.range[1]) { - lastNode = node - } - } else { - lastNode = node - } - } - - (SolidityParser as any).visit(ast, { - MemberAccess: function (node) { - checkLastNode(node) - }, - Identifier: function (node) { - checkLastNode(node) - } - }) - if (lastNode && lastNode.expression && lastNode.expression.expression) { - console.log('lastNode with expression', lastNode, lastNode.expression) - return lastNode.expression.expression - } - if (lastNode && lastNode.expression) { - console.log('lastNode with expression', lastNode, lastNode.expression) - return lastNode.expression - } - console.log('lastNode', lastNode) - return lastNode - } - - /** - * - * @param node - * @returns - */ - referencesOf(node: any) { - const results = [] - const highlights = (id) => { - if (this._index.Declarations && this._index.Declarations[id]) { - const refs = this._index.Declarations[id] - for (const ref in refs) { - const node = refs[ref] - results.push(node) - } - } - } - if (node && node.referencedDeclaration) { - highlights(node.referencedDeclaration) - const current = this._index.FlatReferences[node.referencedDeclaration] - results.push(current) - } else { - highlights(node.id) - } - return results - } - - /** - * - * @param position - * @returns - */ - async referrencesAtPosition(position: any) { - const nodes = await this.nodesAtPosition(position) - if (nodes && nodes.length) { - const node = nodes[nodes.length - 1] - if (node) { - return this.referencesOf(node) - } - } - } - - /** - * - * @returns - */ - async getNodes() { - return this._index.FlatReferences - } - - - - /** - * - * @param node - * @returns - */ - async getNodeLink(node: any) { - const lineColumn = await this.getNodeLineColumn(node) - return lineColumn ? `${lineColumn.fileName} ${lineColumn.position.start.line}:${lineColumn.position.start.column}` : null - } - - /* - * @param node - */ - async getNodeLineColumn(node: any) { - const position = await this.positionOfDefinition(node) - if (position) { - const fileName = this.lastCompilationResult.getSourceName(position.file) - const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.lastCompilationResult.source.sources[fileName].content) - const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) - return { - fileName, - position: lineColumn - } - } - } - - /** - * - * @param node - * @returns - */ - async getNodeDocumentation(node: any) { - if (node.documentation && node.documentation.text) { - let text = '' - node.documentation.text.split('\n').forEach(line => { - text += `${line.trim()}\n` - }) - return text - } - } - - /** - * - * @param node - * @returns - */ - async getVariableDeclaration(node: any) { - if (node.typeDescriptions && node.typeDescriptions.typeString) { - return `${node.typeDescriptions.typeString} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` - } else { - if (node.typeName && node.typeName.name) { - return `${node.typeName.name} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` - } - else if (node.typeName && node.typeName.namePath) { - return `${node.typeName.namePath} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` - } - else { - return `${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` - } - } - } - - /** - * - * @param node - * @returns - */ - async getFunctionParamaters(node: any) { - const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) - if (localParam) { - const params = [] - for (const param of localParam) { - params.push(await this.getVariableDeclaration(param)) - } - return `(${params.join(', ')})` - } - } - - /** - * - * @param node - * @returns - */ - async getFunctionReturnParameters(node: any) { - const localParam = (node.returnParameters && node.returnParameters.parameters) - if (localParam) { - const params = [] - for (const param of localParam) { - params.push(await this.getVariableDeclaration(param)) - } - return `(${params.join(', ')})` - } - } - - /** - * - * @param fileName - */ - async getGasEstimates(fileName: string) { - if (!fileName) { - fileName = await this.currentFile - } - if (this._index.NodesPerFile && this._index.NodesPerFile[fileName]) { - const estimates = [] - for (const contract in this._index.NodesPerFile[fileName]) { - console.log(contract) - const nodes = this._index.NodesPerFile[fileName][contract].contractNodes - for (const node of Object.values(nodes) as any[]) { - if (node.gasEstimate) { - estimates.push({ - node, - range: await this.getNodeLineColumn(node) - }) - } - } - } - return estimates - } - - } - - - async setGasEstimates() { - this.currentFile = await this.call('fileManager', 'file') - this._index.NodesPerFile[this.currentFile] = await this._extractFileNodes(this.currentFile, this.lastCompilationResult) - - const gasEstimates = await this.getGasEstimates(this.currentFile) - console.log('all estimates', gasEstimates) - - const friendlyNames = { - 'executionCost': 'Estimated execution cost', - 'codeDepositCost': 'Estimated code deposit cost', - 'creationCost': 'Estimated creation cost', - } - await this.call('editor', 'discardLineTexts') - if (gasEstimates) { - for (const estimate of gasEstimates) { - console.log(estimate) - const linetext: lineText = { - content: Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${value} gas`).join(' '), - position: estimate.range.position, - hide: false, - className: 'text-muted small', - afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', - from: 'codeParser', - hoverMessage: [{ - value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`, - }, - ], - } - - this.call('editor', 'addLineText', linetext, estimate.range.fileName) - - - } - } - } - - -} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 6626bf87cc..f0287205f4 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -49,7 +49,6 @@ export class FileDecorator extends Plugin { if (!deepequal(newState, this._fileStates)) { this._fileStates = newState - console.log('fileStates', this._fileStates) this.emit('fileDecoratorsChanged', this._fileStates) } } diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx new file mode 100644 index 0000000000..c45af8cd5d --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -0,0 +1,564 @@ +'use strict' +import { Plugin } from '@remixproject/engine' +import { sourceMappingDecoder } from '@remix-project/remix-debug' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import { Compiler } from '@remix-project/remix-solidity' + +import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' +import { helper } from '@remix-project/remix-solidity' +import CodeParserGasService from './services/code-parser-gas-service' +import CodeParserCompiler from './services/code-parser-compiler' +import CodeParserAntlrService from './services/code-parser-antlr-service' +import CodeParserNodeHelper from './services/code-parser-node-helper' +import React from 'react' +import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' + +import { Profile } from '@remixproject/plugin-utils' +// eslint-disable-next-line + + + + +const profile: Profile = { + name: 'codeParser', + methods: ['nodesAtPosition', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], + events: [], + version: '0.0.1' +} + +export function isNodeDefinition(node: any) { + return node.nodeType === 'ContractDefinition' || + node.nodeType === 'FunctionDefinition' || + node.nodeType === 'ModifierDefinition' || + node.nodeType === 'VariableDeclaration' || + node.nodeType === 'StructDefinition' || + node.nodeType === 'EventDefinition' +} + +export class CodeParser extends Plugin { + + currentFileAST: any // contains the simple parsed AST for the current file + + lastCompilationResult: any + currentFile: any + _index: any + astWalker: any + errorState: boolean = false + + gastEstimateTimeOut: any + + gasService: CodeParserGasService + compilerService: CodeParserCompiler + antlrService: CodeParserAntlrService + nodeHelper: CodeParserNodeHelper + + parseSolidity: (text: string) => Promise + getLastNodeInLine: (ast: string) => Promise + listAstNodes: () => Promise + getBlockAtPosition: (position: any, text?: string) => Promise + + constructor(astWalker) { + super(profile) + this.astWalker = astWalker + this._index = { + Declarations: {}, + FlatReferences: {} + } + } + + async onActivation() { + + this.gasService = new CodeParserGasService(this) + this.compilerService = new CodeParserCompiler(this) + this.antlrService = new CodeParserAntlrService(this) + this.nodeHelper = new CodeParserNodeHelper(this) + + this.parseSolidity = this.antlrService.parseSolidity + this.getLastNodeInLine = this.antlrService.getLastNodeInLine + this.listAstNodes = this.antlrService.listAstNodes + this.getBlockAtPosition = this.antlrService.getBlockAtPosition + + this.on('editor', 'didChangeFile', async (file) => { + console.log('contentChanged', file) + await this.call('editor', 'discardLineTexts') + await this.antlrService.getCurrentFileAST() + await this.compilerService.compile() + }) + + this.on('filePanel', 'setWorkspace', async () => { + await this.call('fileDecorator', 'setFileDecorators', []) + }) + + + this.on('fileManager', 'currentFileChanged', async () => { + await this.call('editor', 'discardLineTexts') + await this.antlrService.getCurrentFileAST() + await this.compilerService.compile() + }) + + this.on('solidity', 'loadingCompiler', async (url) => { + console.log('loading compiler', url) + this.compilerService.compiler.loadVersion(true, url) + this.compilerService.compiler.event.register('compilerLoaded', async () => { + console.log('compiler loaded') + }) + }) + + await this.compilerService.init() + + } + + + + /** + * + * @returns + */ + async getLastCompilationResult() { + return this.lastCompilationResult + } + + + + + + /** + * Builds a flat index and declarations of all the nodes in the compilation result + * @param compilationResult + * @param source + */ + _buildIndex(compilationResult, source) { + if (compilationResult && compilationResult.sources) { + const callback = (node) => { + if (node && node.referencedDeclaration) { + if (!this._index.Declarations[node.referencedDeclaration]) { + this._index.Declarations[node.referencedDeclaration] = [] + } + this._index.Declarations[node.referencedDeclaration].push(node) + } + this._index.FlatReferences[node.id] = node + } + for (const s in compilationResult.sources) { + this.astWalker.walkFull(compilationResult.sources[s].ast, callback) + } + + } + } + + // NODE HELPERS + + _getInputParams(node) { + const params = [] + const target = node.parameters + if (target) { + const children = target.parameters + for (const j in children) { + if (children[j].nodeType === 'VariableDeclaration') { + params.push(children[j].typeDescriptions.typeString) + } + } + } + return '(' + params.toString() + ')' + } + + + _flatNodeList(node: any, contractName: string, fileName: string, compilatioResult: any) { + const index = {} + const callback = (node) => { + node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) + node.functionName = node.name + this._getInputParams(node) + index[node.id] = node + } + this.astWalker.walkFull(node, callback) + return index + } + + _extractFileNodes(fileName: string, compilatioResult: any) { + if (compilatioResult && compilatioResult.data.sources && compilatioResult.data.sources[fileName]) { + const source = compilatioResult.data.sources[fileName] + const nodesByContract = [] + this.astWalker.walkFull(source.ast, (node) => { + if (node.nodeType === 'ContractDefinition') { + const flatNodes = this._flatNodeList(node, node.name, fileName, compilatioResult) + node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilatioResult) + nodesByContract[node.name] = { contractDefinition: node, contractNodes: flatNodes } + } + }) + return nodesByContract + } + } + + _getContractGasEstimate(node: any, contractName: string, fileName: string, compilationResult: any) { + + const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile] + for (const name in contracts) { + if (name === contractName) { + const contract = contracts[name] + const estimationObj = contract.evm && contract.evm.gasEstimates + if (node.nodeType === 'ContractDefinition') { + return { + creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost, + codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost, + } + } + let executionCost = null + if (node.nodeType === 'FunctionDefinition') { + const visibility = node.visibility + if (node.kind !== 'constructor') { + const fnName = node.name + const fn = fnName + this._getInputParams(node) + + if (visibility === 'public' || visibility === 'external') { + executionCost = estimationObj === null ? '-' : estimationObj.external[fn] + } else if (visibility === 'private' || visibility === 'internal') { + executionCost = estimationObj === null ? '-' : estimationObj.internal[fn] + } + return { executionCost } + } + } + } + } + } + + + + + + + + + /** + * Nodes at position where position is a number, offset + * @param position + * @param type + * @returns + */ + async nodesAtPosition(position: number, type = '') { + const lastCompilationResult = this.lastCompilationResult + if (!lastCompilationResult) return false + const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) + console.log('URL FROM PATH', urlFromPath) + if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) { + const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) + return nodes + } + return [] + } + + /** + * + * @param id + * @returns + */ + async getNodeById(id: any) { + for (const key in this._index.FlatReferences) { + if (this._index.FlatReferences[key].id === id) { + return this._index.FlatReferences[key] + } + } + } + + /** + * + * @param id + * @returns + */ + async getDeclaration(id: any) { + if (this._index.Declarations && this._index.Declarations[id]) return this._index.Declarations[id] + } + + /** + * + * @param scope + * @returns + */ + async getNodesWithScope(scope: number) { + const nodes = [] + for (const node of Object.values(this._index.FlatReferences) as any[]) { + if (node.scope === scope) nodes.push(node) + } + return nodes + } + + /** + * + * @param name + * @returns + */ + async getNodesWithName(name: string) { + const nodes = [] + for (const node of Object.values(this._index.FlatReferences) as any[]) { + if (node.name === name) nodes.push(node) + } + return nodes + } + /** + * + * @param node + * @returns + */ + declarationOf(node: AstNode) { + if (node && node.referencedDeclaration) { + return this._index.FlatReferences[node.referencedDeclaration] + } else { + // console.log(this._index.FlatReferences) + } + return null + } + + /** + * + * @param position + * @returns + */ + async definitionAtPosition(position: number) { + const nodes = await this.nodesAtPosition(position) + console.log('nodes at position', nodes, position) + console.log(this._index.FlatReferences) + let nodeDefinition: any + let node: any + if (nodes && nodes.length && !this.errorState) { + node = nodes[nodes.length - 1] + nodeDefinition = node + if (!isNodeDefinition(node)) { + nodeDefinition = await this.declarationOf(node) || node + } + if (node.nodeType === 'ImportDirective') { + for (const key in this._index.FlatReferences) { + if (this._index.FlatReferences[key].id === node.sourceUnit) { + nodeDefinition = this._index.FlatReferences[key] + } + } + } + return nodeDefinition + } else { + const astNodes = await this.antlrService.listAstNodes() + for (const node of astNodes) { + if (node.range[0] <= position && node.range[1] >= position) { + if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) { + nodeDefinition = node + } + if (!nodeDefinition) nodeDefinition = node + } + } + if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { + const nodeForIdentifier = await this.findIdentifier(nodeDefinition) + if (nodeForIdentifier) nodeDefinition = nodeForIdentifier + } + return nodeDefinition + } + + } + + /** + * + * @param identifierNode + * @returns + */ + async findIdentifier(identifierNode: any) { + const astNodes = await this.antlrService.listAstNodes() + for (const node of astNodes) { + if (node.name === identifierNode.name && node.nodeType !== 'Identifier') { + return node + } + } + } + + /** + * + * @param node + * @returns + */ + async positionOfDefinition(node: any): Promise { + if (node) { + if (node.src) { + console.log('positionOfDefinition', node) + const position = sourceMappingDecoder.decode(node.src) + if (position) { + return position + } + } + } + return null + } + + /** + * + * @param node + * @param imported + * @returns + */ + async resolveImports(node, imported = {}) { + if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) { + console.log('IMPORTING', node) + const importNode = await this.getNodeById(node.sourceUnit) + imported[importNode.id] = importNode + if (importNode.nodes) { + for (const child of importNode.nodes) { + imported = await this.resolveImports(child, imported) + } + } + } + console.log(imported) + return imported + } + + + + /** + * + * @param node + * @returns + */ + referencesOf(node: any) { + const results = [] + const highlights = (id) => { + if (this._index.Declarations && this._index.Declarations[id]) { + const refs = this._index.Declarations[id] + for (const ref in refs) { + const node = refs[ref] + results.push(node) + } + } + } + if (node && node.referencedDeclaration) { + highlights(node.referencedDeclaration) + const current = this._index.FlatReferences[node.referencedDeclaration] + results.push(current) + } else { + highlights(node.id) + } + return results + } + + /** + * + * @param position + * @returns + */ + async referrencesAtPosition(position: any) { + const nodes = await this.nodesAtPosition(position) + if (nodes && nodes.length) { + const node = nodes[nodes.length - 1] + if (node) { + return this.referencesOf(node) + } + } + } + + /** + * + * @returns + */ + async getNodes() { + return this._index.FlatReferences + } + + + + /** + * + * @param node + * @returns + */ + async getNodeLink(node: any) { + const lineColumn = await this.getLineColumnOfNode(node) + const position = await this.positionOfDefinition(node) + if (this.lastCompilationResult && this.lastCompilationResult.sources) { + const fileName = this.lastCompilationResult.getSourceName(position.file) + return lineColumn ? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}` : null + } + return '' + } + + /* + * @param node + */ + async getLineColumnOfNode(node: any) { + const position = await this.positionOfDefinition(node) + return this.getLineColumnOfPosition(position) + } + + /* + * @param position + */ + async getLineColumnOfPosition(position: any) { + if (position) { + const fileName = this.lastCompilationResult.getSourceName(position.file) + const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.lastCompilationResult.source.sources[fileName].content) + const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks) + return lineColumn + } + } + + /** + * + * @param node + * @returns + */ + async getNodeDocumentation(node: any) { + if (node.documentation && node.documentation.text) { + let text = '' + node.documentation.text.split('\n').forEach(line => { + text += `${line.trim()}\n` + }) + return text + } + } + + /** + * + * @param node + * @returns + */ + async getVariableDeclaration(node: any) { + if (node.typeDescriptions && node.typeDescriptions.typeString) { + return `${node.typeDescriptions.typeString} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` + } else { + if (node.typeName && node.typeName.name) { + return `${node.typeName.name} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` + } + else if (node.typeName && node.typeName.namePath) { + return `${node.typeName.namePath} ${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` + } + else { + return `${node.visibility}${node.name && node.name.length ? ` ${node.name}` : ''}` + } + } + } + + /** + * + * @param node + * @returns + */ + async getFunctionParamaters(node: any) { + const localParam = (node.parameters && node.parameters.parameters) || (node.parameters) + if (localParam) { + const params = [] + for (const param of localParam) { + params.push(await this.getVariableDeclaration(param)) + } + return `(${params.join(', ')})` + } + } + + /** + * + * @param node + * @returns + */ + async getFunctionReturnParameters(node: any) { + const localParam = (node.returnParameters && node.returnParameters.parameters) + if (localParam) { + const params = [] + for (const param of localParam) { + params.push(await this.getVariableDeclaration(param)) + } + return `(${params.join(', ')})` + } + } + + + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts new file mode 100644 index 0000000000..e84d5a915e --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts @@ -0,0 +1,160 @@ +'use strict' + +import { CodeParser } from "../code-parser" + +const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || [] + +export default class CodeParserAntlrService { + plugin: CodeParser + constructor(plugin: CodeParser) { + this.plugin = plugin + } + + /* + * simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports + */ + + async parseSolidity(text: string) { + const t0 = performance.now(); + const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true }) + const t1 = performance.now(); + console.log(`Call to doSomething took ${t1 - t0} milliseconds.`); + console.log('AST PARSE SUCCESS', ast) + return ast + } + + /** + * Tries to parse the current file or the given text and returns the AST + * If the parsing fails it returns the last successful AST for this file + * @param text + * @returns + */ + async getCurrentFileAST(text: string | null = null) { + this.plugin.currentFile = await this.plugin.call('fileManager', 'file') + if (!this.plugin.currentFile) return + const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) + try { + const ast = await this.parseSolidity(fileContent) + this.plugin.currentFileAST = ast + } catch (e) { + console.log(e) + } + return this.plugin.currentFileAST + } + + /** + * Lists the AST nodes from the current file parser + * These nodes need to be changed to match the node types returned by the compiler + * @returns + */ + async listAstNodes() { + await this.getCurrentFileAST(); + const nodes: any = []; + (SolidityParser as any).visit(this.plugin.currentFileAST, { + StateVariableDeclaration: (node) => { + if (node.variables) { + for (const variable of node.variables) { + nodes.push({ ...variable, nodeType: 'VariableDeclaration' }) + } + } + }, + VariableDeclaration: (node) => { + nodes.push({ ...node, nodeType: node.type }) + }, + UserDefinedTypeName: (node) => { + nodes.push({ ...node, nodeType: node.type }) + }, + FunctionDefinition: (node) => { + nodes.push({ ...node, nodeType: node.type }) + }, + ContractDefinition: (node) => { + nodes.push({ ...node, nodeType: node.type }) + }, + MemberAccess: function (node) { + nodes.push({ ...node, nodeType: node.type }) + }, + Identifier: function (node) { + nodes.push({ ...node, nodeType: node.type }) + }, + EventDefinition: function (node) { + nodes.push({ ...node, nodeType: node.type }) + }, + ModifierDefinition: function (node) { + nodes.push({ ...node, nodeType: node.type }) + }, + InvalidNode: function (node) { + nodes.push({ ...node, nodeType: node.type }) + } + }) + console.log("LIST NODES", nodes) + return nodes + } + + + /** + * + * @param ast + * @returns + */ + async getLastNodeInLine(ast: string) { + let lastNode + const checkLastNode = (node) => { + if (lastNode && lastNode.range && lastNode.range[1]) { + if (node.range[1] > lastNode.range[1]) { + lastNode = node + } + } else { + lastNode = node + } + } + + (SolidityParser as any).visit(ast, { + MemberAccess: function (node) { + checkLastNode(node) + }, + Identifier: function (node) { + checkLastNode(node) + } + }) + if (lastNode && lastNode.expression && lastNode.expression.expression) { + console.log('lastNode with expression', lastNode, lastNode.expression) + return lastNode.expression.expression + } + if (lastNode && lastNode.expression) { + console.log('lastNode with expression', lastNode, lastNode.expression) + return lastNode.expression + } + console.log('lastNode', lastNode) + return lastNode + } + + /** + * Returns the block surrounding the given position + * For example if the position is in the middle of a function, it will return the function + * @param {position} position + * @param {string} text // optional + * @return {any} + * */ + async getBlockAtPosition(position: any, text: string = null) { + await this.getCurrentFileAST(text) + const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] + + const walkAst = (node) => { + console.log(node) + if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) { + const children = node.children || node.subNodes + if (children && allowedTypes.indexOf(node.type) !== -1) { + for (const child of children) { + const result = walkAst(child) + if (result) return result + } + } + return node + } + return null + } + if (!this.plugin.currentFileAST) return + return walkAst(this.plugin.currentFileAST) + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts new file mode 100644 index 0000000000..2c488348a9 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts @@ -0,0 +1,198 @@ +'use strict' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import { Compiler } from '@remix-project/remix-solidity' + +import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' +import { helper } from '@remix-project/remix-solidity' +import { CodeParser } from "../code-parser"; +import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' + +export default class CodeParserCompiler { + plugin: CodeParser + compiler: any // used to compile the current file seperately from the main compiler + onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise; + errorState: boolean; + gastEstimateTimeOut: any + constructor( + plugin: CodeParser + ) { + this.plugin = plugin + } + + init() { + + this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => { + console.log('compile success', success, data, this) + this.plugin.call('editor', 'clearAnnotations') + this.errorState = true + const checkIfFatalError = (error: CompilationError) => { + // Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround + const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning' + if (isValidError) { + console.log(error) + } + } + const result = new CompilerAbstract('soljson', data, source, input) + + if (data.error) checkIfFatalError(data.error) + if (data.errors) data.errors.forEach((err) => checkIfFatalError(err)) + const allErrors: any = [] + if (data.errors) { + const sources = result.getSourceCode().sources + for (const error of data.errors) { + const pos = helper.getPositionDetails(error.formattedMessage) + const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) + const lineColumn = await this.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', + { + start: error.sourceLocation.start, + length: error.sourceLocation.end - error.sourceLocation.start + }, + filePosition, + result.getSourceCode().sources, + null) + allErrors.push({ error, lineColumn }) + } + console.log('allErrors', allErrors) + await this.plugin.call('editor', 'addErrorMarker', allErrors) + + const errorsPerFiles = {} + for (const error of allErrors) { + if (!errorsPerFiles[error.error.sourceLocation.file]) { + errorsPerFiles[error.error.sourceLocation.file] = [] + } + errorsPerFiles[error.error.sourceLocation.file].push(error.error) + } + + const errorPriority = { + 'error': 0, + 'warning': 1, + } + + // sort errorPerFiles by error priority + const sortedErrorsPerFiles = {} + for (const fileName in errorsPerFiles) { + const errors = errorsPerFiles[fileName] + errors.sort((a, b) => { + return errorPriority[a.severity] - errorPriority[b.severity] + } + ) + sortedErrorsPerFiles[fileName] = errors + } + console.log('sortedErrorsPerFiles', sortedErrorsPerFiles) + + const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName]) + + console.log('filesWithOutErrors', filesWithOutErrors) + // add decorators + const decorators: fileDecoration[] = [] + for (const fileName in sortedErrorsPerFiles) { + const errors = sortedErrorsPerFiles[fileName] + const decorator: fileDecoration = { + path: fileName, + isDirectory: false, + fileStateType: errors[0].severity === 'error' ? fileDecorationType.Error : fileDecorationType.Warning, + fileStateLabelClass: errors[0].severity === 'error' ? 'text-danger' : 'text-warning', + fileStateIconClass: '', + fileStateIcon: '', + text: errors.length, + owner: 'code-parser', + bubble: true, + commment: errors.map((error) => error.message), + } + decorators.push(decorator) + } + for (const fileName of filesWithOutErrors) { + const decorator: fileDecoration = { + path: fileName, + isDirectory: false, + fileStateType: fileDecorationType.None, + fileStateLabelClass: '', + fileStateIconClass: '', + fileStateIcon: '', + text: '', + owner: 'code-parser', + bubble: false + } + decorators.push(decorator) + } + console.log(decorators) + await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) + await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors) + } else { + await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) + const decorators: fileDecoration[] = [] + for (const fileName of Object.keys(result.getSourceCode().sources)) { + const decorator: fileDecoration = { + path: fileName, + isDirectory: false, + fileStateType: fileDecorationType.None, + fileStateLabelClass: '', + fileStateIconClass: '', + fileStateIcon: '', + text: '', + owner: 'code-parser', + bubble: false + } + decorators.push(decorator) + } + console.log(decorators) + + await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) + + } + + + if (!data.sources) return + if (data.sources && Object.keys(data.sources).length === 0) return + this.plugin.lastCompilationResult = new CompilerAbstract('soljson', data, source, input) + + this.errorState = false + this.plugin._index = { + Declarations: {}, + FlatReferences: {}, + NodesPerFile: {}, + } + this.plugin._buildIndex(data, source) + + if (this.gastEstimateTimeOut) { + window.clearTimeout(this.gastEstimateTimeOut) + } + + this.gastEstimateTimeOut = window.setTimeout(async () => { + await this.plugin.gasService.showGasEstimates() + }, 1000) + + console.log("INDEX", this.plugin._index) + this.plugin.emit('astFinished') + } + + this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined, true).then((result) => cb(null, result)).catch((error) => cb(error.message))) + this.compiler.event.register('compilationFinished', this.onAstFinished) + } + + // COMPILER + + /** + * + * @returns + */ + async compile() { + try { + const state = await this.plugin.call('solidity', 'getCompilerState') + this.compiler.set('optimize', state.optimize) + this.compiler.set('evmVersion', state.evmVersion) + this.compiler.set('language', state.language) + this.compiler.set('runs', state.runs) + this.compiler.set('useFileConfiguration', state.useFileConfiguration) + this.plugin.currentFile = await this.plugin.call('fileManager', 'file') + console.log(this.plugin.currentFile) + if (!this.plugin.currentFile) return + const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile) + const sources = { [this.plugin.currentFile]: { content } } + this.compiler.compile(sources, this.plugin.currentFile) + } catch (e) { + console.log(e) + } + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts new file mode 100644 index 0000000000..404a27ae87 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts @@ -0,0 +1,72 @@ +import { CodeParser } from "../code-parser"; +import { lineText } from '@remix-ui/editor' + +export default class CodeParserGasService { + plugin: CodeParser + + constructor(plugin: CodeParser) { + this.plugin = plugin + } + + async getGasEstimates(fileName: string) { + if (!fileName) { + fileName = await this.plugin.currentFile + } + if (this.plugin._index.NodesPerFile && this.plugin._index.NodesPerFile[fileName]) { + const estimates: any = [] + for (const contract in this.plugin._index.NodesPerFile[fileName]) { + console.log(contract) + const nodes = this.plugin._index.NodesPerFile[fileName][contract].contractNodes + for (const node of Object.values(nodes) as any[]) { + if (node.gasEstimate) { + estimates.push({ + node, + range: await this.plugin.getLineColumnOfNode(node) + }) + } + } + } + return estimates + } + + } + + + async showGasEstimates() { + this.plugin.currentFile = await this.plugin.call('fileManager', 'file') + this.plugin._index.NodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.lastCompilationResult) + + const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) + console.log('all estimates', gasEstimates) + + const friendlyNames = { + 'executionCost': 'Estimated execution cost', + 'codeDepositCost': 'Estimated code deposit cost', + 'creationCost': 'Estimated creation cost', + } + await this.plugin.call('editor', 'discardLineTexts') + if (gasEstimates) { + for (const estimate of gasEstimates) { + console.log(estimate) + const linetext: lineText = { + content: Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${value} gas`).join(' '), + position: estimate.range, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + from: 'codeParser', + hoverMessage: [{ + value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`, + }, + ], + } + + this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName) + + + } + } + } + + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-node-helper.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-node-helper.ts new file mode 100644 index 0000000000..e818a28a99 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-node-helper.ts @@ -0,0 +1,8 @@ +import { CodeParser } from "../code-parser" + +export default class CodeParserNodeHelper { + plugin: CodeParser + constructor(plugin: CodeParser) { + this.plugin = plugin + } +} \ No newline at end of file diff --git a/libs/remix-core-plugin/src/lib/compiler-content-imports.ts b/libs/remix-core-plugin/src/lib/compiler-content-imports.ts index 88b170e870..30cb4a3d2a 100644 --- a/libs/remix-core-plugin/src/lib/compiler-content-imports.ts +++ b/libs/remix-core-plugin/src/lib/compiler-content-imports.ts @@ -125,7 +125,6 @@ export class CompilerImports extends Plugin { * @returns {Promise} - string content */ async resolveAndSave(url: string, targetPath: string, save: boolean = true) { - console.log(url, targetPath, save) try { if (targetPath && this.currentRequest) { const canCall = await this.askUserPermission('resolveAndSave', 'This action will update the path ' + targetPath) diff --git a/libs/remix-core-plugin/src/lib/editor-context-listener.ts b/libs/remix-core-plugin/src/lib/editor-context-listener.ts index 6d7a750a83..a1ff1d833c 100644 --- a/libs/remix-core-plugin/src/lib/editor-context-listener.ts +++ b/libs/remix-core-plugin/src/lib/editor-context-listener.ts @@ -69,47 +69,8 @@ export class EditorContextListener extends Plugin { return [...this._activeHighlights] } - async jumpToDefinition(position: any) { - const node = await this.call('codeParser', 'definitionAtPosition', position) - const sourcePosition = await this.call('codeParser', 'positionOfDefinition', node) - console.log("JUMP", sourcePosition) - if (sourcePosition) { - await this.jumpToPosition(sourcePosition) - } - } - - /* - * onClick jump to position of ast node in the editor - */ - async jumpToPosition(position: any) { - const jumpToLine = async (fileName: string, lineColumn: any) => { - if (fileName !== await this.call('fileManager', 'file')) { - console.log('jump to file', fileName) - await this.call('contentImport', 'resolveAndSave', fileName, null, true) - await this.call('fileManager', 'open', fileName) - } - if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { - this.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1) - } - } - const lastCompilationResult = await this.call('codeParser', 'getLastCompilationResult') // await this.call('compilerArtefacts', 'getLastCompilationResult') - console.log(lastCompilationResult.getSourceCode().sources) - console.log(position) - if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { - const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', - position, - position.file, - lastCompilationResult.getSourceCode().sources, - lastCompilationResult.getAsts()) - const filename = lastCompilationResult.getSourceName(position.file) - // TODO: refactor with rendererAPI.errorClick - console.log(filename, lineColumn) - jumpToLine(filename, lineColumn) - } - } - async _highlightItems(cursorPosition, compilationResult, file) { if (this.currentPosition === cursorPosition) return this._stopHighlighting() diff --git a/libs/remix-debug/src/source/sourceMappingDecoder.ts b/libs/remix-debug/src/source/sourceMappingDecoder.ts index bbffe9d23c..04b3bcb9ba 100644 --- a/libs/remix-debug/src/source/sourceMappingDecoder.ts +++ b/libs/remix-debug/src/source/sourceMappingDecoder.ts @@ -68,7 +68,6 @@ export function getLinebreakPositions (source) { * @return {Object} returns an object {start: {line, column}, end: {line, column}} (line/column count start at 0) */ export function convertOffsetToLineColumn (sourceLocation, lineBreakPositions) { - console.log(sourceLocation, lineBreakPositions) if (sourceLocation.start >= 0 && sourceLocation.length >= 0) { return { start: convertFromCharPosition(sourceLocation.start, lineBreakPositions), diff --git a/libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts b/libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts deleted file mode 100644 index 99d41ee832..0000000000 --- a/libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { lineText } from "../remix-ui-editor" - -export class RemixCodeLensProvider { - props: any - monaco: any - constructor(props: any, monaco: any) { - this.props = props - this.monaco = monaco - } - - - async provideCodeLenses(model: any, token: any) { - - const gasEstimates = await this.props.plugin.call('codeParser', 'getGasEstimates') - const decorations = [] - const friendlyNames = { - 'executionCost': 'Execution Cost', - 'codeDepositCost': 'Code Deposit Cost', - 'creationCost': 'Creation Cost', - } - -/* if (gasEstimates) { - for (const estimate of gasEstimates) { - console.log(estimate) - const linetext: lineText = { - content: Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' '), - position: estimate.range.position, - hide: false, - className: 'remix-code-lens', - from: 'remix-code-lens', - } - - this.props.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName) - - - } - - } */ - - return { - lenses: [], - dispose: () => { } - }; - } - -} diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 69e7d4f178..5e9dc4497e 100644 --- a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts @@ -15,7 +15,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider } triggerCharacters = ['.', ''] - async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext) { + async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise { console.log('AUTOCOMPLETE', context) console.log(position) diff --git a/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts new file mode 100644 index 0000000000..d06149c7be --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts @@ -0,0 +1,57 @@ +import { Monaco } from "@monaco-editor/react" +import monaco from "../../types/monaco" +import { EditorUIProps } from "../remix-ui-editor" + +export class RemixDefinitionProvider implements monaco.languages.DefinitionProvider { + props: EditorUIProps + monaco: Monaco + constructor(props: any, monaco: any) { + this.props = props + this.monaco = monaco + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise { + const cursorPosition = this.props.editorAPI.getCursorPosition() + await this.jumpToDefinition(cursorPosition) + return null + } + + async jumpToDefinition(position: any) { + const node = await this.props.plugin.call('codeParser', 'definitionAtPosition', position) + const sourcePosition = await this.props.plugin.call('codeParser', 'positionOfDefinition', node) + console.log("JUMP", sourcePosition) + if (sourcePosition) { + await this.jumpToPosition(sourcePosition) + } + } + + + + /* + * onClick jump to position of ast node in the editor + */ + async jumpToPosition(position: any) { + const jumpToLine = async (fileName: string, lineColumn: any) => { + if (fileName !== await this.props.plugin.call('fileManager', 'file')) { + console.log('jump to file', fileName) + await this.props.plugin.call('contentImport', 'resolveAndSave', fileName, null, true) + await this.props.plugin.call('fileManager', 'open', fileName) + } + if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { + this.props.plugin.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1) + } + } + const lastCompilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') // await this.props.plugin.call('compilerArtefacts', 'getLastCompilationResult') + console.log(lastCompilationResult.getSourceCode().sources) + console.log(position) + if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { + + const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) + const filename = lastCompilationResult.getSourceName(position.file) + // TODO: refactor with rendererAPI.errorClick + console.log(filename, lineColumn) + jumpToLine(filename, lineColumn) + } + } +} \ No newline at end of file diff --git a/libs/remix-ui/editor/src/lib/providers/highlightProvider.ts b/libs/remix-ui/editor/src/lib/providers/highlightProvider.ts new file mode 100644 index 0000000000..7e630e45aa --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/highlightProvider.ts @@ -0,0 +1,45 @@ +import { Monaco } from "@monaco-editor/react" +import { sourceMappingDecoder } from "@remix-project/remix-debug" +import monaco from "../../types/monaco" +import { EditorUIProps } from "../remix-ui-editor" + +export class RemixHighLightProvider implements monaco.languages.DocumentHighlightProvider { + props: EditorUIProps + monaco: Monaco + constructor(props: any, monaco: any) { + this.props = props + this.monaco = monaco + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async provideDocumentHighlights(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise { + + console.log(model.uri) + const cursorPosition = this.props.editorAPI.getCursorPosition() + const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition) + //console.log('nodes for highlights', nodes) + const highlights: monaco.languages.DocumentHighlight[] = [] + if (nodes && nodes.length) { + const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult') + const file = await this.props.plugin.call('fileManager', 'file') + if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { + for (const node of nodes) { + const position = sourceMappingDecoder.decode(node.src) + const fileInNode = compilationResult.getSourceName(position.file) + console.log(fileInNode, file) + if (fileInNode === file) { + const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) + + const range = new this.monaco.Range(lineColumn.start.line + 1, lineColumn.start.column + 1, lineColumn.end.line + 1, lineColumn.end.column + 1) + + highlights.push({ + range, + }) + } + } + } + } + console.log('highlights', highlights) + return highlights + } +} \ No newline at end of file diff --git a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts index 838faedc26..d92baa96b1 100644 --- a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts @@ -1,16 +1,17 @@ +import { Monaco } from '@monaco-editor/react' import { editor, languages, Position } from 'monaco-editor' import { EditorUIProps } from '../remix-ui-editor' export class RemixHoverProvider implements languages.HoverProvider { props: EditorUIProps - monaco: any + monaco: Monaco constructor(props: any, monaco: any) { this.props = props this.monaco = monaco } - provideHover = async function (model: editor.ITextModel, position: Position) { + provideHover = async function (model: editor.ITextModel, position: Position): Promise { console.log('HOVERING') const cursorPosition = this.props.editorAPI.getHoverPosition(position) @@ -25,6 +26,7 @@ export class RemixHoverProvider implements languages.HoverProvider { }) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const getScope = async (node: any) => { if (node.id) { contents.push({ diff --git a/libs/remix-ui/editor/src/lib/providers/referenceProvider.ts b/libs/remix-ui/editor/src/lib/providers/referenceProvider.ts index fdd54d1421..36380c54a0 100644 --- a/libs/remix-ui/editor/src/lib/providers/referenceProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/referenceProvider.ts @@ -1,14 +1,18 @@ +import { Monaco } from "@monaco-editor/react" import { sourceMappingDecoder } from "@remix-project/remix-debug" +import monaco from "../../types/monaco" +import { EditorUIProps } from "../remix-ui-editor" -export class RemixReferenceProvider { - props: any - monaco: any +export class RemixReferenceProvider implements monaco.languages.ReferenceProvider { + props: EditorUIProps + monaco: Monaco constructor(props: any, monaco: any) { this.props = props this.monaco = monaco } - async provideReferences(model: any, position: any, context: any, token: any) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async provideReferences(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.ReferenceContext, token: monaco.CancellationToken) { const cursorPosition = this.props.editorAPI.getCursorPosition() const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition) @@ -23,11 +27,7 @@ export class RemixReferenceProvider { let fileTarget = await this.props.plugin.call('fileManager', 'getPathFromUrl', fileInNode) fileTarget = fileTarget.file const fileContent = await this.props.plugin.call('fileManager', 'readFile', fileInNode) - const lineColumn = await this.props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', - position, - position.file, - compilationResult.getSourceCode().sources, - compilationResult.getAsts()) + const lineColumn = await this.props.plugin.call('codeParser', 'getLineColumnOfPosition', position) console.log(position, fileTarget, lineColumn) try { this.props.plugin.call('editor', 'addModel', fileTarget, fileContent) diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 6d3f0a8d2a..69a61c8589 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -13,7 +13,8 @@ import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor' import { RemixHoverProvider } from './providers/hoverProvider' import { RemixReferenceProvider } from './providers/referenceProvider' import { RemixCompletionProvider } from './providers/completionProvider' -import { RemixCodeLensProvider } from './providers/codeLensProvider' +import { RemixHighLightProvider } from './providers/highlightProvider' +import { RemixDefinitionProvider } from './providers/definitionProvider' type sourceAnnotation = { row: number, @@ -524,6 +525,7 @@ export const EditorUI = (props: EditorUIProps) => { function handleEditorWillMount(monaco: Monaco) { // MonacoEditorTextDecorationPatch.augmentEditor(monaco.editor) console.log('editor will mount', monaco, typeof monaco) + monacoRef.current = monaco // Register a new language monacoRef.current.languages.register({ id: 'remix-solidity' }) @@ -535,47 +537,21 @@ export const EditorUI = (props: EditorUIProps) => { monacoRef.current.languages.setMonarchTokensProvider('remix-cairo', cairoLang as any) monacoRef.current.languages.setLanguageConfiguration('remix-cairo', cairoConf as any) - // register Definition Provider - monacoRef.current.languages.registerDefinitionProvider('remix-solidity', { - provideDefinition(model: any, position: any, token: any) { - const cursorPosition = props.editorAPI.getCursorPosition() - props.plugin.call('contextualListener', 'jumpToDefinition', cursorPosition) - return null - } - }) + monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: false, noSyntaxValidation: false, }); - monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', { - provideDocumentHighlights(model: any, position: any, token: any) { - console.log('HIghlight', position) - const hightlights = [ - { - range: new monacoRef.current.Range(position.lineNumber, position.column, position.lineNumber, position.column + 5), - kind: monacoRef.current.languages.DocumentHighlightKind.Write - } - ] - return hightlights - } - }) - - // monacoRef.current.languages.registerCodeLensProvider('remix-solidity', new RemixCodeLensProvider(props, monaco)) + + monacoRef.current.languages.registerDefinitionProvider('remix-solidity', new RemixDefinitionProvider(props, monaco)) + monacoRef.current.languages.registerDocumentHighlightProvider('remix-solidity', new RemixHighLightProvider(props, monaco)) monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco)) monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco)) monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco)) // monacoRef.current.languages.registerSignatureHelpProvider('remix-solidity', new RemixSignatureProvider(props, monaco)) loadTypes(monacoRef.current) - - monacoRef.current.languages.registerDefinitionProvider('typescript', { - - provideDefinition(model: any, position: any, token: any) { - console.log(token) - return null - } - }) } return ( diff --git a/libs/remix-ui/editor/src/types/monaco.ts b/libs/remix-ui/editor/src/types/monaco.ts index 91dd573c33..1cfc157bdd 100644 --- a/libs/remix-ui/editor/src/types/monaco.ts +++ b/libs/remix-ui/editor/src/types/monaco.ts @@ -1866,7 +1866,7 @@ declare namespace monaco.editor { /** * Get the language associated with this model. */ - getModeId(): string; + getModeId?(): string; /** * Get the word under or besides `position`. * @param position The position to look for a word. diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index a1ca18e6d8..67c7224c96 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -74,7 +74,6 @@ export const TabsUI = (props: TabsUIProps) => { const getFileDecorationClasses = (tab: any) => { - console.log('TAB', tab, tabsState.fileDecorations) const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => { if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true }) @@ -87,7 +86,6 @@ export const TabsUI = (props: TabsUIProps) => { const renderTab = (tab, index) => { - console.log('rendertab') const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '') const invert = props.themeQuality === 'dark' ? 'invert(1)' : 'invert(0)'