diff --git a/libs/remix-core-plugin/src/lib/editor-context-listener.ts b/libs/remix-core-plugin/src/lib/editor-context-listener.ts index 185cd7debe..b489a9ed7d 100644 --- a/libs/remix-core-plugin/src/lib/editor-context-listener.ts +++ b/libs/remix-core-plugin/src/lib/editor-context-listener.ts @@ -7,7 +7,7 @@ import { canUseWorker, Compiler, CompilerAbstract, urlFromVersion } from '@remix const profile = { name: 'contextualListener', - methods: ['getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'], + methods: ['nodesWithScope', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'], events: [], version: '0.0.1' } @@ -57,7 +57,7 @@ export class EditorContextListener extends Plugin { this.on('editor', 'contentChanged', () => { this._stopHighlighting() }) this.on('solidity', 'astFinished', async (file, source, languageVersion, data, input, version) => { - console.log('compilation result', Object.keys(data.sources)) + // console.log('compilation result', Object.keys(data.sources)) if (languageVersion.indexOf('soljson') !== 0 || !data.sources) return if (data.sources && Object.keys(data.sources).length === 0) return this.lastCompilationResult = new CompilerAbstract(languageVersion, data, source, input) @@ -134,12 +134,12 @@ export class EditorContextListener extends Plugin { return results } - async nodesAtEditorPosition(position: any) { + async nodesAtEditorPosition(position: any, type: string = '') { const lastCompilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult') if (!lastCompilationResult) return false let urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { - const nodes = sourceMappingDecoder.nodesAtPosition(null, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) + const nodes = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) return nodes } return [] @@ -163,6 +163,14 @@ export class EditorContextListener extends Plugin { } } + 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 definitionAtPosition(position: any) { const nodes = await this.nodesAtEditorPosition(position) console.log('nodes at position', nodes) diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index bb3eb82cb1..931279a04e 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -253,7 +253,6 @@ export class Compiler { */ loadWorker (url: string): void { - console.log("WEB", webworkify) this.state.worker = webworkify(require.resolve('./compiler-worker')) const jobs: Record<'sources', SourceWithTarget> [] = [] diff --git a/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx b/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx index 962c43e09a..2946b41307 100644 --- a/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx +++ b/libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx @@ -98,7 +98,7 @@ export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) } } const activeHighlights: Array = await props.getActiveHighlights() - console.log('active highlights', activeHighlights) + // console.log('active highlights', activeHighlights) if (nextNode && activeHighlights && activeHighlights.length) { loopOverReferences.current = activeHighlights.findIndex((el: astNodeLight) => `${el.position.start}:${el.position.length}:${el.position.file}` === nextNode.src) loopOverReferences.current = loopOverReferences.current === -1 ? 0 : loopOverReferences.current diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 7e3faa4967..12b46c00a3 100644 --- a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts @@ -1,4 +1,3 @@ - export class RemixCompletionProvider { props: any @@ -8,42 +7,133 @@ export class RemixCompletionProvider { this.monaco = monaco } - async provideCompletionItems(model: any, position: any) { - console.log('AUTOCOMPLETE') + triggerCharacters = ['.', ''] + async provideCompletionItems(model: any, position: any, context: any) { + console.log('AUTOCOMPLETE', context) + return await this.run(model, position, context) return new Promise((resolve, reject) => { this.props.plugin.once('contextualListener', 'astFinished', async () => { console.log('AST FINISHED') - resolve(await this.run(model, position)) + resolve(await this.run(model, position, context)) }) this.props.plugin.call('contextualListener', 'compile') }) } - async run(model: any, position: any) { + async run(model: any, position: any, context: any) { const textUntilPosition = model.getValueInRange({ - startLineNumber: 1, - startColumn: 1, - endLineNumber: position.lineNumber, - endColumn: position.column - }); - - const word = model.getWordUntilPosition(position); - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn - }; - const nodes = await this.props.plugin.call('contextualListener', 'getNodes') + startLineNumber: 1, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column + }); + + const word = model.getWordUntilPosition(position); + const wordAt = model.getWordAtPosition(position); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + + + const 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('contextualListener', 'getNodeById', id) + params = [...params, ...[baseContract]] + } + } + } + return params + } + + const line = model.getLineContent(position.lineNumber) + let nodes + + + + if (context.triggerCharacter === '.') { + console.log('TEXT', line) + const splits = line.split('.') + console.log('splits', splits) + if (splits.length > 1) { + const last = splits[splits.length - 2].trim() + console.log('last', last) + const cursorPosition = this.props.editorAPI.getCursorPosition() + const nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', cursorPosition) + console.log('NODES AT POSITION', nodesAtPosition) + for (const node of nodesAtPosition) { + const nodesOfScope = await this.props.plugin.call('contextualListener', 'nodesWithScope', 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) + console.log('HAS DECLARATION OF', declarationOf) + nodes = declarationOf.nodes + } + } + } + } + } + /* + console.log('TEXT', line) + const splits = line.split('.') + console.log('splits', splits) + if(splits.length > 1) { + const last = splits[splits.length - 2].trim() + console.log('last', last) + for(const node of Object.values(nodes) as any[]){ + if(node.name === last) { + console.log('FOUND', node) + const cursorPosition = this.props.editorAPI.getCursorPosition() + const nodeAtPosition = await this.props.plugin.call('contextualListener', 'definitionAtPosition', cursorPosition) + console.log('NODE AT POSITION', nodeAtPosition) + if(nodeAtPosition && nodeAtPosition.nodeType === 'Block'){ + if(node.scope && node.scope === nodeAtPosition.id) { + console.log('NODE IN SCOPE', node) + } + } + } + } + } + */ + } else { + const cursorPosition = this.props.editorAPI.getCursorPosition() + const nodesAtPosition = await this.props.plugin.call('contextualListener', 'nodesAtEditorPosition', cursorPosition) + nodes = [] + console.log('NODES AT POSITION', nodesAtPosition) + + for (const node of nodesAtPosition) { + const nodesOfScope = await this.props.plugin.call('contextualListener', 'nodesWithScope', node.id) + nodes = [...nodes, ...nodesOfScope] + } + for (const node of nodesAtPosition) { + const baseContracts = await getlinearizedBaseContracts(node) + for (const baseContract of baseContracts) { + nodes = [...nodes, ...baseContract.nodes] + } + } + //nodes = await this.props.plugin.call('contextualListener', 'getNodes') + } + + + console.log('WORD', word, wordAt) console.log('NODES', nodes) console.log('NODES', Object.values(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) - console.log(filename, position) const lineColumn = await this.props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, @@ -95,30 +185,30 @@ export class RemixCompletionProvider { } const suggestions = [] - for(const node of Object.values(nodes) as any[]){ + for (const node of Object.values(nodes) as any[]) { + 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 getLinks(node), detail: ` ${await getVariableDeclaration(node)}` }, kind: this.monaco.languages.CompletionItemKind.Variable, insertText: node.name, range: range, documentation: await getDocs(node) } suggestions.push(completion) - } else - if (node.nodeType === 'FunctionDefinition') { + } 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 getLinks(node), detail: ` -> ${node.name} ${await getParamaters(node.parameters)}` }, kind: this.monaco.languages.CompletionItemKind.Function, - insertText: `${node.name} ${await completeParameters(node.parameters)}`, + insertText: `${node.name}${await completeParameters(node.parameters)};`, range: range, documentation: await getDocs(node) } suggestions.push(completion) } else if - (node.nodeType === 'ContractDefinition') { + (node.nodeType === 'ContractDefinition') { const completion = { - label: {label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}`}, + label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` }, kind: this.monaco.languages.CompletionItemKind.Interface, insertText: node.name, range: range, @@ -126,9 +216,9 @@ export class RemixCompletionProvider { } suggestions.push(completion) } else if - (node.nodeType === 'StructDefinition') { + (node.nodeType === 'StructDefinition') { const completion = { - label: {label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}`}, + label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` }, kind: this.monaco.languages.CompletionItemKind.Struct, insertText: node.name, range: range, @@ -136,9 +226,9 @@ export class RemixCompletionProvider { } suggestions.push(completion) } else if - (node.nodeType === 'EnumDefinition') { + (node.nodeType === 'EnumDefinition') { const completion = { - label: {label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}`}, + label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` }, kind: this.monaco.languages.CompletionItemKind.Enum, insertText: node.name, range: range, @@ -146,9 +236,9 @@ export class RemixCompletionProvider { } suggestions.push(completion) } else if - (node.nodeType === 'EventDefinition') { + (node.nodeType === 'EventDefinition') { const completion = { - label: {label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}`}, + label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` }, kind: this.monaco.languages.CompletionItemKind.Event, insertText: node.name, range: range, @@ -156,9 +246,9 @@ export class RemixCompletionProvider { } suggestions.push(completion) } else if - (node.nodeType === 'ModifierDefinition') { + (node.nodeType === 'ModifierDefinition') { const completion = { - label: {label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}`}, + label: { label: `"${node.name}"`, description: await getLinks(node), detail: ` ${node.name}` }, kind: this.monaco.languages.CompletionItemKind.Method, insertText: node.name, range: range, diff --git a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts index d0ddb0b5b4..f411efd83b 100644 --- a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts @@ -10,6 +10,8 @@ export class RemixHoverProvider { } provideHover = async function (model: any, position: any) { + console.log('HOVERING') + return await this.run(model, position) return new Promise((resolve, reject) => { this.props.plugin.once('contextualListener', 'astFinished', async () => { console.log('AST FINISHED') @@ -22,8 +24,8 @@ export class RemixHoverProvider { async run(model: any, position: any) { const cursorPosition = this.props.editorAPI.getHoverPosition(position) - const nodeDefinition = await this.props.plugin.call('contextualListener', 'definitionAtPosition', cursorPosition) - console.log(nodeDefinition) + const nodeAtPosition = await this.props.plugin.call('contextualListener', 'definitionAtPosition', cursorPosition) + console.log(nodeAtPosition) const contents = [] const getDocs = async (node: any) => { @@ -39,6 +41,20 @@ export class RemixHoverProvider { } } + const getScope = async (node: any) => { + if (node.id) { + contents.push({ + value: `id: ${node.id}` + }) + } + if (node.scope) { + contents.push({ + value: `scope: ${node.scope}` + }) + } + + } + const getLinks = async (node: any) => { const position = await this.props.plugin.call('contextualListener', 'positionOfDefinition', node) const lastCompilationResult = await this.props.plugin.call('contextualListener', 'getLastCompilationResult') @@ -101,50 +117,50 @@ export class RemixHoverProvider { return '' } - if (!nodeDefinition) { + if (!nodeAtPosition) { contents.push({ value: 'No definition found. Please compile the source code.' }) } - if (nodeDefinition) { - if (nodeDefinition.absolutePath) { - const target = await this.props.plugin.call('fileManager', 'getPathFromUrl', nodeDefinition.absolutePath) - if (target.file !== nodeDefinition.absolutePath) { + if (nodeAtPosition) { + if (nodeAtPosition.absolutePath) { + const target = await this.props.plugin.call('fileManager', 'getPathFromUrl', nodeAtPosition.absolutePath) + if (target.file !== nodeAtPosition.absolutePath) { contents.push({ value: `${target.file}` }) } contents.push({ - value: `${nodeDefinition.absolutePath}` + value: `${nodeAtPosition.absolutePath}` }) } - if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'VariableDeclaration') { + if (nodeAtPosition.typeDescriptions && nodeAtPosition.nodeType === 'VariableDeclaration') { contents.push({ - value: await getVariableDeclaration(nodeDefinition) + value: await getVariableDeclaration(nodeAtPosition) }) } - else if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'ElementaryTypeName') { + else if (nodeAtPosition.typeDescriptions && nodeAtPosition.nodeType === 'ElementaryTypeName') { contents.push({ - value: `${nodeDefinition.typeDescriptions.typeString}` + value: `${nodeAtPosition.typeDescriptions.typeString}` }) - } else if (nodeDefinition.nodeType === 'FunctionDefinition') { + } else if (nodeAtPosition.nodeType === 'FunctionDefinition') { contents.push({ - value: `function ${nodeDefinition.name} ${await getParamaters(nodeDefinition.parameters)} ${nodeDefinition.visibility} ${nodeDefinition.stateMutability}${await getOverrides(nodeDefinition)} returns ${await getParamaters(nodeDefinition.returnParameters)}` + value: `function ${nodeAtPosition.name} ${await getParamaters(nodeAtPosition.parameters)} ${nodeAtPosition.visibility} ${nodeAtPosition.stateMutability}${await getOverrides(nodeAtPosition)} returns ${await getParamaters(nodeAtPosition.returnParameters)}` }) - } else if (nodeDefinition.nodeType === 'ContractDefinition') { + } else if (nodeAtPosition.nodeType === 'ContractDefinition') { contents.push({ - value: `${nodeDefinition.contractKind} ${nodeDefinition.name} ${await getlinearizedBaseContracts(nodeDefinition)}` + value: `${nodeAtPosition.contractKind} ${nodeAtPosition.name} ${await getlinearizedBaseContracts(nodeAtPosition)}` }) } else { contents.push({ - value: `${nodeDefinition.nodeType}` + value: `${nodeAtPosition.nodeType}` }) } @@ -152,9 +168,9 @@ export class RemixHoverProvider { for (const key in contents) { contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```' } - getLinks(nodeDefinition) - getDocs(nodeDefinition) - + getLinks(nodeAtPosition) + getDocs(nodeAtPosition) + getScope(nodeAtPosition) } diff --git a/libs/remix-ui/editor/src/lib/web-types.ts b/libs/remix-ui/editor/src/lib/web-types.ts index 1c82788d30..e196c9390a 100644 --- a/libs/remix-ui/editor/src/lib/web-types.ts +++ b/libs/remix-ui/editor/src/lib/web-types.ts @@ -156,13 +156,11 @@ export const loadTypes = async (monaco) => { // @ts-ignore const versionEthers = await import('raw-loader!ethers/lib/_version.d.ts') versionEthers.default = versionEthers.default.replace(/@ethersproject\//g, '@ethersproject_') - console.log(versionEthers.default) monaco.languages.typescript.typescriptDefaults.addExtraLib(versionEthers.default, `file:///node_modules/@types/_version-ethers-lib/index.d.ts`) // @ts-ignore const utilEthers = await import('raw-loader!ethers/lib/utils.d.ts') utilEthers.default = utilEthers.default.replace(/@ethersproject\//g, '@ethersproject_') - console.log(utilEthers.default) monaco.languages.typescript.typescriptDefaults.addExtraLib(utilEthers.default, `file:///node_modules/@types/utils-ethers-lib/index.d.ts`) // @ts-ignore @@ -171,14 +169,12 @@ export const loadTypes = async (monaco) => { ethers.default = ethers.default.replace(/.\/_version/g, '_version-ethers-lib') ethers.default = ethers.default.replace(/.\/ethers/g, 'ethers-lib') ethers.default = ethers.default.replace(/@ethersproject\//g, '@ethersproject_') - console.log(ethers.default) monaco.languages.typescript.typescriptDefaults.addExtraLib(ethers.default, `file:///node_modules/@types/ethers-lib/index.d.ts`) // @ts-ignore const indexEthers = await import('raw-loader!ethers/lib/index.d.ts') indexEthers.default = indexEthers.default.replace(/.\/ethers/g, 'ethers-lib') indexEthers.default = indexEthers.default.replace(/@ethersproject\//g, '@ethersproject_') - console.log(indexEthers.default) monaco.languages.typescript.typescriptDefaults.addExtraLib(indexEthers.default, `file:///node_modules/@types/ethers/index.d.ts`) // Web3 @@ -222,5 +218,4 @@ export const loadTypes = async (monaco) => { } ` monaco.languages.typescript.typescriptDefaults.addExtraLib(indexRemixApi) - console.log('loaded monaco types') } \ No newline at end of file