add gast costs in editor

pull/5370/head
filip mertens 3 years ago
parent 6b35ac31b1
commit 17eca0f318
  1. 18
      apps/remix-ide/src/app/editor/editor.js
  2. 193
      apps/remix-ide/src/app/plugins/code-parser.tsx
  3. 75
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  4. 10
      libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts
  5. 1
      libs/remix-debug/src/source/sourceMappingDecoder.ts
  6. 46
      libs/remix-ui/editor/src/lib/providers/codeLensProvider.ts
  7. 13
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  8. 70
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  9. 11
      libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx
  10. 6
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx
  11. 26
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx
  12. 33
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx
  13. 4
      libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx
  14. 11
      libs/remix-ui/file-decorators/src/lib/helper/index.tsx
  15. 1
      libs/remix-ui/workspace/src/lib/components/file-label.tsx

@ -13,7 +13,7 @@ const profile = {
name: 'editor', name: 'editor',
description: 'service - editor', description: 'service - editor',
version: packageJson.version, 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 { class Editor extends Plugin {
@ -26,8 +26,8 @@ class Editor extends Plugin {
remixDark: 'remix-dark' remixDark: 'remix-dark'
} }
this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} }
// Init // Init
this.event = new EventManager() this.event = new EventManager()
@ -580,6 +580,18 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations) 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 module.exports = Editor

@ -9,6 +9,7 @@ import { helper } from '@remix-project/remix-solidity'
import React from 'react' import React from 'react'
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators'
import { lineText } from '@remix-ui/editor'
// eslint-disable-next-line // eslint-disable-next-line
@ -16,7 +17,7 @@ const SolidityParser = (window as any).SolidityParser = (window as any).Solidity
const profile = { const profile = {
name: 'codeParser', 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: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
@ -40,6 +41,7 @@ export class CodeParser extends Plugin {
astWalker: any astWalker: any
errorState: boolean = false errorState: boolean = false
onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void> onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void>
gastEstimateTimeOut: any
constructor(astWalker) { constructor(astWalker) {
super(profile) super(profile)
@ -53,16 +55,18 @@ export class CodeParser extends Plugin {
async onActivation() { async onActivation() {
this.on('editor', 'didChangeFile', async (file) => { this.on('editor', 'didChangeFile', async (file) => {
console.log('contentChanged', file) console.log('contentChanged', file)
await this.call('editor', 'discardLineTexts')
await this.getCurrentFileAST() await this.getCurrentFileAST()
await this.compile() await this.compile()
}) })
this.on('filePanel', 'setWorkspace', async() => { this.on('filePanel', 'setWorkspace', async () => {
await this.call('fileDecorator', 'setFileDecorators', []) await this.call('fileDecorator', 'setFileDecorators', [])
}) })
this.on('fileManager', 'currentFileChanged', async () => { this.on('fileManager', 'currentFileChanged', async () => {
await this.call('editor', 'discardLineTexts')
await this.getCurrentFileAST() await this.getCurrentFileAST()
await this.compile() await this.compile()
}) })
@ -101,7 +105,6 @@ export class CodeParser extends Plugin {
for (const error of data.errors) { for (const error of data.errors) {
const pos = helper.getPositionDetails(error.formattedMessage) const pos = helper.getPositionDetails(error.formattedMessage)
const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file) const filePosition = Object.keys(sources).findIndex((fileName) => fileName === error.sourceLocation.file)
const source = sources[pos.file]
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn',
{ {
start: error.sourceLocation.start, start: error.sourceLocation.start,
@ -109,7 +112,7 @@ export class CodeParser extends Plugin {
}, },
filePosition, filePosition,
result.getSourceCode().sources, result.getSourceCode().sources,
result.getAsts()) null)
allErrors.push({ error, lineColumn }) allErrors.push({ error, lineColumn })
} }
console.log('allErrors', allErrors) console.log('allErrors', allErrors)
@ -196,19 +199,33 @@ export class CodeParser extends Plugin {
decorators.push(decorator) decorators.push(decorator)
} }
console.log(decorators) console.log(decorators)
await this.call('fileDecorator', 'setFileDecorators', decorators) await this.call('fileDecorator', 'setFileDecorators', decorators)
} }
if (!data.sources) return if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return if (data.sources && Object.keys(data.sources).length === 0) return
this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input) this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input)
this.errorState = false this.errorState = false
this._index = { this._index = {
Declarations: {}, Declarations: {},
FlatReferences: {} FlatReferences: {},
NodesPerFile: {},
} }
this._buildIndex(data, source) 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.emit('astFinished')
} }
@ -274,13 +291,10 @@ export class CodeParser extends Plugin {
const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile) const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile)
try { try {
const ast = await this.parseSolidity(fileContent) const ast = await this.parseSolidity(fileContent)
this.currentFileAST = ast this.currentFileAST = ast
console.log('AST PARSE SUCCESS', ast)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
console.log('LAST PARSER AST', this.currentFileAST)
return this.currentFileAST return this.currentFileAST
} }
@ -303,12 +317,86 @@ export class CodeParser extends Plugin {
for (const s in compilationResult.sources) { for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback) this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
} }
console.log("INDEX", this._index)
} }
} }
// NODE HELPERS // 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 * Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function * 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} * @return {any}
* */ * */
async getBlockAtPosition(position: any, text: string = null) { async getBlockAtPosition(position: any, text: string = null) {
console.log('GET BLOCK AT ', position)
await this.getCurrentFileAST(text) await this.getCurrentFileAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition'] const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
@ -397,6 +484,7 @@ export class CodeParser extends Plugin {
const lastCompilationResult = this.lastCompilationResult const lastCompilationResult = this.lastCompilationResult
if (!lastCompilationResult) return false if (!lastCompilationResult) return false
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) 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]) { 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]) const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
return nodes return nodes
@ -656,15 +744,23 @@ export class CodeParser extends Plugin {
* @returns * @returns
*/ */
async getNodeLink(node: any) { 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) const position = await this.positionOfDefinition(node)
if (position) { if (position) {
const filename = this.lastCompilationResult.getSourceName(position.file) const fileName = this.lastCompilationResult.getSourceName(position.file)
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.lastCompilationResult.source.sources[fileName].content)
position, const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks)
position.file, return {
this.lastCompilationResult.getSourceCode().sources, fileName,
this.lastCompilationResult.getAsts()) position: lineColumn
return `${filename} ${lineColumn.start.line}:${lineColumn.start.column}` }
} }
} }
@ -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)
}
}
}
} }

@ -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.<string, {content}>} sources - Map of content sources
* @param {Object.<string, {ast, id}>} 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()
})
}
}

@ -30,13 +30,15 @@ export class OffsetToLineColumnConverter extends Plugin {
* @param {Object.<string, {ast, id}>} asts - Map of content sources * @param {Object.<string, {ast, id}>} asts - Map of content sources
*/ */
offsetToLineColumn (rawLocation, file, sources, asts) { offsetToLineColumn (rawLocation, file, sources, asts) {
console.log('offsetToLineColumn', sources)
//if (!this.lineBreakPositionsByContent[file]) { //if (!this.lineBreakPositionsByContent[file]) {
const sourcesArray = Object.keys(sources) 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) // 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 { } else {
console.log("have asts")
for (const filename in asts) { for (const filename in asts) {
const source = asts[filename] const source = asts[filename]
if (source.id === file) { 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])
} }
/** /**

@ -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) * @return {Object} returns an object {start: {line, column}, end: {line, column}} (line/column count start at 0)
*/ */
export function convertOffsetToLineColumn (sourceLocation, lineBreakPositions) { export function convertOffsetToLineColumn (sourceLocation, lineBreakPositions) {
console.log(sourceLocation, lineBreakPositions)
if (sourceLocation.start >= 0 && sourceLocation.length >= 0) { if (sourceLocation.start >= 0 && sourceLocation.length >= 0) {
return { return {
start: convertFromCharPosition(sourceLocation.start, lineBreakPositions), start: convertFromCharPosition(sourceLocation.start, lineBreakPositions),

@ -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: () => { }
};
}
}

@ -1,6 +1,5 @@
import { editor, languages, Position } from 'monaco-editor' import { editor, languages, Position } from 'monaco-editor'
import monaco from '../../types/monaco'
import { EditorUIProps } from '../remix-ui-editor' import { EditorUIProps } from '../remix-ui-editor'
export class RemixHoverProvider implements languages.HoverProvider { export class RemixHoverProvider implements languages.HoverProvider {
@ -62,7 +61,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
const getOverrides = async (node: any) => { const getOverrides = async (node: any) => {
if (node.overrides) { if (node.overrides) {
let overrides = [] const overrides = []
for (const override of node.overrides.overrides) { for (const override of node.overrides.overrides) {
overrides.push(override.name) overrides.push(override.name)
} }
@ -73,7 +72,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
} }
const getlinearizedBaseContracts = async (node: any) => { const getlinearizedBaseContracts = async (node: any) => {
let params = [] const params = []
if (node.linearizedBaseContracts) { if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) { for (const id of node.linearizedBaseContracts) {
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) 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') { } else if (nodeAtPosition.nodeType === 'FunctionDefinition') {
if(!nodeAtPosition.name) return if (!nodeAtPosition.name) return
const returns = await getReturnParameters(nodeAtPosition) const returns = await getReturnParameters(nodeAtPosition)
contents.push({ 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') { } else if (nodeAtPosition.nodeType === 'ModifierDefinition') {
@ -134,6 +133,8 @@ export class RemixHoverProvider implements languages.HoverProvider {
contents.push({ contents.push({
value: `There are errors in the code.` value: `There are errors in the code.`
}) })
} else if (nodeAtPosition.nodeType === 'Block') {
} else { } else {
contents.push({ contents.push({
value: `${nodeAtPosition.nodeType}` value: `${nodeAtPosition.nodeType}`
@ -146,7 +147,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
} }
getLinks(nodeAtPosition) getLinks(nodeAtPosition)
getDocs(nodeAtPosition) getDocs(nodeAtPosition)
getScope(nodeAtPosition) // getScope(nodeAtPosition)
} }

@ -8,20 +8,12 @@ import { cairoLang, cairoConf } from './cairoSyntax'
import './remix-ui-editor.css' import './remix-ui-editor.css'
import { loadTypes } from './web-types' import { loadTypes } from './web-types'
import monaco from '../types/monaco' import monaco from '../types/monaco'
import { IPosition, MarkerSeverity } from 'monaco-editor' import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor'
import { RemixHoverProvider } from './providers/hoverProvider' import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider' import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider' import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixSignatureProvider } from './providers/signatureProvider' import { RemixCodeLensProvider } from './providers/codeLensProvider'
import { CompilationError } from '@remix-project/remix-solidity-ts'
type cursorPosition = {
startLineNumber: number,
startColumn: number,
endLineNumber: number,
endColumn: number
}
type sourceAnnotation = { type sourceAnnotation = {
row: number, row: number,
@ -47,6 +39,25 @@ type sourceMarker = {
hide: boolean 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' } }) loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
export type DecorationsReturn = { export type DecorationsReturn = {
@ -270,7 +281,7 @@ export const EditorUI = (props: EditorUIProps) => {
}, [props.currentFile]) }, [props.currentFile])
const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => { const convertToMonacoDecoration = (decoration: any, typeOfDecoration: string) => {
if (typeOfDecoration === 'sourceAnnotationsPerFile') { if (typeOfDecoration === 'sourceAnnotationsPerFile') {
decoration = decoration as sourceAnnotation decoration = decoration as sourceAnnotation
return { 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) => { 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 { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations), currentDecorations: model.deltaDecorations(currentDecorations, decorations),
registeredDecorations: newRegisteredDecorations registeredDecorations: newRegisteredDecorations
@ -343,10 +369,11 @@ export const EditorUI = (props: EditorUIProps) => {
} }
const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => { const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => {
console.log("addDecoration", decoration, filePath, typeOfDecoration)
const model = editorModelsState[filePath]?.model const model = editorModelsState[filePath]?.model
if (!model) return { currentDecorations: [] } if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration) const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
console.log(monacoDecoration)
return { return {
currentDecorations: model.deltaDecorations([], [monacoDecoration]), currentDecorations: model.deltaDecorations([], [monacoDecoration]),
registeredDecorations: [{ value: decoration, type: typeOfDecoration }] registeredDecorations: [{ value: decoration, type: typeOfDecoration }]
@ -378,10 +405,10 @@ export const EditorUI = (props: EditorUIProps) => {
if (model) { if (model) {
const markerData: monaco.editor.IMarkerData = { const markerData: monaco.editor.IMarkerData = {
severity: errorServerityMap[marker.severity], severity: errorServerityMap[marker.severity],
startLineNumber: (lineColumn.start && lineColumn.start.line) || 0 + 1, startLineNumber: ((lineColumn.start && lineColumn.start.line) || 0) + 1,
startColumn: (lineColumn.start && lineColumn.start.column) || 0 + 1, startColumn: ((lineColumn.start && lineColumn.start.column) || 0) + 1,
endLineNumber: (lineColumn.end && lineColumn.end.line) || 0 + 1, endLineNumber: ((lineColumn.end && lineColumn.end.line) || 0) + 1,
endColumn: (lineColumn.end && lineColumn.end.column) || 0 + 1, endColumn: ((lineColumn.end && lineColumn.end.column) || 0) + 1,
message: marker.message, message: marker.message,
} }
console.log(markerData) console.log(markerData)
@ -495,6 +522,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
function handleEditorWillMount(monaco: Monaco) { function handleEditorWillMount(monaco: Monaco) {
// MonacoEditorTextDecorationPatch.augmentEditor(monaco.editor)
console.log('editor will mount', monaco, typeof monaco) console.log('editor will mount', monaco, typeof monaco)
monacoRef.current = monaco monacoRef.current = monaco
// Register a new language // Register a new language
@ -526,7 +554,7 @@ export const EditorUI = (props: EditorUIProps) => {
console.log('HIghlight', position) console.log('HIghlight', position)
const hightlights = [ 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 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.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(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.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
// monacoRef.current.languages.registerSignatureHelpProvider('remix-solidity', new RemixSignatureProvider(props, monaco)) // monacoRef.current.languages.registerSignatureHelpProvider('remix-solidity', new RemixSignatureProvider(props, monaco))
loadTypes(monacoRef.current) loadTypes(monacoRef.current)
monacoRef.current.languages.registerDefinitionProvider('typescript', {
provideDefinition(model: any, position: any, token: any) {
console.log(token)
return null
}
})
} }
return ( return (

@ -4,6 +4,7 @@ import React, { useEffect, useState } from 'react'
import { fileDecoration, fileDecorationType, FileType } from '../types' import { fileDecoration, fileDecorationType, FileType } from '../types'
import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon' import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon'
import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon' import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon'
import FileDecorationTooltip from './filedecorationicons/file-decoration-tooltip'
import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon' import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon'
export type fileDecorationProps = { 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)) setStates(props.fileDecorations.filter((fileDecoration) => fileDecoration.path === props.file.path || `${fileDecoration.workspace.name}/${fileDecoration.path}` === props.file.path))
}, [props.fileDecorations]) }, [props.fileDecorations])
const getTags = function () { const getTags = function () {
if (states && states.length) { if (states && states.length) {
const elements: JSX.Element[] = [] const elements: JSX.Element[] = []
for (const [index, state] of states.entries()) { for (const [index, state] of states.entries()) {
switch (state.fileStateType) { switch (state.fileStateType) {
case fileDecorationType.Error: case fileDecorationType.Error:
elements.push(<FileDecorationErrorIcon fileState={state} key={index} />) elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationErrorIcon fileDecoration={state} key={index}/>}/>)
break break
case fileDecorationType.Warning: case fileDecorationType.Warning:
elements.push(<FileDecorationWarningIcon fileState={state} key={index}/>) elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationWarningIcon fileDecoration={state} key={index}/>}/>)
break break
case fileDecorationType.Custom: case fileDecorationType.Custom:
elements.push(<FileDecorationCustomIcon fileState={state} key={index} />) elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationCustomIcon fileDecoration={state} key={index}/>}/>)
break break
} }
} }
return elements return elements
} }
} }

@ -3,10 +3,10 @@ import React from 'react'
import { fileDecoration } from '../../types' import { fileDecoration } from '../../types'
const FileDecorationCustomIcon = (props: { const FileDecorationCustomIcon = (props: {
fileState: fileDecoration fileDecoration: fileDecoration
}) => { }) => {
return <><span className={`${props.fileState.fileStateIconClass}pr-2`}> return <><span className={`${props.fileDecoration.fileStateIconClass}pr-2`}>
{props.fileState.fileStateIcon} {props.fileDecoration.fileStateIcon}
</span></> </span></>
} }

@ -1,33 +1,13 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { fileDecoration } from '../../types' import { fileDecoration } from '../../types'
const FileDecorationErrorIcon = (props: { 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 <div key={index}>{comment}<br></br></div>
})
}
}
return <> return <>
<OverlayTrigger <span className={`${props.fileDecoration.fileStateIconClass} text-danger pr-2`}>{props.fileDecoration.text}</span>
placement='auto'
overlay={
<Tooltip id={`tooltip-${props.fileState.path}`}>
<>{getComments()}</>
</Tooltip>
}
>
<span className={`${props.fileState.fileStateIconClass} text-danger pr-2`}>{props.fileState.text}</span>
</OverlayTrigger>
</> </>
} }

@ -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 <div key={index}>{comment}<br></br></div>
})
}
}
return <OverlayTrigger
key={`overlaytrigger-${props.fileDecoration.path}-${props.index}`}
placement='auto'
overlay={
<Tooltip id={`error-tooltip-${props.fileDecoration.path}`}>
<>{getComments(props.fileDecoration)}</>
</Tooltip>
}
><div>{props.icon}</div></OverlayTrigger>
}
export default FileDecorationTooltip;

@ -3,9 +3,9 @@ import React from 'react'
import { fileDecoration } from '../../types' import { fileDecoration } from '../../types'
const FileDecorationWarningIcon = (props: { const FileDecorationWarningIcon = (props: {
fileState: fileDecoration fileDecoration: fileDecoration
}) => { }) => {
return <><span className={`${props.fileState.fileStateIconClass} text-warning pr-2`}>{props.fileState.text}</span></> return <><span className={`${props.fileDecoration.fileStateIconClass} text-warning pr-2`}>{props.fileDecoration.text}</span></>
} }
export default FileDecorationWarningIcon export default FileDecorationWarningIcon

@ -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 <div key={index}>{comment}<br></br></div>
})
}
}

@ -28,7 +28,6 @@ export const FileLabel = (props: FileLabelProps) => {
}, [file.path, focusEdit]) }, [file.path, focusEdit])
useEffect(() => { useEffect(() => {
console.log('fileState', fileDecorations, file.name)
const state = props.fileDecorations.find((state: fileDecoration) => { const state = props.fileDecorations.find((state: fileDecoration) => {
if(state.path === props.file.path) return true if(state.path === props.file.path) return true
if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true

Loading…
Cancel
Save