From c4cd282b6acdc2acd00dbdfc76d8c0649b31e3ac Mon Sep 17 00:00:00 2001 From: filip mertens Date: Mon, 16 May 2022 17:49:55 +0200 Subject: [PATCH] hover --- .../src/lib/editor-context-listener.ts | 47 ++++-- .../editor/src/lib/providers/hoverProvider.ts | 142 +++++++++++++++++ .../editor/src/lib/remix-ui-editor.tsx | 149 ++++++++++++------ 3 files changed, 273 insertions(+), 65 deletions(-) create mode 100644 libs/remix-ui/editor/src/lib/providers/hoverProvider.ts 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 47373dd745..3356812ac3 100644 --- a/libs/remix-core-plugin/src/lib/editor-context-listener.ts +++ b/libs/remix-core-plugin/src/lib/editor-context-listener.ts @@ -2,11 +2,10 @@ import { Plugin } from '@remixproject/engine' import { sourceMappingDecoder } from '@remix-project/remix-debug' -import { AstNode } from '@remix-project/remix-solidity-ts' const profile = { name: 'contextualListener', - methods: ['definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'], + methods: ['getNodeById', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'], events: [], version: '0.0.1' } @@ -95,7 +94,7 @@ export class EditorContextListener extends Plugin { return null } - referencesOf(node: AstNode) { + referencesOf(node: any) { const results = [] const highlights = (id) => { if (this._index.Declarations && this._index.Declarations[id]) { @@ -118,8 +117,9 @@ export class EditorContextListener extends Plugin { async nodesAtEditorPosition(position: any) { const lastCompilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult') + 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]) + const nodes = sourceMappingDecoder.nodesAtPosition(null, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file]) return nodes } return [] @@ -135,16 +135,20 @@ export class EditorContextListener extends Plugin { } } - async getNodeDefinition() { - + async getNodeById(id: any) { + for (const key in this._index.FlatReferences) { + if (this._index.FlatReferences[key].id === id) { + return this._index.FlatReferences[key] + } + } } async definitionAtPosition(position: any) { const nodes = await this.nodesAtEditorPosition(position) console.log(nodes) console.log(this._index.FlatReferences) - let nodeDefinition: AstNode - let node: AstNode + let nodeDefinition: any + let node: any if (nodes && nodes.length) { node = nodes[nodes.length - 1] nodeDefinition = node @@ -165,23 +169,33 @@ export class EditorContextListener extends Plugin { } - async jumpToDefinition(position: any) { - const node = await this.definitionAtPosition(position) + async positionOfDefinition(node: any) { if (node) { if (node.src) { const position = sourceMappingDecoder.decode(node.src) if (position) { - await this.jumpToPosition(position) + return position } } } + return null } - /* + + async jumpToDefinition(position: any) { + const node = await this.definitionAtPosition(position) + const sourcePosition = await this.positionOfDefinition(node) + if(sourcePosition){ + await this.jumpToPosition(sourcePosition) + } + } + + /* * onClick jump to position of ast node in the editor */ async jumpToPosition(position: any) { const jumpToLine = async (fileName: string, lineColumn: any) => { if (fileName !== await this.call('fileManager', 'file')) { + console.log('jump to file', fileName) await this.call('fileManager', 'open', fileName) } if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { @@ -189,6 +203,8 @@ export class EditorContextListener extends Plugin { } } const lastCompilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult') + console.log(lastCompilationResult.getSourceCode().sources) + console.log(position) if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { const lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, @@ -197,6 +213,7 @@ export class EditorContextListener extends Plugin { lastCompilationResult.getAsts()) const filename = lastCompilationResult.getSourceName(position.file) // TODO: refactor with rendererAPI.errorClick + console.log(filename, lineColumn) jumpToLine(filename, lineColumn) } } @@ -206,10 +223,10 @@ export class EditorContextListener extends Plugin { this._stopHighlighting() this.currentPosition = cursorPosition this.currentFile = file - if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { - const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file]) + let urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile) + if (compilationResult && compilationResult.data && (compilationResult.data.sources[file] || compilationResult.data.sources[urlFromPath.file])) { + const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file] || compilationResult.data.sources[urlFromPath.file]) this.nodes = nodes - console.log(nodes) if (nodes && nodes.length && nodes[nodes.length - 1]) { await this._highlightExpressions(nodes[nodes.length - 1], compilationResult) } diff --git a/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts new file mode 100644 index 0000000000..d4b9bf60f9 --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/hoverProvider.ts @@ -0,0 +1,142 @@ +import monaco from '../../types/monaco' + +export class RemixHoverProvider implements monaco.languages.HoverProvider { + + props: any + constructor(props: any) { + this.props = props + } + + provideHover = async function (model: any, position: monaco.Position) { + const cursorPosition = this.propseditorAPI.getHoverPosition(position) + const nodeDefinition = await this.propsplugin.call('contextualListener', 'definitionAtPosition', cursorPosition) + console.log(nodeDefinition) + 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 + }) + } + } + + const getLinks = async (node: any) => { + const position = await this.propsplugin.call('contextualListener', 'positionOfDefinition', node) + const lastCompilationResult = await this.propsplugin.call('compilerArtefacts', 'getLastCompilationResult') + const filename = lastCompilationResult.getSourceName(position.file) + console.log(filename, position) + const lineColumn = await this.propsplugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', + position, + position.file, + lastCompilationResult.getSourceCode().sources, + lastCompilationResult.getAsts()) + contents.push({ + value: `${filename} ${lineColumn.start.line}:${lineColumn.start.column}` + }) + } + + const getVariableDeclaration = async (node: any) => { + if (node.typeDescriptions && node.typeDescriptions.typeString) { + return `${node.typeDescriptions.typeString}${node.name && node.name.length ? ` ${node.name}` : ''}` + } + } + + 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 getOverrides = async (node: any) => { + if (node.overrides) { + let overrides = [] + for (const override of node.overrides.overrides) { + overrides.push(override.name) + } + if (overrides.length) + return ` overrides (${overrides.join(', ')})` + return '' + } + } + + const getlinearizedBaseContracts = async (node: any) => { + let params = [] + for (const id of node.linearizedBaseContracts) { + const baseContract = await this.propsplugin.call('contextualListener', 'getNodeById', id) + params.push( + baseContract.name + ) + } + if (params.length) + return `is ${params.join(', ')}` + return '' + } + + if (!nodeDefinition) return null + if (nodeDefinition.absolutePath) { + const target = await this.propsplugin.call('fileManager', 'getPathFromUrl', nodeDefinition.absolutePath) + if (target.file !== nodeDefinition.absolutePath) { + contents.push({ + value: `${target.file}` + }) + } + contents.push({ + value: `${nodeDefinition.absolutePath}` + }) + } + if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'VariableDeclaration') { + contents.push({ + value: await getVariableDeclaration(nodeDefinition) + }) + + } + else if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'ElementaryTypeName') { + contents.push({ + value: `${nodeDefinition.typeDescriptions.typeString}` + }) + + } else if (nodeDefinition.nodeType === 'FunctionDefinition') { + contents.push({ + value: `function ${nodeDefinition.name} ${await getParamaters(nodeDefinition.parameters)} ${nodeDefinition.visibility} ${nodeDefinition.stateMutability}${await getOverrides(nodeDefinition)} returns ${await getParamaters(nodeDefinition.returnParameters)}` + }) + + getDocs(nodeDefinition) + } else if (nodeDefinition.nodeType === 'ContractDefinition') { + contents.push({ + value: `${nodeDefinition.contractKind} ${nodeDefinition.name} ${await getlinearizedBaseContracts(nodeDefinition)}` + }) + getDocs(nodeDefinition) + + } else { + contents.push({ + value: `${nodeDefinition.nodeType}` + }) + getDocs(nodeDefinition) + } + getLinks(nodeDefinition) + for (const key in contents) { + contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```' + } + + return { + range: new monaco.Range( + position.lineNumber, + position.column, + position.lineNumber, + model.getLineMaxColumn(position.lineNumber) + ), + contents: contents + }; + } + +} \ No newline at end of file diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 05f746cb0b..9999c36864 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -10,6 +10,7 @@ import { loadTypes } from './web-types' import monaco from '../types/monaco' import { IPosition, languages } from 'monaco-editor' import { sourceMappingDecoder } from '@remix-project/remix-debug' +import { RemixHoverProvider } from './providers/hoverProvider' type cursorPosition = { startLineNumber: number, @@ -435,6 +436,7 @@ export const EditorUI = (props: EditorUIProps) => { monacoRef.current.languages.registerReferenceProvider('remix-solidity', { async provideReferences(model: monaco.editor.ITextModel, position: monaco.Position, context: any, token: monaco.CancellationToken) { + const cursorPosition = props.editorAPI.getCursorPosition() const nodes = await props.plugin.call('contextualListener', 'referrencesAtPosition', cursorPosition) const references = [] @@ -471,91 +473,138 @@ export const EditorUI = (props: EditorUIProps) => { } }) - - monacoRef.current.languages.registerHoverProvider('remix-solidity', { provideHover: async function (model: any, position: monaco.Position) { - console.log('--------------------') const cursorPosition = props.editorAPI.getHoverPosition(position) const nodeDefinition = await props.plugin.call('contextualListener', 'definitionAtPosition', cursorPosition) console.log(nodeDefinition) 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` - }) + if (node.documentation && node.documentation.text) { + let text = '' + node.documentation.text.split('\n').forEach(line => { + text += `${line.trim()}\n` + }) + contents.push({ + + value: text + }) + } + } + + const getLinks = async (node: any) => { + const position = await props.plugin.call('contextualListener', 'positionOfDefinition', node) + const lastCompilationResult = await props.plugin.call('compilerArtefacts', 'getLastCompilationResult') + const filename = lastCompilationResult.getSourceName(position.file) + console.log(filename, position) + const lineColumn = await props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', + position, + position.file, + lastCompilationResult.getSourceCode().sources, + lastCompilationResult.getAsts()) contents.push({ - value: text + value: `${filename} ${lineColumn.start.line}:${lineColumn.start.column}` }) - } } - + const getVariableDeclaration = async (node: any) => { - if(node.typeDescriptions && node.typeDescriptions.typeString) { - return `${node.typeDescriptions.typeString} ${node.name}` - } + if (node.typeDescriptions && node.typeDescriptions.typeString) { + return `${node.typeDescriptions.typeString}${node.name && node.name.length ? ` ${node.name}` : ''}` + } + } + + 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 getOverrides = async (node: any) => { + if (node.overrides) { + let overrides = [] + for (const override of node.overrides.overrides) { + overrides.push(override.name) + } + if (overrides.length) + return ` overrides (${overrides.join(', ')})` + return '' + } } - const getParamaters = async (node: any) => { - if(node.parameters && node.parameters.parameters) { + const getlinearizedBaseContracts = async (node: any) => { let params = [] - for(const param of node.parameters.parameters) { - params.push(await getVariableDeclaration(param)) + for (const id of node.linearizedBaseContracts) { + const baseContract = await props.plugin.call('contextualListener', 'getNodeById', id) + params.push( + baseContract.name + ) } - return `(${params.join(', ')})` - } + if (params.length) + return `is ${params.join(', ')}` + return '' } if (!nodeDefinition) return null if (nodeDefinition.absolutePath) { - const target = await props.plugin.call('fileManager', 'getPathFromUrl', nodeDefinition.absolutePath) - if (target.file !== nodeDefinition.absolutePath) { + const target = await props.plugin.call('fileManager', 'getPathFromUrl', nodeDefinition.absolutePath) + if (target.file !== nodeDefinition.absolutePath) { + contents.push({ + value: `${target.file}` + }) + } contents.push({ - value: `${target.file}` + value: `${nodeDefinition.absolutePath}` }) - } - contents.push({ - value: `${nodeDefinition.absolutePath}` - }) } if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'VariableDeclaration') { - contents.push({ - value: await getVariableDeclaration(nodeDefinition) - }) + contents.push({ + value: await getVariableDeclaration(nodeDefinition) + }) } else if (nodeDefinition.typeDescriptions && nodeDefinition.nodeType === 'ElementaryTypeName') { - contents.push({ - value: `${nodeDefinition.typeDescriptions.typeString}` - }) + contents.push({ + value: `${nodeDefinition.typeDescriptions.typeString}` + }) } else if (nodeDefinition.nodeType === 'FunctionDefinition') { - contents.push({ - value: `(${nodeDefinition.visibility} function) ${nodeDefinition.name}: ${await getParamaters(nodeDefinition)}` - }) - getDocs(nodeDefinition) + contents.push({ + value: `function ${nodeDefinition.name} ${await getParamaters(nodeDefinition.parameters)} ${nodeDefinition.visibility} ${nodeDefinition.stateMutability}${await getOverrides(nodeDefinition)} returns ${await getParamaters(nodeDefinition.returnParameters)}` + }) + + getDocs(nodeDefinition) + } else if (nodeDefinition.nodeType === 'ContractDefinition') { + contents.push({ + value: `${nodeDefinition.contractKind} ${nodeDefinition.name} ${await getlinearizedBaseContracts(nodeDefinition)}` + }) + getDocs(nodeDefinition) + } else { - contents.push({ - value: `${nodeDefinition.nodeType}` - }) - getDocs(nodeDefinition) + contents.push({ + value: `${nodeDefinition.nodeType}` + }) + getDocs(nodeDefinition) } + getLinks(nodeDefinition) for (const key in contents) { - contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```' + contents[key].value = '```remix-solidity\n' + contents[key].value + '\n```' } + return { - range: new monaco.Range( - position.lineNumber, - position.column, - position.lineNumber, - model.getLineMaxColumn(position.lineNumber) - ), - contents: contents + range: new monaco.Range( + position.lineNumber, + position.column, + position.lineNumber, + model.getLineMaxColumn(position.lineNumber) + ), + contents: contents }; - } + } }) loadTypes(monacoRef.current)