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