From aeeb3d1cf19aec736c0a6dcef1f07bce105fbb6e Mon Sep 17 00:00:00 2001 From: filip mertens Date: Wed, 14 Sep 2022 18:43:51 +0200 Subject: [PATCH] add imports to autodcomplete --- .../providers/completion/completionGlobals.ts | 268 +++++++++++++++++- .../src/lib/providers/completionProvider.ts | 131 +++++---- 2 files changed, 337 insertions(+), 62 deletions(-) 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 a3dd0f05e8..de64477848 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -330,10 +330,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', - 'after', 'promise', 'alias', 'apply','auto', 'copyof', 'default', 'define', 'final', 'implements', + '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', + '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 => { @@ -389,6 +389,268 @@ export function GeCompletionUnits(range: IRange, monaco): monaco.languages.Compl return completionItems; } +export function GetImports(range: IRange, monaco): monaco.languages.CompletionItem[] { + let list = [ + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/ERC20.sol', + label: 'OZ ERC20', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC721/ERC721.sol', + label: 'OZ ERC721', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/access/Ownable.sol', + label: 'OZ Ownable', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/Counters.sol', + label: 'OZ Counters', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/Address.sol', + label: 'OZ Address', + range + }, + { + detail: '@openzeppelin/contracts/utils/Context.sol', + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/Context.sol', + label: 'OZ Context', + range + }, + { + detail: '@openzeppelin/contracts/utils/EnumerableSet.sol', + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/EnumerableSet.sol', + label: 'OZ EnumerableSet', + range + }, + { + detail: '@openzeppelin/contracts/utils/EnumerableMap.sol', + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/EnumerableMap.sol', + label: 'OZ EnumerableMap', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/Strings.sol', + label: 'OZ Strings', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/ReentrancyGuard.sol', + label: 'OZ ReentrancyGuard', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/Pausable.sol', + label: 'OZ Pausable', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/structs/EnumerableSet.sol', + label: 'OZ EnumerableSet', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/structs/EnumerableMap.sol', + label: 'OZ EnumerableMap', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/SafeMath.sol', + label: 'OZ SafeMath', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/SafeCast.sol', + label: 'OZ SafeCast', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/Math.sol', + label: 'OZ Math', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/SignedSafeMath.sol', + label: 'OZ SignedSafeMath', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/SafeMath.sol', + label: 'OZ SafeMath', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/utils/math/SafeCast.sol', + label: 'OZ SafeCast', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', + label: 'OZ ERC20Burnable', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', + label: 'OZ ERC20Pausable', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol', + label: 'OZ ERC20Snapshot', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol', + label: 'OZ ERC20Permit', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Votes.sol', + label: 'OZ ERC20Votes', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC1155/ERC1155.sol', + label: 'OZ ERC1155', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/IERC20.sol', + label: 'OZ IERC20', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC721/IERC721.sol', + label: 'OZ IERC721', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC1155/IERC1155.sol', + label: 'OZ IERC1155', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/IERC20Metadata.sol', + label: 'OZ IERC20Metadata', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol', + label: 'OZ IERC721Metadata', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC1155/IERC1155MetadataURI.sol', + label: 'OZ IERC1155MetadataURI', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol', + label: 'OZ IERC1155Receiver', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + label: 'OZ IERC721Receiver', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol', + label: 'OZ SafeERC20', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/Governor.sol', + label: 'OZ Governor', + range + }, + { + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol', + label: 'OZ GovernorCountingSimple', + range + + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorVotes.sol', + label: 'OZ GovernorVotes', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol', + label: 'OZ GovernorVotesQuorumFraction', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol', + label: 'OZ GovernorTimelockCompound', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol', + label: 'OZ GovernorTimelockControl', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol', + label: 'OZ GovernorCountingSimple', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorSettings.sol', + label: 'OZ GovernorSettings', + range + },{ + kind: monaco.languages.CompletionItemKind.Module, + insertText: '@openzeppelin/contracts/governance/extensions/GovernorCompatibilityBravo.sol', + label: 'OZ GovernorCompatibilityBravo', + range + } + ] + list = list.map((item) => { + return { + ...item, + label: `${item.label}: ${item.insertText}`, + } + }) + console.log(list) + return list; +}; export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ @@ -668,7 +930,7 @@ export function getAddressCompletionItems(range: IRange, monaco): monaco.languag ] } - + export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { if (word === 'ArrayTypeName') { return getArrayCompletionItems(range, monaco); diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 745c5e395b..c9b88c9805 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, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals" +import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, GetImports } from "./completion/completionGlobals" export class RemixCompletionProvider implements languages.CompletionItemProvider { @@ -16,11 +16,11 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider this.monaco = monaco } - triggerCharacters = ['.', ''] + triggerCharacters = ['.', '', '"'] async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise { const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion') - if(!completionSettings) return + if (!completionSettings) return const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, @@ -33,62 +33,75 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider let nodes: AstNode[] = [] let suggestions: monaco.languages.CompletionItem[] = [] - if (context.triggerCharacter === '.') { - const lineTextBeforeCursor: string = line.substring(0, position.column - 1) - const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) - const expressionElements = lineTextBeforeCursor.split('.') + if (context.triggerCharacter === '"') { - let dotCompleted = false + const ast = await this.props.plugin.call('codeParser', 'parseSolidity', line) + if (ast && ast.children && ast.children[0] && ast.children[0].type === "ImportDirective") { + suggestions = [...suggestions, + ...GetImports(range, this.monaco), + ] - // handles completion from for builtin types - if(lastNodeInExpression.memberName === 'sender') { // exception for this member - lastNodeInExpression.name = 'sender' + }else{ + return } - const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) - if (globalCompletion) { - dotCompleted = true - suggestions = [...suggestions, ...globalCompletion] - setTimeout(() => { - // eslint-disable-next-line no-debugger - // debugger - }, 2000) - } - // handle completion for global THIS. - if (lastNodeInExpression.name === 'this') { - dotCompleted = true - nodes = [...nodes, ...await this.getThisCompletions(position)] - } - // handle completion for other dot completions - if (expressionElements.length > 1 && !dotCompleted) { + } else - const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName - const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) - nodes = [...nodes, ...dotCompletions.nodes] - suggestions = [...suggestions, ...dotCompletions.suggestions] - } - } else { + if (context.triggerCharacter === '.') { + const lineTextBeforeCursor: string = line.substring(0, position.column - 1) + const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) + const expressionElements = lineTextBeforeCursor.split('.') - // handles contract completions and other suggestions - suggestions = [...suggestions, - ...GetGlobalVariable(range, this.monaco), - ...getCompletionSnippets(range, this.monaco), - ...GetCompletionTypes(range, this.monaco), - ...GetCompletionKeywords(range, this.monaco), - ...GetGlobalFunctions(range, this.monaco), - ...GeCompletionUnits(range, this.monaco), - ] - let contractCompletions = await this.getContractCompletions(position) + let dotCompleted = false - // we can't have external nodes without using this. - contractCompletions = contractCompletions.filter(node => { - if (node.visibility && node.visibility === 'external') { - return false + // handles completion from for builtin types + if (lastNodeInExpression.memberName === 'sender') { // exception for this member + lastNodeInExpression.name = 'sender' } - return true - }) - nodes = [...nodes, ...contractCompletions] + const globalCompletion = getContextualAutoCompleteByGlobalVariable(lastNodeInExpression.name, range, this.monaco) + if (globalCompletion) { + dotCompleted = true + suggestions = [...suggestions, ...globalCompletion] + setTimeout(() => { + // eslint-disable-next-line no-debugger + // debugger + }, 2000) + } + // handle completion for global THIS. + if (lastNodeInExpression.name === 'this') { + dotCompleted = true + nodes = [...nodes, ...await this.getThisCompletions(position)] + } + // handle completion for other dot completions + if (expressionElements.length > 1 && !dotCompleted) { - } + const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName + const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) + nodes = [...nodes, ...dotCompletions.nodes] + suggestions = [...suggestions, ...dotCompletions.suggestions] + } + } else { + + // handles contract completions and other suggestions + suggestions = [...suggestions, + ...GetGlobalVariable(range, this.monaco), + ...getCompletionSnippets(range, this.monaco), + ...GetCompletionTypes(range, this.monaco), + ...GetCompletionKeywords(range, this.monaco), + ...GetGlobalFunctions(range, this.monaco), + ...GeCompletionUnits(range, this.monaco), + ] + let contractCompletions = await this.getContractCompletions(position) + + // we can't have external nodes without using this. + contractCompletions = contractCompletions.filter(node => { + if (node.visibility && node.visibility === 'external') { + return false + } + return true + }) + nodes = [...nodes, ...contractCompletions] + + } // remove duplicates const nodeIds = {}; @@ -220,7 +233,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider } suggestions.push(completion) - } + } } return { @@ -242,9 +255,9 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider // 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 - || (contractNode['kind'] === 'constructor' && ANTLRBlock.name === null ) + for (const contractNode of Object.values(contractNodes)) { + if (contractNode['name'] === ANTLRBlock.name + || (contractNode['kind'] === 'constructor' && ANTLRBlock.name === null) ) { let nodeOfScope = await this.props.plugin.call('codeParser', 'getNodesWithScope', (contractNode as any).id) nodes = [...nodes, ...nodeOfScope] @@ -270,7 +283,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider */ } } - + // we are only interested in nodes that are in the same block as the cursor nodes = nodes.filter(node => { if (node.src) { @@ -279,7 +292,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider return true } } - if(node.outSideBlock){ return true } + if (node.outSideBlock) { return true } return false }) @@ -387,9 +400,9 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider 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') { + } 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') { + } else if (nodeOfScope.typeName && nodeOfScope.typeName.nodeType === 'ElementaryTypeName' && nodeOfScope.typeName.name === 'address') { suggestions = [...suggestions, ...getContextualAutoCompleteBTypeName('address', range, this.monaco)] } }