|
|
@ -1,3 +1,4 @@ |
|
|
|
|
|
|
|
import { sourceMappingDecoder } from "@remix-project/remix-debug" |
|
|
|
import { AstNode } from "@remix-project/remix-solidity-ts" |
|
|
|
import { AstNode } from "@remix-project/remix-solidity-ts" |
|
|
|
import { isArray } from "lodash" |
|
|
|
import { isArray } from "lodash" |
|
|
|
import { editor, languages, Position } from "monaco-editor" |
|
|
|
import { editor, languages, Position } from "monaco-editor" |
|
|
@ -13,17 +14,13 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
constructor(props: any, monaco: any) { |
|
|
|
constructor(props: any, monaco: any) { |
|
|
|
this.props = props |
|
|
|
this.props = props |
|
|
|
this.monaco = monaco |
|
|
|
this.monaco = monaco |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
triggerCharacters = ['.', ''] |
|
|
|
triggerCharacters = ['.', ''] |
|
|
|
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> { |
|
|
|
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> { |
|
|
|
console.log('AUTOCOMPLETE', context) |
|
|
|
console.log('AUTOCOMPLETE', context) |
|
|
|
console.log(position) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const word = model.getWordUntilPosition(position); |
|
|
|
const word = model.getWordUntilPosition(position); |
|
|
|
const wordAt = model.getWordAtPosition(position); |
|
|
|
|
|
|
|
const range = { |
|
|
|
const range = { |
|
|
|
startLineNumber: position.lineNumber, |
|
|
|
startLineNumber: position.lineNumber, |
|
|
|
endLineNumber: position.lineNumber, |
|
|
|
endLineNumber: position.lineNumber, |
|
|
@ -35,20 +32,18 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
let nodes: AstNode[] = [] |
|
|
|
let nodes: AstNode[] = [] |
|
|
|
let suggestions: monaco.languages.CompletionItem[] = [] |
|
|
|
let suggestions: monaco.languages.CompletionItem[] = [] |
|
|
|
|
|
|
|
|
|
|
|
const cursorPosition: number = this.props.editorAPI.getCursorPosition() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (context.triggerCharacter === '.') { |
|
|
|
if (context.triggerCharacter === '.') { |
|
|
|
console.clear() |
|
|
|
console.clear() |
|
|
|
console.log('TEXT', line) |
|
|
|
|
|
|
|
const lineTextBeforeCursor: string = line.substring(0, position.column - 1) |
|
|
|
const lineTextBeforeCursor: string = line.substring(0, position.column - 1) |
|
|
|
const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) |
|
|
|
const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) |
|
|
|
console.log('lastNode found', lastNodeInExpression) |
|
|
|
console.log('lastNode found', lastNodeInExpression) |
|
|
|
console.log(lineTextBeforeCursor) |
|
|
|
|
|
|
|
const expressionElements = lineTextBeforeCursor.split('.') |
|
|
|
const expressionElements = lineTextBeforeCursor.split('.') |
|
|
|
|
|
|
|
|
|
|
|
console.log('expression elements', expressionElements) |
|
|
|
|
|
|
|
let dotCompleted = false |
|
|
|
let dotCompleted = false |
|
|
|
//if (expressionElements.length === 2) {
|
|
|
|
|
|
|
|
|
|
|
|
// handles completion from for builtin types
|
|
|
|
const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) |
|
|
|
const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) |
|
|
|
if (globalCompletion) { |
|
|
|
if (globalCompletion) { |
|
|
|
dotCompleted = true |
|
|
|
dotCompleted = true |
|
|
@ -58,115 +53,21 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
// debugger
|
|
|
|
// debugger
|
|
|
|
}, 2000) |
|
|
|
}, 2000) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// handle completion for global THIS.
|
|
|
|
if (lastNodeInExpression.name === 'this') { |
|
|
|
if (lastNodeInExpression.name === 'this') { |
|
|
|
dotCompleted = true |
|
|
|
dotCompleted = true |
|
|
|
let thisCompletionNodes = await this.getContractCompletions(nodes, position) |
|
|
|
nodes = [...nodes, ...await this.getThisCompletions(position)] |
|
|
|
|
|
|
|
|
|
|
|
// with this. you can't have internal nodes and no contractDefinitions
|
|
|
|
|
|
|
|
thisCompletionNodes = thisCompletionNodes.filter(node => { |
|
|
|
|
|
|
|
if (node.visibility && node.visibility === 'internal') { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (node.nodeType && node.nodeType === 'ContractDefinition') { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
nodes = [...nodes, ...thisCompletionNodes] |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
|
|
|
// debugger
|
|
|
|
|
|
|
|
}, 2000) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
//}
|
|
|
|
// handle completion for other dot completions
|
|
|
|
if (expressionElements.length > 1 && !dotCompleted) { |
|
|
|
if (expressionElements.length > 1 && !dotCompleted) { |
|
|
|
|
|
|
|
|
|
|
|
const last = lastNodeInExpression.name || lastNodeInExpression.memberName |
|
|
|
const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName |
|
|
|
console.log('last', last) |
|
|
|
nodes = [...nodes, ...await this.getDotCompletions(position, nameOfLastTypedExpression)] |
|
|
|
|
|
|
|
|
|
|
|
let nodesAtPosition; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null) |
|
|
|
|
|
|
|
console.log('BLOCK', block) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (block) { |
|
|
|
|
|
|
|
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: AstNode[] = 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: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName) |
|
|
|
|
|
|
|
console.log('METHOD 1 HAS DECLARATION OF', declarationOf) |
|
|
|
|
|
|
|
nodes = [...nodes, ...declarationOf.nodes || declarationOf.members] |
|
|
|
|
|
|
|
const baseContracts = await this.getlinearizedBaseContracts(declarationOf) |
|
|
|
|
|
|
|
for (const baseContract of baseContracts) { |
|
|
|
|
|
|
|
nodes = [...nodes, ...baseContract.nodes] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// anything within the block statements might provide a clue to what it is
|
|
|
|
|
|
|
|
// if (!nodes.length) {
|
|
|
|
|
|
|
|
for (const node of nodesAtPosition) { |
|
|
|
|
|
|
|
if (node.statements) { |
|
|
|
|
|
|
|
for (const statement of node.statements) { |
|
|
|
|
|
|
|
if (statement.expression && statement.expression.memberName === last) { |
|
|
|
|
|
|
|
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('codeParser', 'declarationOf', declarationOf.typeName) |
|
|
|
|
|
|
|
console.log('METHOD 2 HAS BASE DECLARATION OF', baseDeclaration) |
|
|
|
|
|
|
|
nodes = [...nodes, ...baseDeclaration.nodes || baseDeclaration.members] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// brute force search in all nodes with the name
|
|
|
|
|
|
|
|
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('codeParser', 'declarationOf', nodeOfScope.typeName) |
|
|
|
|
|
|
|
console.log('METHOD 3 HAS DECLARATION OF', declarationOf) |
|
|
|
|
|
|
|
nodes = [...nodes, ...declarationOf.nodes || declarationOf.members] |
|
|
|
|
|
|
|
//const baseContracts = await this.getlinearizedBaseContracts(declarationOf)
|
|
|
|
|
|
|
|
//for (const baseContract of baseContracts) {
|
|
|
|
|
|
|
|
//nodes = [...nodes, ...baseContract.nodes]
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const filterNodeTypes = ['ContractDefinition', 'ModifierDefinition', 'EventDefinition'] |
|
|
|
|
|
|
|
nodes = nodes.filter(node => { |
|
|
|
|
|
|
|
if (node.visibility && node.visibility === 'private') { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (node.nodeType && filterNodeTypes.indexOf(node.nodeType) !== -1) { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// handles contract completions and other suggestions
|
|
|
|
suggestions = [...suggestions, |
|
|
|
suggestions = [...suggestions, |
|
|
|
...GetGlobalVariable(range, this.monaco), |
|
|
|
...GetGlobalVariable(range, this.monaco), |
|
|
|
...getCompletionSnippets(range, this.monaco), |
|
|
|
...getCompletionSnippets(range, this.monaco), |
|
|
@ -175,19 +76,18 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
...GetGlobalFunctions(range, this.monaco), |
|
|
|
...GetGlobalFunctions(range, this.monaco), |
|
|
|
...GeCompletionUnits(range, this.monaco), |
|
|
|
...GeCompletionUnits(range, this.monaco), |
|
|
|
] |
|
|
|
] |
|
|
|
let thisCompletionNodes = await this.getContractCompletions(nodes, position) |
|
|
|
let contractCompletions = await this.getContractCompletions(position) |
|
|
|
|
|
|
|
|
|
|
|
// we can't have external nodes without using this.
|
|
|
|
// we can't have external nodes without using this.
|
|
|
|
thisCompletionNodes = thisCompletionNodes.filter(node => { |
|
|
|
contractCompletions = contractCompletions.filter(node => { |
|
|
|
if (node.visibility && node.visibility === 'external') { |
|
|
|
if (node.visibility && node.visibility === 'external') { |
|
|
|
return false |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
return true |
|
|
|
return true |
|
|
|
}) |
|
|
|
}) |
|
|
|
nodes = [...nodes, ...thisCompletionNodes] |
|
|
|
nodes = [...nodes, ...contractCompletions] |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('WORD', word, wordAt) |
|
|
|
|
|
|
|
console.log('NODES', nodes) |
|
|
|
console.log('NODES', nodes) |
|
|
|
|
|
|
|
|
|
|
|
// remove duplicates
|
|
|
|
// remove duplicates
|
|
|
@ -225,7 +125,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getVariableDeclaration = async (node: any) => { |
|
|
|
const getVariableDeclaration = async (node: any) => { |
|
|
|
let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node) |
|
|
|
let variableDeclaration = await this.props.plugin.call('codeParser', 'getVariableDeclaration', node) |
|
|
|
if (node.scope) { |
|
|
|
if (node.scope) { |
|
|
@ -250,7 +149,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
} |
|
|
|
} |
|
|
|
suggestions.push(completion) |
|
|
|
suggestions.push(completion) |
|
|
|
} else if (node.nodeType === 'FunctionDefinition') { |
|
|
|
} else if (node.nodeType === 'FunctionDefinition') { |
|
|
|
|
|
|
|
|
|
|
|
const completion = { |
|
|
|
const completion = { |
|
|
|
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, |
|
|
|
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, |
|
|
|
kind: this.monaco.languages.CompletionItemKind.Function, |
|
|
|
kind: this.monaco.languages.CompletionItemKind.Function, |
|
|
@ -293,9 +191,10 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
} else if |
|
|
|
} else if |
|
|
|
(node.nodeType === 'EventDefinition') { |
|
|
|
(node.nodeType === 'EventDefinition') { |
|
|
|
const completion = { |
|
|
|
const completion = { |
|
|
|
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` ${node.name}` }, |
|
|
|
label: { label: `"${node.name}"`, description: await getNodeLink(node), detail: ` -> ${node.name} ${await getParamaters(node)}` }, |
|
|
|
kind: this.monaco.languages.CompletionItemKind.Event, |
|
|
|
kind: this.monaco.languages.CompletionItemKind.Event, |
|
|
|
insertText: node.name, |
|
|
|
insertText: `${node.name}${await completeParameters(node.parameters)};`, |
|
|
|
|
|
|
|
insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, |
|
|
|
range: range, |
|
|
|
range: range, |
|
|
|
documentation: await getDocs(node) |
|
|
|
documentation: await getDocs(node) |
|
|
|
} |
|
|
|
} |
|
|
@ -310,6 +209,17 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
documentation: await getDocs(node) |
|
|
|
documentation: await getDocs(node) |
|
|
|
} |
|
|
|
} |
|
|
|
suggestions.push(completion) |
|
|
|
suggestions.push(completion) |
|
|
|
|
|
|
|
} else if |
|
|
|
|
|
|
|
(node.nodeType === 'EnumValue' || node.type === 'EnumValue') { |
|
|
|
|
|
|
|
const completion = { |
|
|
|
|
|
|
|
label: { label: `"${node.name}"` }, |
|
|
|
|
|
|
|
kind: this.monaco.languages.CompletionItemKind.EnumMember, |
|
|
|
|
|
|
|
insertText: node.name, |
|
|
|
|
|
|
|
range: range, |
|
|
|
|
|
|
|
documentation: null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
suggestions.push(completion) |
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
console.log('UNKNOWN NODE', node) |
|
|
|
console.log('UNKNOWN NODE', node) |
|
|
|
} |
|
|
|
} |
|
|
@ -321,77 +231,204 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private getlinearizedBaseContracts = async (node: any) => { |
|
|
|
private getBlockNodesAtPosition = async (position: Position) => { |
|
|
|
let params = [] |
|
|
|
let nodes: any[] = [] |
|
|
|
if (node.linearizedBaseContracts) { |
|
|
|
const cursorPosition = this.props.editorAPI.getCursorPosition() |
|
|
|
for (const id of node.linearizedBaseContracts) { |
|
|
|
const nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
|
|
|
if (id !== node.id) { |
|
|
|
// try to get the block from ANTLR of which the position is in
|
|
|
|
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) |
|
|
|
const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) |
|
|
|
params = [...params, ...[baseContract]] |
|
|
|
// if the block has a name and a type we can maybe find it in the contract nodes
|
|
|
|
|
|
|
|
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('file nodes', fileNodes); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
|
|
|
|
|
|
|
for (const node of nodesAtPosition) { |
|
|
|
|
|
|
|
// try to find the real block in the AST and get the nodes in that scope
|
|
|
|
|
|
|
|
if (node.nodeType === 'ContractDefinition') { |
|
|
|
|
|
|
|
const contractNodes = fileNodes.contracts[node.name].contractNodes |
|
|
|
|
|
|
|
for (const contractNode of Object.values(contractNodes)) { |
|
|
|
|
|
|
|
if (contractNode['name'] === ANTLRBlock.name) { |
|
|
|
|
|
|
|
console.log('found real block', contractNode) |
|
|
|
|
|
|
|
let nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode as any).id) |
|
|
|
|
|
|
|
nodes = [...nodes, ...nodeOfScope] |
|
|
|
|
|
|
|
if (contractNode['body']) { |
|
|
|
|
|
|
|
nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode['body'] as any).id) |
|
|
|
|
|
|
|
nodes = [...nodes, ...nodeOfScope] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//const nodesOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', node.id)
|
|
|
|
|
|
|
|
// nodes = [...nodes, ...nodesOfScope]
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return params |
|
|
|
console.log('NODES AT BLOCK SCOPE', nodes) |
|
|
|
|
|
|
|
// we are only interested in nodes that are in the same block as the cursor
|
|
|
|
|
|
|
|
nodes = nodes.filter(node => { |
|
|
|
|
|
|
|
if (node.src) { |
|
|
|
|
|
|
|
const position = sourceMappingDecoder.decode(node.src) |
|
|
|
|
|
|
|
if (position.start >= ANTLRBlock.range[0] && (position.start + position.length) <= ANTLRBlock.range[1]) { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nodes; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private getContractCompletions = async (nodes: any[], position: Position) => { |
|
|
|
private getContractAtPosition = async (position: Position) => { |
|
|
|
const cursorPosition = this.props.editorAPI.getCursorPosition() |
|
|
|
const cursorPosition = this.props.editorAPI.getCursorPosition() |
|
|
|
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
|
|
|
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
|
|
|
console.log('NODES AT POSITION', nodesAtPosition) |
|
|
|
console.log('NODES AT POSITION', nodesAtPosition) |
|
|
|
// if no nodes exits at position, try to get the block of which the position is in
|
|
|
|
// if no nodes exits at position, try to get the block of which the position is in
|
|
|
|
|
|
|
|
const ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) |
|
|
|
if (!nodesAtPosition.length) { |
|
|
|
if (!nodesAtPosition.length) { |
|
|
|
const block = await this.props.plugin.call('codeParser', 'getBlockAtPosition', position, null) |
|
|
|
if (ANTLRBlock) { |
|
|
|
if (block) { |
|
|
|
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', ANTLRBlock.range[0]) |
|
|
|
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0]) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// get all children of all nodes at position
|
|
|
|
|
|
|
|
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
|
|
|
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
|
|
|
for (const node of 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] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// if the node is a contract at this positio mark the nodes as the contractNodes
|
|
|
|
|
|
|
|
if (node.nodeType === 'ContractDefinition') { |
|
|
|
if (node.nodeType === 'ContractDefinition') { |
|
|
|
for (const node of nodesOfScope) { |
|
|
|
return node |
|
|
|
node.isInContract = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
nodes = [...nodes, ...nodesOfScope] |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// get the linearized base contracts
|
|
|
|
} |
|
|
|
|
|
|
|
return null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private getContractCompletions = async (position: Position) => { |
|
|
|
|
|
|
|
let nodes: any[] = [] |
|
|
|
|
|
|
|
const cursorPosition = this.props.editorAPI.getCursorPosition() |
|
|
|
|
|
|
|
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition) |
|
|
|
|
|
|
|
console.log('NODES AT POSITION', nodesAtPosition) |
|
|
|
|
|
|
|
// if no nodes exits at position, try to get the block of which the position is in
|
|
|
|
|
|
|
|
const block = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) |
|
|
|
|
|
|
|
console.log('BLOCK AT POSITION', block) |
|
|
|
|
|
|
|
if (!nodesAtPosition.length) { |
|
|
|
|
|
|
|
if (block) { |
|
|
|
|
|
|
|
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.range[0]) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// find the contract and get the nodes of the contract and the base contracts and imports
|
|
|
|
|
|
|
|
if (isArray(nodesAtPosition) && nodesAtPosition.length) { |
|
|
|
|
|
|
|
let contractNode: any = {} |
|
|
|
for (const node of nodesAtPosition) { |
|
|
|
for (const node of nodesAtPosition) { |
|
|
|
const baseContracts = await this.getlinearizedBaseContracts(node) |
|
|
|
if (node.nodeType === 'ContractDefinition') { |
|
|
|
for (const baseContract of baseContracts) { |
|
|
|
contractNode = node |
|
|
|
nodes = [...nodes, ...baseContract.nodes] |
|
|
|
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes') |
|
|
|
|
|
|
|
console.log('FILE NODES', fileNodes) |
|
|
|
|
|
|
|
const contractNodes = fileNodes.contracts[node.name] |
|
|
|
|
|
|
|
nodes = [...Object.values(contractNodes.contractScopeNodes), ...nodes] |
|
|
|
|
|
|
|
nodes = [...Object.values(contractNodes.baseNodesWithBaseContractScope), ...nodes] |
|
|
|
|
|
|
|
nodes = [...Object.values(fileNodes.imports), ...nodes] |
|
|
|
|
|
|
|
// some types like Enum and Events are not scoped so we need to add them manually
|
|
|
|
|
|
|
|
nodes = [...contractNodes.contractDefinition.nodes, ...nodes] |
|
|
|
|
|
|
|
// at the nodes at the block itself
|
|
|
|
|
|
|
|
nodes = [...nodes, ...await this.getBlockNodesAtPosition(position)] |
|
|
|
|
|
|
|
// filter private nodes, only allow them when contract ID is the same as the current contract
|
|
|
|
|
|
|
|
nodes = nodes.filter(node => { |
|
|
|
|
|
|
|
if (node.visibility) { |
|
|
|
|
|
|
|
if (node.visibility === 'private') { |
|
|
|
|
|
|
|
return (node.contractId ? node.contractId === contractNode.id : false) || false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// get all the nodes from a simple code parser which only parses the current file
|
|
|
|
// get all the nodes from a simple code parser which only parses the current file
|
|
|
|
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')] |
|
|
|
nodes = [...nodes, ...await this.props.plugin.call('codeParser', 'listAstNodes')] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return nodes |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// filter private nodes, only allow them when isContract is true
|
|
|
|
private getThisCompletions = async (position: Position) => { |
|
|
|
nodes = nodes.filter(node => { |
|
|
|
let nodes: any[] = [] |
|
|
|
if (node.visibility) { |
|
|
|
let thisCompletionNodes = await this.getContractCompletions(position) |
|
|
|
if (!node.isInContract) { |
|
|
|
const allowedTypesForThisCompletion = ['VariableDeclaration', 'FunctionDefinition'] |
|
|
|
return node.visibility !== 'private' |
|
|
|
// with this. you can't have internal nodes and no contractDefinitions
|
|
|
|
} |
|
|
|
thisCompletionNodes = thisCompletionNodes.filter(node => { |
|
|
|
|
|
|
|
console.log('node filter', node) |
|
|
|
|
|
|
|
if (node.visibility && (node.visibility === 'internal' || node.visibility === 'private')) { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (node.nodeType && !allowedTypesForThisCompletion.includes(node.nodeType)) { |
|
|
|
|
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
return true |
|
|
|
return true |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
nodes = [...nodes, ...thisCompletionNodes] |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
// eslint-disable-next-line no-debugger
|
|
|
|
|
|
|
|
// debugger
|
|
|
|
|
|
|
|
}, 2000) |
|
|
|
return nodes |
|
|
|
return nodes |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string) => { |
|
|
|
|
|
|
|
const contractCompletions = await this.getContractCompletions(position) |
|
|
|
|
|
|
|
let nodes: any[] = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => { |
|
|
|
|
|
|
|
return nodes && nodes.filter(node => { |
|
|
|
|
|
|
|
if (node.visibility) { |
|
|
|
|
|
|
|
if(declarationOf && declarationOf.nodeType && declarationOf.nodeType === 'StructDefinition') { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if ((node.visibility === 'internal' && !parentNode.isBaseNode) || node.visibility === 'private') { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const nodeOfScope of contractCompletions) { |
|
|
|
|
|
|
|
// console.log('nodeOfScope', nodeOfScope.name, nodeOfScope)
|
|
|
|
|
|
|
|
if (nodeOfScope.name === nameOfLastTypedExpression) { |
|
|
|
|
|
|
|
console.log('FOUND NODE', nodeOfScope) |
|
|
|
|
|
|
|
if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'UserDefinedTypeName') { |
|
|
|
|
|
|
|
const declarationOf: AstNode = await this.props.plugin.call('codeParser', 'declarationOf', nodeOfScope.typeName) |
|
|
|
|
|
|
|
console.log('METHOD 1 HAS DECLARATION OF', declarationOf) |
|
|
|
|
|
|
|
nodes = [...nodes,
|
|
|
|
|
|
|
|
...filterNodes(declarationOf.nodes, nodeOfScope, declarationOf)
|
|
|
|
|
|
|
|
|| filterNodes(declarationOf.members, nodeOfScope, declarationOf)] |
|
|
|
|
|
|
|
const baseContracts = await this.getlinearizedBaseContracts(declarationOf) |
|
|
|
|
|
|
|
for (const baseContract of baseContracts) { |
|
|
|
|
|
|
|
nodes = [...nodes, ...filterNodes(baseContract.nodes, nodeOfScope)] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (nodeOfScope.members) { |
|
|
|
|
|
|
|
console.log('METHOD 1 HAS MEMBERS OF') |
|
|
|
|
|
|
|
nodes = [...nodes, ...filterNodes(nodeOfScope.members, nodeOfScope)] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nodes |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private getlinearizedBaseContracts = async (node: any) => { |
|
|
|
|
|
|
|
let params = [] |
|
|
|
|
|
|
|
if (node.linearizedBaseContracts) { |
|
|
|
|
|
|
|
for (const id of node.linearizedBaseContracts) { |
|
|
|
|
|
|
|
if (id !== node.id) { |
|
|
|
|
|
|
|
const baseContract = await this.props.plugin.call('codeParser', 'getNodeById', id) |
|
|
|
|
|
|
|
params = [...params, ...[baseContract]] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return params |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param lineTextBeforeCursor
|
|
|
|
* @param lineTextBeforeCursor
|
|
|
|