diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx index 6d022ac940..9535ca2f68 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -2,8 +2,6 @@ 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 { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' import { helper } from '@remix-project/remix-solidity' import CodeParserGasService from './services/code-parser-gas-service' @@ -11,8 +9,6 @@ import CodeParserCompiler from './services/code-parser-compiler' import CodeParserAntlrService from './services/code-parser-antlr-service' import CodeParserNodeHelper from './services/code-parser-node-helper' import React from 'react' -import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' - import { Profile } from '@remixproject/plugin-utils' import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from 'dist/libs/remix-analyzer/src/types' import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api' @@ -203,7 +199,9 @@ export class CodeParser extends Plugin { const index = {} const contractName: string = contractNode.name const callback = (node) => { - if(inScope && node.scope !== contractNode.id) return + if(inScope && node.scope !== contractNode.id + && !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition')) + return if(inScope) node.isClassNode = true; node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult) node.functionName = node.name + this._getInputParams(node) @@ -308,13 +306,6 @@ export class CodeParser extends Plugin { } } - - - - - - - /** * Nodes at position where position is a number, offset * @param position diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts index ced7926405..ba96543c0b 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts @@ -95,7 +95,6 @@ export default class CodeParserCompiler { this.plugin.currentFile = await this.plugin.call('fileManager', 'file') if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) { const state = await this.plugin.call('solidity', 'getCompilerState') - console.log('COMPILER STATE', state) this.compiler.set('optimize', state.optimize) this.compiler.set('evmVersion', state.evmVersion) this.compiler.set('language', state.language) diff --git a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts index 7380f698f2..4c5285e9ef 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -1,6 +1,33 @@ import { IRange } from "monaco-editor"; import monaco from "../../../types/monaco"; +export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { + return [ + { + detail: 'concatenate an arbitrary number of string values', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'concat(${1:string})', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'concat()', + range, + }, + ] +} + +export function getBytesCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { + return [ + { + detail: 'concatenate an arbitrary number of values', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'concat(${1:bytes})', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'concat()', + range, + }, + ] +} + + export function getBlockCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ { @@ -10,6 +37,20 @@ export function getBlockCompletionItems(range: IRange, monaco): monaco.languages label: 'coinbase', range, }, + { + detail: '(uint): Current block’s base fee', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'basefee', + label: 'basefee', + range, + }, + { + detail: '(uint): Current chain id', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'chainid', + label: 'chainid', + range, + }, { detail: '(bytes32): DEPRICATED In 0.4.22 use blockhash(uint) instead. Hash of the given block - only works for 256 most recent blocks excluding current', insertText: 'blockhash(${1:blockNumber});', @@ -200,6 +241,14 @@ export function getAbiCompletionItems(range: IRange, monaco): monaco.languages.C label: 'encode', range }, + { + detail: 'encodeCall(function functionPointer, (...)) returns (bytes memory) ABI-encodes a call to functionPointer with the arguments found in the tuple', + insertText: 'encode(${1:arg});', + kind: monaco.languages.CompletionItemKind.Method, + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'encodecall', + range + }, { detail: 'encodePacked(..) returns (bytes): Performes packed encoding of the given arguments', insertText: 'encodePacked(${1:arg});', @@ -260,7 +309,10 @@ function CreateCompletionItem(label: string, kind: monaco.languages.CompletionIt export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] { const completionItems = []; const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for', - 'if', 'new', 'return', 'returns', 'while', 'using', 'emit', + 'after', 'promise', 'alias', 'apply','auto', 'copyof', 'default', 'define', 'final', 'implements', + 'inline', 'let', 'macro', 'match', 'mutable', 'null', 'of', 'partial', 'reference', 'relocatable', + 'sealed', 'sizeof', 'static', 'supports', 'switch', 'typedef', + 'if', 'new', 'return', 'returns', 'while', 'using', 'emit', 'anonymous', 'indexed', 'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally', 'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override']; keywords.forEach(unit => { @@ -277,6 +329,7 @@ export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.C completionItems.push(CreateCompletionItem('contract', monaco.languages.CompletionItemKind.Class, null, range)); completionItems.push(CreateCompletionItem('library', monaco.languages.CompletionItemKind.Class, null, range)); completionItems.push(CreateCompletionItem('storage', monaco.languages.CompletionItemKind.Field, null, range)); + completionItems.push(CreateCompletionItem('calldata', monaco.languages.CompletionItemKind.Field, null, range)); completionItems.push(CreateCompletionItem('memory', monaco.languages.CompletionItemKind.Field, null, range)); completionItems.push(CreateCompletionItem('var', monaco.languages.CompletionItemKind.Field, null, range)); completionItems.push(CreateCompletionItem('constant', monaco.languages.CompletionItemKind.Constant, null, range)); @@ -491,14 +544,119 @@ export function getContextualAutoCompleteByGlobalVariable(word: string, range: I if (word === 'block') { return getBlockCompletionItems(range, monaco); } + if (word === 'string') { + return getStringCompletionItems(range, monaco); + } + if (word === 'bytes') { + return getBytesCompletionItems(range, monaco); + } if (word === 'msg') { return getMsgCompletionItems(range, monaco); } if (word === 'tx') { return getTxCompletionItems(range, monaco); } - if (word === 'address') { + if (word === 'abi') { return getAbiCompletionItems(range, monaco); } + if (word === 'sender') { + return getAddressCompletionItems(range, monaco); + } return null; +} + +export function getArrayCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { + return [ + { + detail: '', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'length;', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'length', + range, + }, + { + detail: '', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'push(${1:value});', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'push(value)', + range, + }, + { + detail: '', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'push();', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'push()', + range, + }, + { + detail: '', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'pop();', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'pop()', + range, + }, + ] +} + +export function getAddressCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { + return [ + { + detail: '(uint256): balance of the Address in Wei', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'balance;', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'balance', + range, + }, + { + detail: '(bytes memory): code at the Address (can be empty)', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'code;', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'code', + range, + }, + { + detail: '(bytes32): the codehash of the Address', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'codehash;', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'codehash', + range, + }, + { + detail: '(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'send(${1:value});', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'send()', + range, + }, + { + detail: '(uint256 amount): send given amount of Wei to Address, throws on failure', + kind: monaco.languages.CompletionItemKind.Method, + insertText: 'transfer(${1:value});', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + label: 'transfer()', + range, + }, + ] + +} + +export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { + if (word === 'ArrayTypeName') { + return getArrayCompletionItems(range, monaco); + } + if (word === 'bytes') { + return getBytesCompletionItems(range, monaco); + } + if (word === 'address') { + return getAddressCompletionItems(range, monaco); + } + return []; } \ No newline at end of file diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index eb98ec35fa..217f4202c3 100644 --- a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts @@ -4,7 +4,7 @@ import { isArray } from "lodash" import { editor, languages, Position } from "monaco-editor" import monaco from "../../types/monaco" import { EditorUIProps } from "../remix-ui-editor" -import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals" +import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals" export class RemixCompletionProvider implements languages.CompletionItemProvider { @@ -44,6 +44,9 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider let dotCompleted = false // handles completion from for builtin types + if(lastNodeInExpression.memberName === 'sender') { // exception for this member + lastNodeInExpression.name = 'sender' + } const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) if (globalCompletion) { dotCompleted = true @@ -62,8 +65,9 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider if (expressionElements.length > 1 && !dotCompleted) { const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName - nodes = [...nodes, ...await this.getDotCompletions(position, nameOfLastTypedExpression)] - + const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) + nodes = [...nodes, ...dotCompletions.nodes] + suggestions = [...suggestions, ...dotCompletions.suggestions] } } else { @@ -278,27 +282,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider return nodes; } - private getContractAtPosition = async (position: Position) => { - 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 ANTLRBlock = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', position, null) - if (!nodesAtPosition.length) { - if (ANTLRBlock) { - nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', ANTLRBlock.range[0]) - } - } - if (isArray(nodesAtPosition) && nodesAtPosition.length) { - for (const node of nodesAtPosition) { - if (node.nodeType === 'ContractDefinition') { - return node - } - } - } - return null - } - private getContractCompletions = async (position: Position) => { let nodes: any[] = [] const cursorPosition = this.props.editorAPI.getCursorPosition() @@ -324,8 +307,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider 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 @@ -371,14 +352,15 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider return nodes } - private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string) => { + private getDotCompletions = async (position: Position, nameOfLastTypedExpression: string, range) => { const contractCompletions = await this.getContractCompletions(position) let nodes: any[] = [] + let suggestions: monaco.languages.CompletionItem[] = [] const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => { return nodes && nodes.filter(node => { if (node.visibility) { - if(declarationOf && declarationOf.nodeType && declarationOf.nodeType === 'StructDefinition') { + if (declarationOf && declarationOf.nodeType && declarationOf.nodeType === 'StructDefinition') { return true } if ((node.visibility === 'internal' && !parentNode.isBaseNode) || node.visibility === 'private') { @@ -397,23 +379,28 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider 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)] + 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)] + } else if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ArrayTypeName') { + suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('ArrayTypeName', range, this.monaco)] + } else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'bytes') { + suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('bytes', range, this.monaco)] + } else if(nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'address') { + suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('address', range, this.monaco)] } } } - return nodes + return { nodes, suggestions } } private getlinearizedBaseContracts = async (node: any) => {