parent
600f76b0e0
commit
70382389f0
@ -1,220 +0,0 @@ |
|||||||
'use strict' |
|
||||||
import { Plugin } from '@remixproject/engine' |
|
||||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'contextualListener', |
|
||||||
methods: ['getActiveHighlights', 'gasEstimation', 'jumpToPosition', 'jumpToDefinition'], |
|
||||||
events: [], |
|
||||||
version: '0.0.1' |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
trigger contextChanged(nodes) |
|
||||||
*/ |
|
||||||
export class EditorContextListener extends Plugin { |
|
||||||
_activeHighlights: Array<any> |
|
||||||
astWalker: any |
|
||||||
currentPosition: any |
|
||||||
currentFile: string |
|
||||||
nodes: Array<any> |
|
||||||
results: any |
|
||||||
estimationObj: any |
|
||||||
creationCost: any |
|
||||||
codeDepositCost: any |
|
||||||
contract: any |
|
||||||
activated: boolean |
|
||||||
|
|
||||||
constructor(astWalker) { |
|
||||||
super(profile) |
|
||||||
this.activated = false |
|
||||||
|
|
||||||
this._activeHighlights = [] |
|
||||||
|
|
||||||
this.astWalker = astWalker |
|
||||||
} |
|
||||||
|
|
||||||
async onActivation() { |
|
||||||
return |
|
||||||
this.on('editor', 'contentChanged', async () => { |
|
||||||
console.log('contentChanged') |
|
||||||
this._stopHighlighting() |
|
||||||
}) |
|
||||||
|
|
||||||
this.on('fileManager', 'currentFileChanged', async () => { |
|
||||||
this._stopHighlighting() |
|
||||||
}) |
|
||||||
|
|
||||||
setInterval(async () => { |
|
||||||
console.log('interval') |
|
||||||
const compilationResult = await this.call('codeParser', 'getLastCompilationResult') |
|
||||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
|
||||||
|
|
||||||
let currentFile |
|
||||||
try { |
|
||||||
currentFile = await this.call('fileManager', 'file') |
|
||||||
} catch (error) { |
|
||||||
if (error.message !== 'Error: No such file or directory No file selected') throw error |
|
||||||
} |
|
||||||
this._highlightItems( |
|
||||||
await this.call('editor', 'getCursorPosition'), |
|
||||||
compilationResult, |
|
||||||
currentFile |
|
||||||
) |
|
||||||
} |
|
||||||
}, 1000) |
|
||||||
} |
|
||||||
|
|
||||||
getActiveHighlights() { |
|
||||||
return [...this._activeHighlights] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async _highlightItems(cursorPosition, compilationResult, file) { |
|
||||||
if (this.currentPosition === cursorPosition) return |
|
||||||
this._stopHighlighting() |
|
||||||
this.currentPosition = cursorPosition |
|
||||||
this.currentFile = file |
|
||||||
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) |
|
||||||
if (compilationResult && compilationResult.data && (compilationResult.data.sources[file] || compilationResult.data.sources[urlFromPath.file])) { |
|
||||||
const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file] || compilationResult.data.sources[urlFromPath.file]) |
|
||||||
this.nodes = nodes |
|
||||||
if (nodes && nodes.length && nodes[nodes.length - 1]) { |
|
||||||
await this._highlightExpressions(nodes[nodes.length - 1], compilationResult) |
|
||||||
} |
|
||||||
this.emit('contextChanged', nodes) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async _highlight(node, compilationResult) { |
|
||||||
if (!node) return |
|
||||||
const position = sourceMappingDecoder.decode(node.src) |
|
||||||
const fileTarget = compilationResult.getSourceName(position.file) |
|
||||||
const nodeFound = this._activeHighlights.find((el) => el.fileTarget === fileTarget && el.position.file === position.file && el.position.length === position.length && el.position.start === position.start) |
|
||||||
if (nodeFound) return // if the content is already highlighted, do nothing.
|
|
||||||
|
|
||||||
await this._highlightInternal(position, node, compilationResult) |
|
||||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
|
||||||
this._activeHighlights.push({ position, fileTarget, nodeId: node.id }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async _highlightInternal(position, node, compilationResult) { |
|
||||||
if (node.nodeType === 'Block') return |
|
||||||
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { |
|
||||||
let lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, compilationResult.getSourceCode().sources, compilationResult.getAsts()) |
|
||||||
if (node.nodes && node.nodes.length) { |
|
||||||
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
|
|
||||||
lineColumn = { |
|
||||||
start: { |
|
||||||
line: lineColumn.start.line, |
|
||||||
column: 0 |
|
||||||
}, |
|
||||||
end: { |
|
||||||
line: lineColumn.start.line + 1, |
|
||||||
column: 0 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
const fileName = compilationResult.getSourceName(position.file) |
|
||||||
if (fileName) { |
|
||||||
return await this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
async _highlightExpressions(node, compilationResult) { |
|
||||||
|
|
||||||
const highlights = async (id) => { |
|
||||||
const refs = await this.call('codeParser', 'getDeclaration', id) |
|
||||||
if (refs) { |
|
||||||
for (const ref in refs) { |
|
||||||
const node = refs[ref] |
|
||||||
await this._highlight(node, compilationResult) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if (node && node.referencedDeclaration) { |
|
||||||
await highlights(node.referencedDeclaration) |
|
||||||
const current = await this.call('codeParser', 'getNodeById', node.referencedDeclaration) // this._index.FlatReferences[node.referencedDeclaration]
|
|
||||||
await this._highlight(current, compilationResult) |
|
||||||
} else { |
|
||||||
await highlights(node.id) |
|
||||||
await this._highlight(node, compilationResult) |
|
||||||
} |
|
||||||
|
|
||||||
this.results = compilationResult |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
_stopHighlighting() { |
|
||||||
this.call('editor', 'discardHighlight') |
|
||||||
this.emit('stopHighlighting') |
|
||||||
this._activeHighlights = [] |
|
||||||
} |
|
||||||
|
|
||||||
gasEstimation(node) { |
|
||||||
this._loadContractInfos(node) |
|
||||||
let executionCost, codeDepositCost |
|
||||||
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 = this.estimationObj === null ? '-' : this.estimationObj.external[fn] |
|
||||||
} else if (visibility === 'private' || visibility === 'internal') { |
|
||||||
executionCost = this.estimationObj === null ? '-' : this.estimationObj.internal[fn] |
|
||||||
} |
|
||||||
} else { |
|
||||||
executionCost = this.creationCost |
|
||||||
codeDepositCost = this.codeDepositCost |
|
||||||
} |
|
||||||
} else { |
|
||||||
executionCost = '-' |
|
||||||
} |
|
||||||
return { executionCost, codeDepositCost } |
|
||||||
} |
|
||||||
|
|
||||||
_loadContractInfos(node) { |
|
||||||
console.log(this.results) |
|
||||||
const path = (this.nodes.length && this.nodes[0].absolutePath) || this.results.source.target |
|
||||||
for (const i in this.nodes) { |
|
||||||
if (this.nodes[i].id === node.scope) { |
|
||||||
const contract = this.nodes[i] |
|
||||||
this.contract = this.results.data.contracts && this.results.data.contracts[path] && this.results.data.contracts[path][contract.name] |
|
||||||
if (this.contract) { |
|
||||||
this.estimationObj = this.contract.evm && this.contract.evm.gasEstimates |
|
||||||
if (this.estimationObj) { |
|
||||||
this.creationCost = this.estimationObj === null ? '-' : this.estimationObj.creation.totalCost |
|
||||||
this.codeDepositCost = this.estimationObj === null ? '-' : this.estimationObj.creation.codeDepositCost |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_getInputParams(node) { |
|
||||||
const params = [] |
|
||||||
const target = node.parameters |
|
||||||
// for (const i in node.children) {
|
|
||||||
// if (node.children[i].name === 'ParameterList') {
|
|
||||||
// target = node.children[i]
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
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() + ')' |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
{ |
|
||||||
"presets": ["@nrwl/react/babel"], |
|
||||||
"plugins": [] |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
{ |
|
||||||
"env": { |
|
||||||
"browser": true, |
|
||||||
"es6": true |
|
||||||
}, |
|
||||||
"extends": "../../../.eslintrc", |
|
||||||
"globals": { |
|
||||||
"Atomics": "readonly", |
|
||||||
"SharedArrayBuffer": "readonly" |
|
||||||
}, |
|
||||||
"parserOptions": { |
|
||||||
"ecmaVersion": 11, |
|
||||||
"sourceType": "module" |
|
||||||
}, |
|
||||||
"rules": { |
|
||||||
"no-unused-vars": "off", |
|
||||||
"@typescript-eslint/no-unused-vars": "error" |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
# remix-ui-editor-context-view |
|
||||||
|
|
||||||
This library was generated with [Nx](https://nx.dev). |
|
||||||
|
|
||||||
## Running unit tests |
|
||||||
|
|
||||||
Run `nx test remix-ui-editor-context-view` to execute the unit tests via [Jest](https://jestjs.io). |
|
@ -1 +0,0 @@ |
|||||||
export * from './lib/remix-ui-editor-context-view' |
|
@ -1,43 +0,0 @@ |
|||||||
|
|
||||||
.container-context-view { |
|
||||||
padding : 1px 15px; |
|
||||||
} |
|
||||||
.line { |
|
||||||
display : flex; |
|
||||||
align-items : center; |
|
||||||
text-overflow : ellipsis; |
|
||||||
overflow : hidden; |
|
||||||
white-space : nowrap; |
|
||||||
font-size : 13px; |
|
||||||
} |
|
||||||
.type { |
|
||||||
font-style : italic; |
|
||||||
margin-right : 5px; |
|
||||||
} |
|
||||||
.name { |
|
||||||
font-weight : bold; |
|
||||||
} |
|
||||||
.jump { |
|
||||||
cursor : pointer; |
|
||||||
margin : 0 5px; |
|
||||||
} |
|
||||||
.jump:hover { |
|
||||||
color : var(--secondary); |
|
||||||
} |
|
||||||
.referencesnb { |
|
||||||
float : right; |
|
||||||
margin-left : 15px; |
|
||||||
} |
|
||||||
.gasEstimation { |
|
||||||
margin-right : 15px; |
|
||||||
display : flex; |
|
||||||
align-items : center; |
|
||||||
} |
|
||||||
.gasStationIcon { |
|
||||||
margin-right : 5px; |
|
||||||
} |
|
||||||
.contextviewcontainer { |
|
||||||
z-index : 50; |
|
||||||
border-radius : 1px; |
|
||||||
border : 2px solid var(--secondary); |
|
||||||
} |
|
@ -1,184 +0,0 @@ |
|||||||
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
|
|
||||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
|
||||||
|
|
||||||
import './remix-ui-editor-context-view.css' |
|
||||||
|
|
||||||
/* eslint-disable-next-line */ |
|
||||||
|
|
||||||
export type astNode = { |
|
||||||
name: string, |
|
||||||
id: number, |
|
||||||
children?: Array<any>, |
|
||||||
typeDescriptions: any, |
|
||||||
nodeType: string, |
|
||||||
src: string // e.g "142:1361:0"
|
|
||||||
} |
|
||||||
|
|
||||||
export type nodePositionLight = { |
|
||||||
file: number, |
|
||||||
length: number, |
|
||||||
start: number |
|
||||||
} |
|
||||||
|
|
||||||
export type astNodeLight = { |
|
||||||
fileTarget: string, |
|
||||||
nodeId: number, |
|
||||||
position: nodePositionLight |
|
||||||
} |
|
||||||
|
|
||||||
export type onContextListenerChangedListener = (nodes: Array<astNode>) => void |
|
||||||
export type ononCurrentFileChangedListener = (name: string) => void |
|
||||||
|
|
||||||
export type gasEstimationType = { |
|
||||||
executionCost: string, |
|
||||||
codeDepositCost: string |
|
||||||
} |
|
||||||
export interface RemixUiEditorContextViewProps { |
|
||||||
hide: boolean, |
|
||||||
jumpToPosition: (position: any) => void |
|
||||||
onContextListenerChanged: (listener: onContextListenerChangedListener) => void |
|
||||||
onCurrentFileChanged: (listener: ononCurrentFileChangedListener) => void |
|
||||||
getActiveHighlights: () => Array<astNodeLight> |
|
||||||
gasEstimation: (node: astNode) => gasEstimationType |
|
||||||
declarationOf: (node: astNode) => astNode |
|
||||||
} |
|
||||||
|
|
||||||
function isDefinition (node: any) { |
|
||||||
return node.nodeType === 'ContractDefinition' || |
|
||||||
node.nodeType === 'FunctionDefinition' || |
|
||||||
node.nodeType === 'ModifierDefinition' || |
|
||||||
node.nodeType === 'VariableDeclaration' || |
|
||||||
node.nodeType === 'StructDefinition' || |
|
||||||
node.nodeType === 'EventDefinition' |
|
||||||
} |
|
||||||
|
|
||||||
type nullableAstNode = astNode | null |
|
||||||
|
|
||||||
export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) { |
|
||||||
const loopOverReferences = useRef(0) |
|
||||||
const currentNodeDeclaration = useRef<nullableAstNode>(null) |
|
||||||
const [state, setState] = useState<{ |
|
||||||
nodes: Array<astNode>, |
|
||||||
activeHighlights: Array<any> |
|
||||||
gasEstimation: gasEstimationType |
|
||||||
}>({ |
|
||||||
nodes: [], |
|
||||||
activeHighlights: [], |
|
||||||
gasEstimation: { executionCost: '', codeDepositCost: '' } |
|
||||||
}) |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
props.onCurrentFileChanged(() => { |
|
||||||
currentNodeDeclaration.current = null |
|
||||||
setState(prevState => { |
|
||||||
return { ...prevState, nodes: [], activeHighlights: [] } |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
props.onContextListenerChanged(async (nodes: Array<astNode>) => { |
|
||||||
let nextNodeDeclaration |
|
||||||
let nextNode |
|
||||||
if (!props.hide && nodes && nodes.length) { |
|
||||||
nextNode = nodes[nodes.length - 1] |
|
||||||
if (!isDefinition(nextNode)) { |
|
||||||
nextNodeDeclaration = await props.declarationOf(nextNode) |
|
||||||
} else { |
|
||||||
nextNodeDeclaration = nextNode |
|
||||||
} |
|
||||||
} |
|
||||||
//console.log(nextNode, nextNodeDeclaration)
|
|
||||||
if (nextNodeDeclaration && currentNodeDeclaration.current && nextNodeDeclaration.id === currentNodeDeclaration.current.id) return |
|
||||||
|
|
||||||
currentNodeDeclaration.current = nextNodeDeclaration |
|
||||||
|
|
||||||
let gasEstimation |
|
||||||
if (currentNodeDeclaration.current) { |
|
||||||
if (currentNodeDeclaration.current.nodeType === 'FunctionDefinition') { |
|
||||||
gasEstimation = await props.gasEstimation(currentNodeDeclaration.current) |
|
||||||
} |
|
||||||
} |
|
||||||
const activeHighlights: Array<astNodeLight> = await props.getActiveHighlights() |
|
||||||
// console.log('active highlights', activeHighlights)
|
|
||||||
if (nextNode && activeHighlights && activeHighlights.length) { |
|
||||||
loopOverReferences.current = activeHighlights.findIndex((el: astNodeLight) => `${el.position.start}:${el.position.length}:${el.position.file}` === nextNode.src) |
|
||||||
loopOverReferences.current = loopOverReferences.current === -1 ? 0 : loopOverReferences.current |
|
||||||
} else { |
|
||||||
loopOverReferences.current = 0 |
|
||||||
} |
|
||||||
setState(prevState => { |
|
||||||
return { ...prevState, nodes, activeHighlights, gasEstimation } |
|
||||||
}) |
|
||||||
}) |
|
||||||
}, []) |
|
||||||
|
|
||||||
/* |
|
||||||
* show gas estimation |
|
||||||
*/ |
|
||||||
const gasEstimation = (node) => { |
|
||||||
if (node.nodeType === 'FunctionDefinition' && state && state.gasEstimation) { |
|
||||||
const result: gasEstimationType = state.gasEstimation |
|
||||||
const executionCost = ' Execution cost: ' + result.executionCost + ' gas' |
|
||||||
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' |
|
||||||
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` |
|
||||||
return ( |
|
||||||
<div className="gasEstimation"> |
|
||||||
<i className="fas fa-gas-pump gasStationIcon" title='Gas estimation'></i> |
|
||||||
<span>{estimatedGas}</span> |
|
||||||
</div> |
|
||||||
) |
|
||||||
} else { |
|
||||||
return (<div></div>) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const _render = () => { |
|
||||||
const node = currentNodeDeclaration.current |
|
||||||
if (!node) return (<div></div>) |
|
||||||
const references = state.activeHighlights |
|
||||||
// console.log(node)
|
|
||||||
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType |
|
||||||
const referencesCount = `${references ? references.length : '0'} reference(s)` |
|
||||||
|
|
||||||
const nodes: Array<astNodeLight> = state.activeHighlights |
|
||||||
|
|
||||||
const jumpTo = () => { |
|
||||||
if (node && node.src) { |
|
||||||
// console.log(node)
|
|
||||||
const position = sourceMappingDecoder.decode(node.src) |
|
||||||
if (position) { |
|
||||||
props.jumpToPosition(position) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// JUMP BETWEEN REFERENCES
|
|
||||||
const jump = (e: any) => { |
|
||||||
e.target.dataset.action === 'next' ? loopOverReferences.current++ : loopOverReferences.current-- |
|
||||||
if (loopOverReferences.current < 0) loopOverReferences.current = nodes.length - 1 |
|
||||||
if (loopOverReferences.current >= nodes.length) loopOverReferences.current = 0 |
|
||||||
console.log(loopOverReferences.current) |
|
||||||
props.jumpToPosition(nodes[loopOverReferences.current].position) |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="line">{gasEstimation(node)} |
|
||||||
<div title={type} className="type">{type}</div> |
|
||||||
<div title={node.name} className="name mr-2">{node.name}</div> |
|
||||||
<i className="fas fa-share jump" data-action='gotoref' aria-hidden="true" onClick={jumpTo}></i> |
|
||||||
<span className="referencesnb">{referencesCount}</span> |
|
||||||
<i data-action='previous' className="fas fa-chevron-up jump" aria-hidden="true" onClick={jump}></i> |
|
||||||
<i data-action='next' className="fas fa-chevron-down jump" aria-hidden="true" onClick={jump}></i> |
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0 py-1"> |
|
||||||
{_render()} |
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default RemixUiEditorContextView |
|
@ -1,16 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": "../../../tsconfig.base.json", |
|
||||||
"compilerOptions": { |
|
||||||
"jsx": "react", |
|
||||||
"allowJs": true, |
|
||||||
"esModuleInterop": true, |
|
||||||
"allowSyntheticDefaultImports": true |
|
||||||
}, |
|
||||||
"files": [], |
|
||||||
"include": [], |
|
||||||
"references": [ |
|
||||||
{ |
|
||||||
"path": "./tsconfig.lib.json" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": "./tsconfig.json", |
|
||||||
"compilerOptions": { |
|
||||||
"outDir": "../../../dist/out-tsc", |
|
||||||
"types": ["node"] |
|
||||||
}, |
|
||||||
"files": [ |
|
||||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
|
||||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
|
||||||
], |
|
||||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
|
||||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
|
||||||
} |
|
Loading…
Reference in new issue