editorcontextDummy
filip mertens 2 years ago
parent 3af5c0c737
commit fb42aff834
  1. 6
      apps/remix-ide/src/app.js
  2. 1
      libs/remix-core-plugin/src/index.ts
  3. 503
      libs/remix-core-plugin/src/lib/code-parser.ts
  4. 374
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  5. 273
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  6. 63
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  7. 4
      libs/remix-ui/editor/src/lib/providers/referenceProvider.ts
  8. 4
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  9. 60
      yarn.lock

@ -13,7 +13,7 @@ import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy, CodeParser } from '@remix-project/core-plugin'
import { WalkthroughService } from './walkthroughService'
@ -201,6 +201,7 @@ class AppComponent {
}
)
const contextualListener = new EditorContextListener(new AstWalker())
const codeParser = new CodeParser(new AstWalker())
this.notification = new NotificationPlugin()
@ -225,6 +226,7 @@ class AppComponent {
networkModule,
offsetToLineColumnConverter,
contextualListener,
codeParser,
terminal,
web3Provider,
compileAndRun,
@ -350,7 +352,7 @@ class AppComponent {
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'codeParser', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun'])

@ -8,3 +8,4 @@ export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'
export { OpenZeppelinProxy } from './lib/openzeppelin-proxy'
export { CodeParser } from './lib/code-parser'

@ -4,26 +4,42 @@ import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { Compiler } from '@remix-project/remix-solidity'
import { CompilationError, CompilationResult, CompilationSource, helper } from '@remix-project/remix-solidity-ts'
import { AstNode, CompilationError, CompilationResult, CompilationSource, helper } from '@remix-project/remix-solidity-ts'
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
const profile = {
name: 'codeParser',
methods: ['getBlockName', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getAST', 'nodesWithScope', 'nodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'],
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'],
events: [],
version: '0.0.1'
}
export function isNodeDefinition(node: any) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
export class CodeParser extends Plugin {
currentFileAST: any // contains the simple parsed AST for the current file
compiler: any // used to compile the current file seperately from the main compiler
onAstFinished: (success: any, data: any, source: any, input: any, version: any) => Promise<void>
lastCompilationResult: any
currentFile: any
_index: any
astWalker: any
onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void>
constructor() {
constructor(astWalker) {
super(profile)
this.astWalker = astWalker
this._index = {
Declarations: {},
FlatReferences: {}
}
}
async onActivation() {
@ -38,11 +54,82 @@ export class CodeParser extends Plugin {
await this.compile()
})
this.on('solidity', 'loadingCompiler', async (url) => {
console.log('loading compiler', url)
this.compiler.loadVersion(true, url)
this.compiler.event.register('compilerLoaded', async () => {
console.log('compiler loaded')
})
})
/**
* - processes compilation results
* - calls the editor to add markers on the errors
* - builds the flat index of nodes
*/
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => {
console.log('compile success', success, data, this)
this.call('editor', 'clearAnnotations')
let noFatalErrors = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => {
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning'
if (isValidError) {
console.log(error)
noFatalErrors = false
}
}
const result = new CompilerAbstract('soljson', data, source, input)
if (data.error) checkIfFatalError(data.error)
if (data.errors) data.errors.forEach((err) => checkIfFatalError(err))
const allErrors = []
if (data.errors) {
for (const error of data.errors) {
console.log('ERROR POS', error)
let pos = helper.getPositionDetails(error.formattedMessage)
console.log('ERROR POS', pos)
const sources = result.getSourceCode().sources
const source = sources[pos.file]
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn',
{
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
},
0,
sources,
null)
console.log('lineColumn', lineColumn)
allErrors.push({ error, lineColumn })
}
await this.call('editor', 'addErrorMarker', allErrors)
} else {
await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input)
this._index = {
Declarations: {},
FlatReferences: {}
}
this._buildIndex(data, source)
this.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.call('contentImport', 'resolveAndSave', url, undefined, false).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('astFinished', this.onAstFinished)
}
// COMPILER
/**
*
* @returns
*/
async compile() {
try {
const state = await this.call('solidity', 'getCompilerState')
@ -52,6 +139,7 @@ export class CodeParser extends Plugin {
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', state.useFileConfiguration)
this.currentFile = await this.call('fileManager', 'file')
console.log(this.currentFile)
if (!this.currentFile) return
const content = await this.call('fileManager', 'readFile', this.currentFile)
const sources = { [this.currentFile]: { content } }
@ -61,12 +149,23 @@ export class CodeParser extends Plugin {
}
}
/**
*
* @returns
*/
async getLastCompilationResult() {
return this.lastCompilationResult
}
/*
* simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports
*/
async parseSolidity(text: string) {
const t0 = performance.now();
const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
const t1 = performance.now();
console.log(`Call to doSomething took ${t1 - t0} milliseconds.`);
console.log('AST PARSE SUCCESS', ast)
return ast
}
@ -77,12 +176,13 @@ export class CodeParser extends Plugin {
* @param text
* @returns
*/
async getCurrentFileAST(text: string = null) {
async getCurrentFileAST(text: string | null = null) {
this.currentFile = await this.call('fileManager', 'file')
if (!this.currentFile) return
const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile)
try {
const ast = await this.parseSolidity(fileContent)
this.currentFileAST = ast
console.log('AST PARSE SUCCESS', ast)
} catch (e) {
@ -92,6 +192,31 @@ export class CodeParser extends Plugin {
return this.currentFileAST
}
/**
* Builds a flat index and declarations of all the nodes in the compilation result
* @param compilationResult
* @param source
*/
_buildIndex(compilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node) => {
if (node && node.referencedDeclaration) {
if (!this._index.Declarations[node.referencedDeclaration]) {
this._index.Declarations[node.referencedDeclaration] = []
}
this._index.Declarations[node.referencedDeclaration].push(node)
}
this._index.FlatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
console.log("INDEX", this._index)
}
}
// NODE HELPERS
/**
* Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function
@ -100,6 +225,7 @@ export class CodeParser extends Plugin {
* @return {any}
* */
async getBlockAtPosition(position: any, text: string = null) {
console.log('GET BLOCK AT ', position)
await this.getCurrentFileAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
@ -121,5 +247,370 @@ export class CodeParser extends Plugin {
return walkAst(this.currentFileAST)
}
/**
* Lists the AST nodes from the current file parser
* These nodes need to be changed to match the node types returned by the compiler
* @returns
*/
async listAstNodes() {
await this.getCurrentFileAST();
let nodes = [];
(SolidityParser as any).visit(this.currentFileAST, {
StateVariableDeclaration: (node) => {
if (node.variables) {
for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration' })
}
}
},
UserDefinedTypeName: (node) => {
nodes.push({ ...node, nodeType: node.type })
},
FunctionDefinition: (node) => {
nodes.push({ ...node, nodeType: node.type })
},
ContractDefinition: (node) => {
nodes.push({ ...node, nodeType: node.type })
},
MemberAccess: function (node) {
nodes.push({ ...node, nodeType: node.type })
},
Identifier: function (node) {
nodes.push({ ...node, nodeType: node.type })
},
InvalidNode: function (node) {
nodes.push({ ...node, nodeType: node.type })
}
})
console.log("LIST NODES", nodes)
return nodes
}
/**
* Nodes at position where position is a number, offset
* @param position
* @param type
* @returns
*/
async nodesAtPosition(position: number, type = '') {
const lastCompilationResult = this.lastCompilationResult
if (!lastCompilationResult) return false
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
return nodes
}
return []
}
/**
*
* @param id
* @returns
*/
async getNodeById(id: any) {
for (const key in this._index.FlatReferences) {
if (this._index.FlatReferences[key].id === id) {
return this._index.FlatReferences[key]
}
}
}
/**
*
* @param id
* @returns
*/
async getDeclaration(id: any) {
if(this._index.Declarations && this._index.Declarations[id]) return this._index.Declarations[id]
}
/**
*
* @param scope
* @returns
*/
async getNodesWithScope(scope: number) {
const nodes = []
for (const node of Object.values(this._index.FlatReferences) as any[]) {
if (node.scope === scope) nodes.push(node)
}
return nodes
}
/**
*
* @param name
* @returns
*/
async getNodesWithName(name: string) {
const nodes = []
for (const node of Object.values(this._index.FlatReferences) as any[]) {
if (node.name === name) nodes.push(node)
}
return nodes
}
/**
*
* @param node
* @returns
*/
declarationOf(node: AstNode) {
if (node && node.referencedDeclaration) {
return this._index.FlatReferences[node.referencedDeclaration]
} else {
// console.log(this._index.FlatReferences)
}
return null
}
/**
*
* @param position
* @returns
*/
async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position)
console.log('nodes at position', nodes, position)
console.log(this._index.FlatReferences)
let nodeDefinition: any
let node: any
if (nodes && nodes.length) {
node = nodes[nodes.length - 1]
nodeDefinition = node
if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node
}
if (node.nodeType === 'ImportDirective') {
for (const key in this._index.FlatReferences) {
if (this._index.FlatReferences[key].id === node.sourceUnit) {
nodeDefinition = this._index.FlatReferences[key]
}
}
}
return nodeDefinition
} else {
let astNodes = await this.listAstNodes()
for (const node of astNodes) {
if (node.range[0] <= position && node.range[1] >= position) {
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) {
nodeDefinition = node
}
if (!nodeDefinition) nodeDefinition = node
}
}
return nodeDefinition
}
}
/**
*
* @param node
* @returns
*/
async positionOfDefinition(node: any): Promise<any | null> {
if (node) {
if (node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
return position
}
}
}
return null
}
/**
*
* @param node
* @param imported
* @returns
*/
async resolveImports(node, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
console.log('IMPORTING', node)
const importNode = await this.getNodeById(node.sourceUnit)
imported[importNode.id] = importNode
if (importNode.nodes) {
for (const child of importNode.nodes) {
imported = await this.resolveImports(child, imported)
}
}
}
console.log(imported)
return imported
}
/**
*
* @param ast
* @returns
*/
async getLastNodeInLine(ast: string) {
let lastNode
const checkLastNode = (node) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, {
MemberAccess: function (node) {
checkLastNode(node)
},
Identifier: function (node) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression && lastNode.expression.expression) {
console.log('lastNode with expression', lastNode, lastNode.expression)
return lastNode.expression.expression
}
if (lastNode && lastNode.expression) {
console.log('lastNode with expression', lastNode, lastNode.expression)
return lastNode.expression
}
console.log('lastNode', lastNode)
return lastNode
}
/**
*
* @param node
* @returns
*/
referencesOf(node: any) {
const results = []
const highlights = (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
for (const ref in refs) {
const node = refs[ref]
results.push(node)
}
}
}
if (node && node.referencedDeclaration) {
highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[node.referencedDeclaration]
results.push(current)
} else {
highlights(node.id)
}
return results
}
/**
*
* @param position
* @returns
*/
async referrencesAtPosition(position: any) {
const nodes = await this.nodesAtPosition(position)
if (nodes && nodes.length) {
const node = nodes[nodes.length - 1]
if (node) {
return this.referencesOf(node)
}
}
}
/**
*
* @returns
*/
async getNodes() {
return this._index.FlatReferences
}
/**
*
* @param node
* @returns
*/
async getNodeLink(node: any) {
const position = await this.positionOfDefinition(node)
if (position) {
const filename = this.lastCompilationResult.getSourceName(position.file)
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn',
position,
position.file,
this.lastCompilationResult.getSourceCode().sources,
this.lastCompilationResult.getAsts())
return `${filename} ${lineColumn.start.line}:${lineColumn.start.column}`
}
}
/**
*
* @param node
* @returns
*/
async getNodeDocumentation(node: any) {
if (node.documentation && node.documentation.text) {
let text = ''
node.documentation.text.split('\n').forEach(line => {
text += `${line.trim()}\n`
})
return text
}
}
/**
*
* @param node
* @returns
*/
async getVariableDeclaration(node: any) {
if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else
if (node.typeName && node.typeName.name) {
return `${node.typeName.name}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else {
return `${node.name && node.name.length ? ` ${node.name}` : ''}`
}
}
/**
*
* @param node
* @returns
*/
async getFunctionParamaters(node: any) {
let localParam = (node.parameters && node.parameters.parameters) || (node.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
/**
*
* @param node
* @returns
*/
async getFunctionReturnParameters(node: any) {
let localParam = (node.returnParameters && node.returnParameters.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
}

@ -1,36 +1,18 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { Compiler } from '@remix-project/remix-solidity'
import { CompilationError, CompilationResult, CompilationSource, helper } from '@remix-project/remix-solidity-ts'
const profile = {
name: 'contextualListener',
methods: ['getBlockName', 'getLastNodeInLine', 'resolveImports', 'parseSource', 'getAST', 'nodesWithScope', 'nodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'],
methods: ['getActiveHighlights', 'gasEstimation', 'jumpToPosition', 'jumpToDefinition'],
events: [],
version: '0.0.1'
}
export function isDefinition(node: any) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
/*
trigger contextChanged(nodes)
*/
export class EditorContextListener extends Plugin {
_index: any
_activeHighlights: Array<any>
astWalker: any
currentPosition: any
@ -42,18 +24,11 @@ export class EditorContextListener extends Plugin {
codeDepositCost: any
contract: any
activated: boolean
lastCompilationResult: any
lastAST: any
compiler: any
onAstFinished: (success: any, data: any, source: any, input: any, version: any) => Promise<void>
constructor(astWalker) {
super(profile)
this.activated = false
this._index = {
Declarations: {},
FlatReferences: {}
}
this._activeHighlights = []
this.astWalker = astWalker
@ -62,97 +37,15 @@ export class EditorContextListener extends Plugin {
async onActivation() {
this.on('editor', 'contentChanged', async () => {
console.log('contentChanged')
await this.getAST()
await this.compile()
this._stopHighlighting()
})
this.on('fileManager', 'currentFileChanged', async () => {
await this.getAST()
await this.compile()
this._stopHighlighting()
})
this.on('solidity', 'loadingCompiler', async (url) => {
console.log('loading compiler', url)
this.compiler.loadVersion(true, url)
this.compiler.event.register('compilerLoaded', async () => {
console.log('compiler loaded')
})
})
this.compiler = new Compiler((url, cb) => this.call('contentImport', 'resolveAndSave', url, undefined, false).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => {
console.log('compile success', success, data)
this.call('editor', 'clearAnnotations')
let noFatalErrors = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => {
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning'
if (isValidError) {
console.log(error)
noFatalErrors = false
}
}
const result = new CompilerAbstract('soljson', data, source, input)
if (data.error) checkIfFatalError(data.error)
if (data.errors) data.errors.forEach((err) => checkIfFatalError(err))
const allErrors = []
if (data.errors) {
for (const error of data.errors) {
console.log('ERROR POS', error)
let pos = helper.getPositionDetails(error.formattedMessage)
console.log('ERROR POS', pos)
const sources = result.getSourceCode().sources
const source = sources[pos.file]
const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn',
{
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
},
0,
sources,
null)
console.log('lineColumn', lineColumn)
allErrors.push({ error, lineColumn })
}
await this.call('editor', 'addErrorMarker', allErrors)
} else {
await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.lastCompilationResult = new CompilerAbstract('soljson', data, source, input)
this._stopHighlighting()
this._index = {
Declarations: {},
FlatReferences: {}
}
this._buildIndex(data, source)
this.emit('astFinished')
}
this.compiler.event.register('astFinished', this.onAstFinished)
setInterval(async () => {
//await this.compile()
}, 1000)
setInterval(async () => {
return
const compilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult')
const compilationResult = await this.call('codeParser', 'getLastCompilationResult')
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let currentFile
@ -170,245 +63,20 @@ export class EditorContextListener extends Plugin {
}, 1000)
}
async getLastCompilationResult() {
return this.lastCompilationResult
}
async compile() {
console.log('compile')
try {
const state = await this.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', state.useFileConfiguration)
this.currentFile = await this.call('fileManager', 'file')
if (!this.currentFile) return
const content = await this.call('fileManager', 'readFile', this.currentFile)
// console.log('compile', this.currentFile, content)
const sources = { [this.currentFile]: { content } }
this.compiler.compile(sources, this.currentFile)
} catch (e) {
console.log(e)
}
}
async resolveImports(node, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
console.log('IMPORTING', node)
const importNode = await this.getNodeById(node.sourceUnit)
imported[importNode.id] = importNode
if (importNode.nodes) {
for (const child of importNode.nodes) {
imported = await this.resolveImports(child, imported)
}
}
}
console.log(imported)
return imported
}
async getBlockName(position: any, text: string = null) {
await this.getAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
const walkAst = (node) => {
console.log(node)
if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) {
const children = node.children || node.subNodes
if (children && allowedTypes.indexOf(node.type) !== -1) {
for (const child of children) {
const result = walkAst(child)
if (result) return result
}
}
return node
}
return null
}
if (!this.lastAST) return
return walkAst(this.lastAST)
}
async getAST(text: string = null) {
this.currentFile = await this.call('fileManager', 'file')
if (!this.currentFile) return
const fileContent = text || await this.call('fileManager', 'readFile', this.currentFile)
try {
const ast = await this.parseSource(fileContent)
this.lastAST = ast
console.log('AST PARSE SUCCESS', ast)
} catch (e) {
console.log(e)
}
console.log('LAST PARSER AST', this.lastAST)
return this.lastAST
}
async parseSource(text: string) {
//console.log('PARSING', text)
const ast = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
console.log('AST PARSE SUCCESS', ast)
return ast
}
async getLastNodeInLine(ast: string) {
let lastNode
const checkLastNode = (node) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, {
MemberAccess: function (node) {
checkLastNode(node)
},
Identifier: function (node) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression) {
console.log('lastNode', lastNode.expression)
return lastNode.expression
}
console.log('lastNode', lastNode)
return lastNode
}
getActiveHighlights() {
return [...this._activeHighlights]
}
declarationOf(node) {
if (node && node.referencedDeclaration) {
return this._index.FlatReferences[node.referencedDeclaration]
} else {
// console.log(this._index.FlatReferences)
}
return null
}
referencesOf(node: any) {
const results = []
const highlights = (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
for (const ref in refs) {
const node = refs[ref]
results.push(node)
}
}
}
if (node && node.referencedDeclaration) {
highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[node.referencedDeclaration]
results.push(current)
} else {
highlights(node.id)
}
return results
}
async nodesAtEditorPosition(position: any, type = '') {
const lastCompilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult')
if (!lastCompilationResult) return false
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
return nodes
}
return []
}
async referrencesAtPosition(position: any) {
const nodes = await this.nodesAtEditorPosition(position)
if (nodes && nodes.length) {
const node = nodes[nodes.length - 1]
if (node) {
return this.referencesOf(node)
}
}
}
async getNodeById(id: any) {
for (const key in this._index.FlatReferences) {
if (this._index.FlatReferences[key].id === id) {
return this._index.FlatReferences[key]
}
}
}
async nodesWithScope(scope: any) {
const nodes = []
for (const node of Object.values(this._index.FlatReferences) as any[]) {
if (node.scope === scope) nodes.push(node)
}
return nodes
}
async nodesWithName(name: string) {
const nodes = []
for (const node of Object.values(this._index.FlatReferences) as any[]) {
if (node.name === name) nodes.push(node)
}
return nodes
}
async definitionAtPosition(position: any) {
const nodes = await this.nodesAtEditorPosition(position)
console.log('nodes at position', nodes)
console.log(this._index.FlatReferences)
let nodeDefinition: any
let node: any
if (nodes && nodes.length) {
node = nodes[nodes.length - 1]
nodeDefinition = node
if (!isDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node
}
if (node.nodeType === 'ImportDirective') {
for (const key in this._index.FlatReferences) {
if (this._index.FlatReferences[key].id === node.sourceUnit) {
nodeDefinition = this._index.FlatReferences[key]
}
}
}
return nodeDefinition
} else {
return false
}
}
async positionOfDefinition(node: any) {
if (node) {
if (node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
return position
}
}
}
return null
}
async jumpToDefinition(position: any) {
const node = await this.definitionAtPosition(position)
const sourcePosition = await this.positionOfDefinition(node)
const node = await this.call('codeParser', 'definitionAtPosition', position)
const sourcePosition = await this.call('codeParser', 'positionOfDefinition', node)
console.log("JUMP", sourcePosition)
if (sourcePosition) {
await this.jumpToPosition(sourcePosition)
}
}
async getNodes() {
return this._index.FlatReferences
}
/*
* onClick jump to position of ast node in the editor
@ -424,7 +92,7 @@ export class EditorContextListener extends Plugin {
this.call('editor', 'gotoLine', lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult')
const lastCompilationResult = await this.call('codeParser', 'getLastCompilationResult') // await this.call('compilerArtefacts', 'getLastCompilationResult')
console.log(lastCompilationResult.getSourceCode().sources)
console.log(position)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
@ -456,23 +124,6 @@ export class EditorContextListener extends Plugin {
}
}
_buildIndex(compilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node) => {
if (node && node.referencedDeclaration) {
if (!this._index.Declarations[node.referencedDeclaration]) {
this._index.Declarations[node.referencedDeclaration] = []
}
this._index.Declarations[node.referencedDeclaration].push(node)
}
this._index.FlatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
async _highlight(node, compilationResult) {
if (!node) return
const position = sourceMappingDecoder.decode(node.src)
@ -512,9 +163,10 @@ export class EditorContextListener extends Plugin {
}
async _highlightExpressions(node, compilationResult) {
const highlights = async (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
let refs = await this.call('codeParser', 'getDeclaration', id)
if (refs) {
for (const ref in refs) {
const node = refs[ref]
await this._highlight(node, compilationResult)
@ -523,7 +175,7 @@ export class EditorContextListener extends Plugin {
}
if (node && node.referencedDeclaration) {
await highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[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)
@ -531,6 +183,7 @@ export class EditorContextListener extends Plugin {
}
this.results = compilationResult
}
_stopHighlighting() {
@ -563,6 +216,7 @@ export class EditorContextListener extends Plugin {
}
_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) {

@ -1,3 +1,4 @@
import { isArray } from "lodash"
import { editor, languages, Position } from "monaco-editor"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor"
@ -39,114 +40,53 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
if (context.triggerCharacter === '.') {
console.clear()
console.log('TEXT', line)
const textBeforeCursor = line.substring(0, position.column - 1)
const textAfterCursor = line.substring(position.column - 1)
// parse the line witout changing the line
const wrapLineInFunction = (text: string) => {
return `function() {
${text}
}`
}
let lastNode
const checkLastNode = (node) => {
if (!node) return
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
const lineTextBeforeCursor = line.substring(0, position.column - 1)
const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor)
console.log('lastNode found', lastNodeInExpression)
console.log(lineTextBeforeCursor)
const expressionElements = lineTextBeforeCursor.split('.')
const linesToCheck =
[
textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode;",
textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode;}",
textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode);",
wrapLineInFunction(textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode;"),
wrapLineInFunction(textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode;}"),
wrapLineInFunction(textBeforeCursor.substring(0, textBeforeCursor.lastIndexOf('.')) + ".lastnode;)"),
]
for (const line of linesToCheck) {
try {
const lineAst = await this.props.plugin.call('contextualListener', 'parseSource', line)
const lastNode = await this.props.plugin.call('contextualListener', 'getLastNodeInLine', lineAst)
checkLastNode(lastNode)
} catch (e) {
}
}
console.log('lastNode found', lastNode)
console.log(textBeforeCursor, textAfterCursor)
const splits = textBeforeCursor.split('.')
console.log('splits', splits)
console.log('expression elements', expressionElements)
let dotCompleted = false
if (splits.length === 2) {
let globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNode.name, range, this.monaco)
if (expressionElements.length === 2) {
let globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco)
if (globalCompletion) {
dotCompleted = true
suggestions = [...suggestions, ...globalCompletion]
}
if (lastNode.name === 'this') {
if (lastNodeInExpression.name === 'this') {
dotCompleted = true
nodes = [...nodes, ...await this.getContractCompletions(nodes, position)]
}
}
if (splits.length > 1 && !dotCompleted) {
let last = splits[splits.length - 2].trim()
const lastParentheses = last.lastIndexOf('(')
const lastClosingParentheses = last.lastIndexOf(')')
const lastBracket = last.lastIndexOf('{')
const lastSemiColon = last.lastIndexOf(';')
let textBefore = null
let lineWithoutEdits = null
// get word before last closing parentheses
if (lastParentheses > -1 && lastClosingParentheses > -1) {
//textBefore = last.substring(0, lastParentheses)
}
// find largest
const lastIndex = Math.max(lastParentheses, lastBracket, lastSemiColon)
if (lastIndex > -1) {
textBefore = last.substring(0, lastIndex + 1)
console.log('textBefore', textBefore)
console.log('text without edits', textBefore, textAfterCursor)
lineWithoutEdits = `${textBefore}${textAfterCursor}`
}
last = lastNode.name || lastNode.memberName
console.log('last', last)
const lines = model.getLinesContent()
lines[position.lineNumber - 1] = lineWithoutEdits
if (expressionElements.length > 1 && !dotCompleted) {
const textWithoutEdits = lines.join('\n')
console.log('textWithoutEdits', textWithoutEdits)
const last = lastNodeInExpression.name || lastNodeInExpression.memberName
console.log('last', last)
let nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', cursorPosition)
let nodesAtPosition;
console.log('NODES AT POSITION', nodesAtPosition)
const block = await this.props.plugin.call('contextualListener', 'getBlockName', position, textWithoutEdits)
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null)
console.log('BLOCK', block)
//if (!nodesAtPosition.length) {
if (block) {
nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', block.body ? block.body.range[0] : block.range[0])
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.body ? block.body.range[0] : block.range[0])
console.log('NODES AT POSITION WITH BLOCK', nodesAtPosition)
} else{
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
console.log('NODES AT POSITION', nodesAtPosition)
}
//}
// explore nodes at the BLOCK
if (nodesAtPosition) {
for (const node of nodesAtPosition) {
const nodesOfScope = await this.props.plugin.call('contextualListener', 'nodesWithScope', node.id)
const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
console.log('NODES OF SCOPE ', node.name, node.id, nodesOfScope)
for (const nodeOfScope of nodesOfScope) {
if (nodeOfScope.name === last) {
console.log('FOUND NODE', nodeOfScope)
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf = await this.props.plugin.call('contextualListener', 'declarationOf', nodeOfScope.typeName)
const declarationOf = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
console.log('HAS DECLARATION OF', declarationOf)
nodes = [...nodes, ...declarationOf.nodes || declarationOf.members]
const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
@ -163,9 +103,9 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
if (node.statements) {
for (const statement of node.statements) {
if (statement.expression && statement.expression.memberName === last) {
const declarationOf = await this.props.plugin.call('contextualListener', 'declarationOf', statement.expression)
const declarationOf = await this.props.plugin.call('codeParser', 'declarationOf', statement.expression)
if (declarationOf.typeName && declarationOf.typeName.nodeType === 'UserDefinedTypeName') {
const baseDeclaration = await this.props.plugin.call('contextualListener', 'declarationOf', declarationOf.typeName)
const baseDeclaration = await this.props.plugin.call('codeParser', 'declarationOf', declarationOf.typeName)
console.log('HAS BASE DECLARATION OF', baseDeclaration)
nodes = [...nodes, ...baseDeclaration.nodes || baseDeclaration.members]
}
@ -178,13 +118,13 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
// brute force search in all nodes with the name
if (!nodes.length) {
const nodesOfScope = await this.props.plugin.call('contextualListener', 'nodesWithName', last)
const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithName', last)
console.log('NODES WITHE NAME ', last, nodesOfScope)
for (const nodeOfScope of nodesOfScope) {
if (nodeOfScope.name === last) {
console.log('FOUND NODE', nodeOfScope)
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') {
const declarationOf = await this.props.plugin.call('contextualListener', 'declarationOf', nodeOfScope.typeName)
const declarationOf = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName)
console.log('HAS DECLARATION OF', declarationOf)
// nodes = [...nodes, ...declarationOf.nodes || declarationOf.members]
const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
@ -213,43 +153,24 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
console.log('WORD', word, wordAt)
console.log('NODES', nodes)
const getLinks = async (node: any) => {
const position = await this.props.plugin.call('contextualListener', 'positionOfDefinition', node)
const lastCompilationResult = await this.props.plugin.call('contextualListener', 'getLastCompilationResult')
const filename = lastCompilationResult.getSourceName(position.file)
const lineColumn = await this.props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn',
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
return `${filename} ${lineColumn.start.line}:${lineColumn.start.column}`
const getNodeLink = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getNodeLink', node)
}
const getDocs = async (node: any) => {
if (node.documentation && node.documentation.text) {
let text = ''
node.documentation.text.split('\n').forEach(line => {
text += `${line.trim()}\n`
})
return text
}
return await this.props.plugin.call('codeParser', 'getNodeDocumentation', node)
}
const getParamaters = async (parameters: any) => {
if (parameters && parameters.parameters) {
const params = []
for (const param of parameters.parameters) {
params.push(await getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
const getParamaters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node)
}
const completeParameters = async (parameters: any) => {
if (parameters && parameters.parameters) {
let localParam = ( parameters && parameters.parameters ) || (parameters)
if (localParam) {
const params = []
for (const key in parameters.parameters) {
params.push('${' + (key + 1) + ':' + parameters.parameters[key].name + '}')
for (const key in localParam) {
params.push('${' + (key + 1) + ':' + localParam[key].name + '}')
}
return `(${params.join(', ')})`
}
@ -257,14 +178,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
const getVariableDeclaration = async (node: any) => {
if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else
if (node.typeName && node.typeName.name) {
return `${node.typeName.name}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else {
return `${node.name && node.name.length ? ` ${node.name}` : ''}`
}
return await this.props.plugin.call('codeParser', 'getVariableDeclaration', node)
}
@ -272,7 +186,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
if (!node.name) continue
if (node.nodeType === 'VariableDeclaration') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${await getVariableDeclaration(node)}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${await getVariableDeclaration(node)}` },
kind: this.monaco.languages.CompletionItemKind.Variable,
insertText: node.name,
range: range,
@ -282,7 +196,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if (node.nodeType === 'FunctionDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` -> ${node.name} ${await getParamaters(node.parameters)}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` },
kind: this.monaco.languages.CompletionItemKind.Function,
insertText: `${node.name}${await completeParameters(node.parameters)};`,
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
@ -293,7 +207,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if
(node.nodeType === 'ContractDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Interface,
insertText: node.name,
range: range,
@ -303,7 +217,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if
(node.nodeType === 'StructDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Struct,
insertText: node.name,
range: range,
@ -313,7 +227,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if
(node.nodeType === 'EnumDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Enum,
insertText: node.name,
range: range,
@ -323,7 +237,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if
(node.nodeType === 'EventDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Event,
insertText: node.name,
range: range,
@ -333,7 +247,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} else if
(node.nodeType === 'ModifierDefinition') {
const completion = {
label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` },
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` },
kind: this.monaco.languages.CompletionItemKind.Method,
insertText: node.name,
range: range,
@ -345,18 +259,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
}
}
suggestions = [...suggestions,
/* ...GetGlobalVariable(range, this.monaco),
...getCompletionSnippets(range, this.monaco),
...getBlockCompletionItems(range, this.monaco),
...GetCompletionTypes(range,this.monaco),
...GetCompletionKeywords(range,this.monaco),
...GetGlobalFunctions(range,this.monaco),
...GeCompletionUnits(range,this.monaco),
...getMsgCompletionItems(range,this.monaco),
...getTxCompletionItems(range,this.monaco), */
]
console.log(suggestions)
return {
suggestions
@ -368,7 +270,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) {
if (id !== node.id) {
const baseContract = await this.props.plugin.call('contextualListener', 'getNodeById', id)
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id)
params = [...params, ...[baseContract]]
}
}
@ -378,37 +280,86 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
private getContractCompletions = async (nodes: any[], position: Position) => {
const cursorPosition = this.props.editorAPI.getCursorPosition()
let nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', cursorPosition)
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
// if no nodes exits at position, try to get the block of which the position is in
if (!nodesAtPosition.length) {
const block = await this.props.plugin.call('contextualListener', 'getBlockName', position, null)
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null)
if (block) {
nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', block.range[0])
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0])
}
}
// get all children of all nodes at position
for (const node of nodesAtPosition) {
const nodesOfScope = await this.props.plugin.call('contextualListener', 'nodesWithScope', node.id)
for (const nodeOfScope of nodesOfScope) {
const imports = await this.props.plugin.call('contextualListener', 'resolveImports', nodeOfScope)
if (imports) {
for (const key in imports) {
if (imports[key].nodes)
nodes = [...nodes, ...imports[key].nodes]
if (isArray(nodesAtPosition)) {
for (const node of nodesAtPosition) {
const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
for (const nodeOfScope of nodesOfScope) {
const imports = await this.props.plugin.call('codeParser', 'resolveImports', nodeOfScope)
if (imports) {
for (const key in imports) {
if (imports[key].nodes)
nodes = [...nodes, ...imports[key].nodes]
}
}
}
nodes = [...nodes, ...nodesOfScope]
}
nodes = [...nodes, ...nodesOfScope]
}
// get the linearized base contracts
for (const node of nodesAtPosition) {
const baseContracts = await this.getlinearizedBaseContracts(node)
for (const baseContract of baseContracts) {
nodes = [...nodes, ...baseContract.nodes]
// get the linearized base contracts
for (const node of nodesAtPosition) {
const baseContracts = await this.getlinearizedBaseContracts(node)
for (const baseContract of baseContracts) {
nodes = [...nodes, ...baseContract.nodes]
}
}
} else {
// get all the nodes from a simple code parser which only parses the current file
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')]
}
return nodes
}
/**
*
* @param lineTextBeforeCursor
* @returns
*/
private async getLastNodeInExpression(lineTextBeforeCursor: string) {
const wrapLineInFunction = async(text: string) => {
return `function() {
${text}
}`
}
let lastNodeInExpression
const linesToCheck =
[
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;",
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}",
lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);",
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;}"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode;)"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode)"),
await wrapLineInFunction(lineTextBeforeCursor.substring(0, lineTextBeforeCursor.lastIndexOf('.')) + ".lastnode);"),
]
for (const line of linesToCheck) {
try {
const lineAst = await this.props.plugin.call('codeParser', 'parseSolidity', line)
const lastNode = await this.props.plugin.call('codeParser', 'getLastNodeInLine', lineAst)
if(lastNode) {
lastNodeInExpression = lastNode
break
}
} catch (e) {
}
}
return lastNodeInExpression
}
}

@ -13,24 +13,17 @@ export class RemixHoverProvider implements languages.HoverProvider {
provideHover = async function (model: editor.ITextModel, position: Position) {
console.log('HOVERING')
const cursorPosition = this.props.editorAPI.getHoverPosition(position)
const nodeAtPosition = await this.props.plugin.call('contextualListener', 'definitionAtPosition', cursorPosition)
const nodeAtPosition = await this.props.plugin.call('codeParser', 'definitionAtPosition', cursorPosition)
console.log(nodeAtPosition)
const contents = []
const getDocs = async (node: any) => {
if (node.documentation && node.documentation.text) {
let text = ''
node.documentation.text.split('\n').forEach(line => {
text += `${line.trim()}\n`
})
contents.push({
value: text
})
}
contents.push({
value: await this.props.plugin.call('codeParser', 'getNodeDocumentation', node)
})
}
const getScope = async (node: any) => {
@ -48,42 +41,25 @@ export class RemixHoverProvider implements languages.HoverProvider {
}
const getLinks = async (node: any) => {
const position = await this.props.plugin.call('contextualListener', 'positionOfDefinition', node)
const lastCompilationResult = await this.props.plugin.call('contextualListener', 'getLastCompilationResult')
const filename = lastCompilationResult.getSourceName(position.file)
console.log(filename, position)
const lineColumn = await this.props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn',
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
contents.push({
value: `${filename} ${lineColumn.start.line}:${lineColumn.start.column}`
value: await this.props.plugin.call('codeParser', 'getNodeLink', node)
})
}
const getVariableDeclaration = async (node: any) => {
if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else
if (node.typeName && node.typeName.name) {
return `${node.typeName.name}${node.name && node.name.length ? ` ${node.name}` : ''}`
} else {
return `${node.name && node.name.length ? ` ${node.name}` : ''}`
}
return await this.props.plugin.call('codeParser', 'getVariableDeclaration', node)
}
const getParamaters = async (parameters: any) => {
if (parameters && parameters.parameters) {
let params = []
for (const param of parameters.parameters) {
params.push(await getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
const getParamaters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionParamaters', node)
}
const getReturnParameters = async (node: any) => {
return await this.props.plugin.call('codeParser', 'getFunctionReturnParameters', node)
}
const getOverrides = async (node: any) => {
if (node.overrides) {
let overrides = []
@ -99,7 +75,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
const getlinearizedBaseContracts = async (node: any) => {
let params = []
for (const id of node.linearizedBaseContracts) {
const baseContract = await this.props.plugin.call('contextualListener', 'getNodeById', id)
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id)
params.push(
baseContract.name
)
@ -112,6 +88,8 @@ export class RemixHoverProvider implements languages.HoverProvider {
if (!nodeAtPosition) {
contents.push({
value: 'Loading...'
}, {
value: 'Compilation errors'
})
}
@ -140,7 +118,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
} else if (nodeAtPosition.nodeType === 'FunctionDefinition') {
contents.push({
value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition.parameters)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} returns ${await getParamaters(nodeAtPosition.returnParameters)}`
value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} returns ${await getReturnParameters(nodeAtPosition)}`
})
@ -149,7 +127,10 @@ export class RemixHoverProvider implements languages.HoverProvider {
value: `${nodeAtPosition.contractKind} ${nodeAtPosition.name} ${await getlinearizedBaseContracts(nodeAtPosition)}`
})
} else if (nodeAtPosition.nodeType === 'InvalidNode') {
contents.push({
value: `Parsing error found!`
})
} else {
contents.push({
value: `${nodeAtPosition.nodeType}`

@ -11,10 +11,10 @@ export class RemixReferenceProvider {
async provideReferences(model: any, position: any, context: any, token: any) {
const cursorPosition = this.props.editorAPI.getCursorPosition()
const nodes = await this.props.plugin.call('contextualListener', 'referrencesAtPosition', cursorPosition)
const nodes = await this.props.plugin.call('codeParser', 'referrencesAtPosition', cursorPosition)
const references = []
if (nodes && nodes.length) {
const compilationResult = await this.props.plugin.call('contextualListener', 'getLastCompilationResult')
const compilationResult = await this.props.plugin.call('codeParser', 'getLastCompilationResult')
const file = await this.props.plugin.call('fileManager', 'file')
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) {
for (const node of nodes) {

@ -74,7 +74,7 @@ export interface EditorUIProps {
findMatches: (uri: string, value: string) => any
getFontSize: () => number,
getValue: (uri: string) => string
getCursorPosition: () => cursorPosition
getCursorPosition: () => number
getHoverPosition: (position: IPosition) => number
addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn
addErrorMarker: (errors: []) => void
@ -541,7 +541,7 @@ export const EditorUI = (props: EditorUIProps) => {
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('contextualListener', 'declarationOf', node) }}
declarationOf={(node: astNode) => { return props.plugin.call('codeParser', 'declarationOf', node) }}
/>
</div>
</div>

@ -7249,15 +7249,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001030, caniuse-lite@^1.0.30001264:
version "1.0.30001265"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3"
integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==
caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286:
version "1.0.30001287"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001287.tgz#5fab6a46ab9e47146d5dd35abfe47beaf8073c71"
integrity sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001030, caniuse-lite@^1.0.30001264, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286:
version "1.0.30001356"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001356.tgz"
integrity sha512-/30854bktMLhxtjieIxsrJBfs2gTM1pel6MXKF3K+RdIVJZcsn2A2QdhsuR4/p9+R204fZw0zCBBhktX8xWuyQ==
capital-case@^1.0.4:
version "1.0.4"
@ -9550,7 +9545,7 @@ ent@~2.2.0:
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
entities@^2.0.0:
entities@^2.0.0, entities@^2.0.3:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
@ -14921,7 +14916,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2
emojis-list "^3.0.0"
json5 "^1.0.1"
loader-utils@^2.0.0, loader-utils@^2.0.2:
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
@ -16142,13 +16137,6 @@ mold-source-map@~0.4.0:
convert-source-map "^1.1.0"
through "~2.2.7"
monaco-editor-webpack-plugin@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz#ba19c60aba990184e36ad8722b1ed6a564527c7c"
integrity sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw==
dependencies:
loader-utils "^2.0.2"
monaco-editor@^0.30.1:
version "0.30.1"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.30.1.tgz#47f8d18a0aa2264fc5654581741ab8d7bec01689"
@ -18230,6 +18218,11 @@ pbkdf2@^3.0.17, pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
pegjs@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd"
integrity sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@ -20209,6 +20202,14 @@ rollup@1.31.1:
"@types/node" "*"
acorn "^7.1.0"
rss-parser@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.12.0.tgz#b8888699ea46304a74363fbd8144671b2997984c"
integrity sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==
dependencies:
entities "^2.0.3"
xml2js "^0.4.19"
rsvp@^4.8.4:
version "4.8.5"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
@ -20339,7 +20340,7 @@ sax@0.5.x:
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=
sax@~1.2.4:
sax@>=0.6.0, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@ -20931,6 +20932,14 @@ solc@0.7.4:
semver "^5.5.0"
tmp "0.0.33"
solparse-exp-jb@^2.2.23:
version "2.2.23"
resolved "https://registry.yarnpkg.com/solparse-exp-jb/-/solparse-exp-jb-2.2.23.tgz#a46facb5546bc37ef64de4f1ce17b6ccb56d7ed4"
integrity sha512-LCU3C6IKrXu+C8ORBuNGD0JHyxhwNsSqF65YqnqGXrCi8KjfOQcrA4gliGZp/E0jtQl43O19as/ay1NnzLvOpQ==
dependencies:
pegjs "^0.10.0"
yargs "^10.0.3"
sort-keys@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
@ -24093,6 +24102,19 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xml2js@^0.4.19:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"

Loading…
Cancel
Save