From 2356bd8e0d9f966b655bb3f1560c886276dca736 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Mon, 4 Jul 2022 21:14:57 +0200 Subject: [PATCH] add gast costs in editor --- apps/remix-ide/src/app/editor/editor.js | 18 +- .../remix-ide/src/app/plugins/code-parser.tsx | 193 ++++++++++++++++-- .../src/lib/offsetToLineColumnConverter.js | 75 ------- .../lib/offset-line-to-column-converter.ts | 10 +- .../src/source/sourceMappingDecoder.ts | 1 + .../src/lib/providers/codeLensProvider.ts | 46 +++++ .../editor/src/lib/providers/hoverProvider.ts | 13 +- .../editor/src/lib/remix-ui-editor.tsx | 70 +++++-- .../lib/components/file-decoration-icon.tsx | 11 +- .../file-decoration-custom-icon.tsx | 6 +- .../file-decoration-error-icon.tsx | 26 +-- .../file-decoration-tooltip.tsx | 33 +++ .../file-decoration-warning-icon.tsx | 4 +- .../file-decorators/src/lib/helper/index.tsx | 11 + .../src/lib/components/file-label.tsx | 1 - 15 files changed, 363 insertions(+), 155 deletions(-) delete mode 100644 apps/remix-ide/src/lib/offsetToLineColumnConverter.js create mode 100644 libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/helper/index.tsx diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 8779fdcfae..773958be22 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -13,7 +13,7 @@ const profile = { name: 'editor', description: 'service - editor', version: packageJson.version, - methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel', 'addErrorMarker', 'clearErrorMarkers'] + methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel', 'addErrorMarker', 'clearErrorMarkers'] } class Editor extends Plugin { @@ -26,8 +26,8 @@ class Editor extends Plugin { remixDark: 'remix-dark' } - this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } - this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } + this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } + this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } // Init this.event = new EventManager() @@ -580,6 +580,18 @@ class Editor extends Plugin { this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations) } } + + async addLineText (lineText, filePath) { + filePath = filePath || this.currentFile + await this.addDecoration(lineText, filePath, 'lineTextPerFile') + } + + discardLineTexts() { + const { from } = this.currentRequest + for (const session in this.sessions) { + this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations) + } + } } module.exports = Editor diff --git a/apps/remix-ide/src/app/plugins/code-parser.tsx b/apps/remix-ide/src/app/plugins/code-parser.tsx index be91676f6e..efdf54e2e9 100644 --- a/apps/remix-ide/src/app/plugins/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/code-parser.tsx @@ -9,6 +9,7 @@ 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 @@ -16,7 +17,7 @@ const SolidityParser = (window as any).SolidityParser = (window as any).Solidity 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'], + 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' } @@ -40,6 +41,7 @@ export class CodeParser extends Plugin { astWalker: any errorState: boolean = false onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise + gastEstimateTimeOut: any constructor(astWalker) { super(profile) @@ -53,16 +55,18 @@ export class CodeParser extends Plugin { 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() => { + 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() }) @@ -101,7 +105,6 @@ export class CodeParser extends Plugin { for (const error of data.errors) { const pos = helper.getPositionDetails(error.formattedMessage) const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) - const source = sources[pos.file] const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', { start: error.sourceLocation.start, @@ -109,7 +112,7 @@ export class CodeParser extends Plugin { }, filePosition, result.getSourceCode().sources, - result.getAsts()) + null) allErrors.push({ error, lineColumn }) } console.log('allErrors', allErrors) @@ -196,19 +199,33 @@ export class CodeParser extends Plugin { 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: {} + 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') } @@ -274,13 +291,10 @@ export class CodeParser extends Plugin { const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile) try { const ast = await this.parseSolidity(fileContent) - this.currentFileAST = ast - console.log('AST PARSE SUCCESS', ast) } catch (e) { console.log(e) } - console.log('LAST PARSER AST', this.currentFileAST) return this.currentFileAST } @@ -303,12 +317,86 @@ export class CodeParser extends Plugin { for (const s in compilationResult.sources) { this.astWalker.walkFull(compilationResult.sources[s].ast, callback) } - console.log("INDEX", this._index) + } } // 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 @@ -317,7 +405,6 @@ export class CodeParser extends Plugin { * @return {any} * */ async getBlockAtPosition(position: any, text: string = null) { - console.log('GET BLOCK AT ', position) await this.getCurrentFileAST(text) const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] @@ -397,6 +484,7 @@ export class CodeParser extends Plugin { 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 @@ -656,15 +744,23 @@ export class CodeParser extends Plugin { * @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 lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', - position, - position.file, - this.lastCompilationResult.getSourceCode().sources, - this.lastCompilationResult.getAsts()) - return `${filename} ${lineColumn.start.line}:${lineColumn.start.column}` + 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 + } } } @@ -736,6 +832,69 @@ export class CodeParser extends Plugin { } } + /** + * + * @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/lib/offsetToLineColumnConverter.js b/apps/remix-ide/src/lib/offsetToLineColumnConverter.js deleted file mode 100644 index b2a2e37e32..0000000000 --- a/apps/remix-ide/src/lib/offsetToLineColumnConverter.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' -import { Plugin } from '@remixproject/engine' -import * as packageJson from '../../../../package.json' -import { sourceMappingDecoder } from '@remix-project/remix-debug' - -const profile = { - name: 'offsetToLineColumnConverter', - methods: ['offsetToLineColumn'], - events: [], - version: packageJson.version -} - -export class OffsetToLineColumnConverter extends Plugin { - constructor () { - super(profile) - this.lineBreakPositionsByContent = {} - this.sourceMappingDecoder = sourceMappingDecoder - } - - /** - * Convert offset representation with line/column representation. - * This is also used to resolve the content: - * @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index. - * So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index. - * @param {{start, length}} rawLocation - offset location - * @param {number} file - The index where to find the source in the sources parameters - * @param {Object.} sources - Map of content sources - * @param {Object.} asts - Map of content sources - */ - offsetToLineColumn (rawLocation, file, sources, asts) { - if (!this.lineBreakPositionsByContent[file]) { - const sourcesArray = Object.keys(sources) - if (!asts || (file === 0 && sourcesArray.length === 1)) { - // if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12) - this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content) - } else { - for (var filename in asts) { - const source = asts[filename] - if (source.id === file) { - this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content) - break - } - } - } - } - return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) - } - - /** - * Convert offset representation with line/column representation. - * @param {{start, length}} rawLocation - offset location - * @param {number} file - The index where to find the source in the sources parameters - * @param {string} content - source - */ - offsetToLineColumnWithContent (rawLocation, file, content) { - this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content) - return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) - } - - /** - * Clear the cache - */ - clear () { - this.lineBreakPositionsByContent = {} - } - - /** - * called by plugin API - */ - activate () { - this.on('solidity', 'compilationFinished', () => { - this.clear() - }) - } -} diff --git a/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts b/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts index 359b9c6549..c2bf2fa8b0 100644 --- a/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts +++ b/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts @@ -30,13 +30,15 @@ export class OffsetToLineColumnConverter extends Plugin { * @param {Object.} asts - Map of content sources */ offsetToLineColumn (rawLocation, file, sources, asts) { + console.log('offsetToLineColumn', sources) //if (!this.lineBreakPositionsByContent[file]) { const sourcesArray = Object.keys(sources) - if (!asts || (file === 0 && sourcesArray.length === 1)) { + if (!asts || (file === 0 && sourcesArray.length === 1) || !Array.isArray(asts)) { + console.log("no asts or only one file", sourcesArray, sources, sources[sourcesArray[file || 0]].content) // if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12) - this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content) - + this.lineBreakPositionsByContent[file || 0] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[file || 0]].content) } else { + console.log("have asts") for (const filename in asts) { const source = asts[filename] if (source.id === file) { @@ -46,7 +48,7 @@ export class OffsetToLineColumnConverter extends Plugin { } } //} - return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) + return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file || 0]) } /** diff --git a/libs/remix-debug/src/source/sourceMappingDecoder.ts b/libs/remix-debug/src/source/sourceMappingDecoder.ts index 04b3bcb9ba..bbffe9d23c 100644 --- a/libs/remix-debug/src/source/sourceMappingDecoder.ts +++ b/libs/remix-debug/src/source/sourceMappingDecoder.ts @@ -68,6 +68,7 @@ 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 new file mode 100644 index 0000000000..99d41ee832 --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts @@ -0,0 +1,46 @@ +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/hoverProvider.ts b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts index 215662c5bd..838faedc26 100644 --- a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts @@ -1,6 +1,5 @@ import { editor, languages, Position } from 'monaco-editor' -import monaco from '../../types/monaco' import { EditorUIProps } from '../remix-ui-editor' export class RemixHoverProvider implements languages.HoverProvider { @@ -62,7 +61,7 @@ export class RemixHoverProvider implements languages.HoverProvider { const getOverrides = async (node: any) => { if (node.overrides) { - let overrides = [] + const overrides = [] for (const override of node.overrides.overrides) { overrides.push(override.name) } @@ -73,7 +72,7 @@ export class RemixHoverProvider implements languages.HoverProvider { } const getlinearizedBaseContracts = async (node: any) => { - let params = [] + const params = [] if (node.linearizedBaseContracts) { for (const id of node.linearizedBaseContracts) { const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) @@ -111,10 +110,10 @@ export class RemixHoverProvider implements languages.HoverProvider { }) } else if (nodeAtPosition.nodeType === 'FunctionDefinition') { - if(!nodeAtPosition.name) return + if (!nodeAtPosition.name) return const returns = await getReturnParameters(nodeAtPosition) contents.push({ - value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} ${returns? `returns ${returns}`: ''}` + value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} ${returns ? `returns ${returns}` : ''}` }) } else if (nodeAtPosition.nodeType === 'ModifierDefinition') { @@ -134,6 +133,8 @@ export class RemixHoverProvider implements languages.HoverProvider { contents.push({ value: `There are errors in the code.` }) + } else if (nodeAtPosition.nodeType === 'Block') { + } else { contents.push({ value: `${nodeAtPosition.nodeType}` @@ -146,7 +147,7 @@ export class RemixHoverProvider implements languages.HoverProvider { } getLinks(nodeAtPosition) getDocs(nodeAtPosition) - getScope(nodeAtPosition) + // getScope(nodeAtPosition) } 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 4590350942..6d3f0a8d2a 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -8,20 +8,12 @@ import { cairoLang, cairoConf } from './cairoSyntax' import './remix-ui-editor.css' import { loadTypes } from './web-types' import monaco from '../types/monaco' -import { IPosition, MarkerSeverity } from 'monaco-editor' +import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor' import { RemixHoverProvider } from './providers/hoverProvider' import { RemixReferenceProvider } from './providers/referenceProvider' import { RemixCompletionProvider } from './providers/completionProvider' -import { RemixSignatureProvider } from './providers/signatureProvider' -import { CompilationError } from '@remix-project/remix-solidity-ts' - -type cursorPosition = { - startLineNumber: number, - startColumn: number, - endLineNumber: number, - endColumn: number -} +import { RemixCodeLensProvider } from './providers/codeLensProvider' type sourceAnnotation = { row: number, @@ -47,6 +39,25 @@ type sourceMarker = { hide: boolean } +export type lineText = { + position: { + start: { + line: number + column: number + }, + end: { + line: number + column: number + } + }, + from: string // plugin name + content: string + className: string + afterContentClassName: string + hide: boolean, + hoverMessage: IMarkdownString | IMarkdownString[] +} + loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } }) export type DecorationsReturn = { @@ -270,7 +281,7 @@ export const EditorUI = (props: EditorUIProps) => { }, [props.currentFile]) - const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => { + const convertToMonacoDecoration = (decoration: any, typeOfDecoration: string) => { if (typeOfDecoration === 'sourceAnnotationsPerFile') { decoration = decoration as sourceAnnotation return { @@ -300,6 +311,20 @@ export const EditorUI = (props: EditorUIProps) => { } } } + if (typeOfDecoration === 'lineTextPerFile') { + console.log('lineTextPerFile', decoration) + decoration = decoration as lineText + return { + type: typeOfDecoration, + range: new monacoRef.current.Range(decoration.position.start.line + 1, decoration.position.start.column + 1, decoration.position.start.line + 1, 1024), + options: { + after: { content: ` ${decoration.content}`, inlineClassName: `${decoration.className}` }, + afterContentClassName: `${decoration.afterContentClassName}`, + hoverMessage : decoration.hoverMessage + }, + + } + } } props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => { @@ -318,6 +343,7 @@ export const EditorUI = (props: EditorUIProps) => { } } } + console.log(decorations, currentDecorations) return { currentDecorations: model.deltaDecorations(currentDecorations, decorations), registeredDecorations: newRegisteredDecorations @@ -343,10 +369,11 @@ export const EditorUI = (props: EditorUIProps) => { } const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => { + console.log("addDecoration", decoration, filePath, typeOfDecoration) const model = editorModelsState[filePath]?.model if (!model) return { currentDecorations: [] } const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration) - + console.log(monacoDecoration) return { currentDecorations: model.deltaDecorations([], [monacoDecoration]), registeredDecorations: [{ value: decoration, type: typeOfDecoration }] @@ -378,10 +405,10 @@ export const EditorUI = (props: EditorUIProps) => { if (model) { const markerData: monaco.editor.IMarkerData = { severity: errorServerityMap[marker.severity], - startLineNumber: (lineColumn.start && lineColumn.start.line) || 0 + 1, - startColumn: (lineColumn.start && lineColumn.start.column) || 0 + 1, - endLineNumber: (lineColumn.end && lineColumn.end.line) || 0 + 1, - endColumn: (lineColumn.end && lineColumn.end.column) || 0 + 1, + startLineNumber: ((lineColumn.start && lineColumn.start.line) || 0) + 1, + startColumn: ((lineColumn.start && lineColumn.start.column) || 0) + 1, + endLineNumber: ((lineColumn.end && lineColumn.end.line) || 0) + 1, + endColumn: ((lineColumn.end && lineColumn.end.column) || 0) + 1, message: marker.message, } console.log(markerData) @@ -495,6 +522,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 @@ -526,7 +554,7 @@ export const EditorUI = (props: EditorUIProps) => { console.log('HIghlight', position) const hightlights = [ { - range: new monacoRef.current.Range(position.lineNumber, position.column, position.lineNumber, position.column+5), + range: new monacoRef.current.Range(position.lineNumber, position.column, position.lineNumber, position.column + 5), kind: monacoRef.current.languages.DocumentHighlightKind.Write } ] @@ -534,12 +562,20 @@ export const EditorUI = (props: EditorUIProps) => { } }) + // monacoRef.current.languages.registerCodeLensProvider('remix-solidity', new RemixCodeLensProvider(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/file-decorators/src/lib/components/file-decoration-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx index 4c0743a89c..f05cea9f65 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from 'react' import { fileDecoration, fileDecorationType, FileType } from '../types' import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon' import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon' +import FileDecorationTooltip from './filedecorationicons/file-decoration-tooltip' import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon' export type fileDecorationProps = { @@ -19,23 +20,25 @@ export const FileDecorationIcons = (props: fileDecorationProps) => { setStates(props.fileDecorations.filter((fileDecoration) => fileDecoration.path === props.file.path || `${fileDecoration.workspace.name}/${fileDecoration.path}` === props.file.path)) }, [props.fileDecorations]) + const getTags = function () { if (states && states.length) { const elements: JSX.Element[] = [] - + for (const [index, state] of states.entries()) { switch (state.fileStateType) { case fileDecorationType.Error: - elements.push() + elements.push(}/>) break case fileDecorationType.Warning: - elements.push() + elements.push(}/>) break case fileDecorationType.Custom: - elements.push() + elements.push(}/>) break } } + return elements } } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx index d45319740b..c157bc66c0 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx @@ -3,10 +3,10 @@ import React from 'react' import { fileDecoration } from '../../types' const FileDecorationCustomIcon = (props: { - fileState: fileDecoration + fileDecoration: fileDecoration }) => { - return <> - {props.fileState.fileStateIcon} + return <> + {props.fileDecoration.fileStateIcon} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx index ea128709ba..2bb61fd97f 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx @@ -1,33 +1,13 @@ // eslint-disable-next-line no-use-before-define import React from 'react' -import { OverlayTrigger, Tooltip } from 'react-bootstrap' + import { fileDecoration } from '../../types' const FileDecorationErrorIcon = (props: { - fileState: fileDecoration + fileDecoration: fileDecoration }) => { - - const getComments = function () { - if(props.fileState.commment){ - const commments = Array.isArray(props.fileState.commment) ? props.fileState.commment : [props.fileState.commment] - return commments.map((comment, index) => { - return
{comment}

- }) - } - } - return <> - - <>{getComments()} - - } - > - {props.fileState.text} - + {props.fileDecoration.text} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx new file mode 100644 index 0000000000..7c9149c14d --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { OverlayTrigger, Tooltip } from "react-bootstrap"; +import { fileDecoration } from "../../types"; + +const FileDecorationTooltip = (props: { + fileDecoration: fileDecoration, + icon: JSX.Element + index: number +}, +) => { + const getComments = function (fileDecoration: fileDecoration) { + if (fileDecoration.commment) { + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } + } + + return + <>{getComments(props.fileDecoration)} + + } + >
{props.icon}
+ +} + + +export default FileDecorationTooltip; \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx index 309dbac35e..1c027c9854 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx @@ -3,9 +3,9 @@ import React from 'react' import { fileDecoration } from '../../types' const FileDecorationWarningIcon = (props: { - fileState: fileDecoration + fileDecoration: fileDecoration }) => { - return <>{props.fileState.text} + return <>{props.fileDecoration.text} } export default FileDecorationWarningIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx new file mode 100644 index 0000000000..1afe91b0cf --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx @@ -0,0 +1,11 @@ +import React from "react" +import { fileDecoration } from "../types" + +export const getComments = function (fileDecoration: fileDecoration) { + if(fileDecoration.commment){ + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/components/file-label.tsx b/libs/remix-ui/workspace/src/lib/components/file-label.tsx index 0f358d5aed..94d53f8a8c 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-label.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-label.tsx @@ -28,7 +28,6 @@ export const FileLabel = (props: FileLabelProps) => { }, [file.path, focusEdit]) useEffect(() => { - console.log('fileState', fileDecorations, file.name) const state = props.fileDecorations.find((state: fileDecoration) => { if(state.path === props.file.path) return true if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true