rm context view

editorcontextDummy
filip mertens 2 years ago
parent 924dc0b208
commit 2898da6470
  1. 1
      libs/remix-core-plugin/src/index.ts
  2. 220
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  3. 4
      libs/remix-ui/editor-context-view/.babelrc
  4. 19
      libs/remix-ui/editor-context-view/.eslintrc
  5. 7
      libs/remix-ui/editor-context-view/README.md
  6. 1
      libs/remix-ui/editor-context-view/src/index.ts
  7. 43
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.css
  8. 184
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx
  9. 16
      libs/remix-ui/editor-context-view/tsconfig.json
  10. 13
      libs/remix-ui/editor-context-view/tsconfig.lib.json
  11. 14
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  12. 3
      nx.json
  13. 18
      workspace.json

@ -3,7 +3,6 @@ export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener'
export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'

@ -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"]
}

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { RemixUiEditorContextView, astNode } from '@remix-ui/editor-context-view'
import Editor, { loader, Monaco } from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { language, conf } from './syntax'
@ -578,17 +578,7 @@ export const EditorUI = (props: EditorUIProps) => {
options={{ glyphMargin: true, readOnly: true }}
defaultValue={defaultEditorValue}
/>
<div className="contextview">
<RemixUiEditorContextView
hide={false}
jumpToPosition={(position) => props.plugin.call('contextualListener', 'jumpToPosition', position)}
onContextListenerChanged={(listener) => { props.plugin.on('contextualListener', 'contextChanged', listener) }}
onCurrentFileChanged={(listener) => { props.plugin.on('fileManager', 'currentFileChanged', listener) }}
getActiveHighlights={() => { return props.plugin.call('contextualListener', 'getActiveHighlights') }}
gasEstimation={(node: astNode) => { return props.plugin.call('contextualListener', 'gasEstimation', node) }}
declarationOf={(node: astNode) => { return props.plugin.call('codeParser', 'declarationOf', node) }}
/>
</div>
</div>
)
}

@ -161,9 +161,6 @@
"solidity-unit-testing": {
"tags": []
},
"remix-ui-editor-context-view": {
"tags": []
},
"remix-ui-run-tab": {
"tags": []
},

@ -1175,24 +1175,6 @@
}
}
},
"remix-ui-editor-context-view": {
"root": "libs/remix-ui/editor-context-view",
"sourceRoot": "libs/remix-ui/editor-context-view/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/editor-context-view/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/editor-context-view/**/*"
]
}
}
}
},
"remix-ui-run-tab": {
"root": "libs/remix-ui/run-tab",
"sourceRoot": "libs/remix-ui/run-tab/src",

Loading…
Cancel
Save