diff --git a/.circleci/config.yml b/.circleci/config.yml index 00935b2a78..4e447bd417 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -496,10 +496,10 @@ jobs: working_directory: ~/remix-project steps: + - checkout - node/install: install-yarn: true node-version: "v14.17.6" - - checkout - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production @@ -529,6 +529,9 @@ jobs: steps: - checkout + - node/install: + install-yarn: true + node-version: "v14.17.6" - run: yarn - run: yarn run downloadsolc_assets - run: yarn run build:production diff --git a/apps/remix-ide-e2e/src/commands/addFile.ts b/apps/remix-ide-e2e/src/commands/addFile.ts index eda0f320c7..6eac6b7b9d 100644 --- a/apps/remix-ide-e2e/src/commands/addFile.ts +++ b/apps/remix-ide-e2e/src/commands/addFile.ts @@ -13,18 +13,22 @@ class AddFile extends EventEmitter { } } -function addFile(browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) { +function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) { browser + .saveScreenshot('./reports/screenshots/addFile.png') .isVisible({ selector: "//*[@data-id='sidePanelSwapitTitle' and contains(.,'File explorer')]", locateStrategy: 'xpath', suppressNotFoundErrors: true, - timeout: 1000 + timeout: 1000 }, (okVisible) => { if (!okVisible.value) { browser.clickLaunchIcon('filePanel') + .saveScreenshot('./reports/screenshots/addFile2.png') } }) + .scrollInto('li[data-id="treeViewLitreeViewItemREADME.txt"]') + .saveScreenshot('./reports/screenshots/addFile3.png') .waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]') .click('li[data-id="treeViewLitreeViewItemREADME.txt"]').pause(1000) // focus on root directory .elements('css selector', `li[data-id="treeViewLitreeViewItem${name}"]`, (res) => { diff --git a/apps/remix-ide-e2e/src/helpers/init.ts b/apps/remix-ide-e2e/src/helpers/init.ts index 89b2190844..0f486d7f79 100644 --- a/apps/remix-ide-e2e/src/helpers/init.ts +++ b/apps/remix-ide-e2e/src/helpers/init.ts @@ -26,8 +26,8 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url .waitForElementVisible('[data-id="sidePanelSwapitTitle"]') .perform(done()) }) - .maximizeWindow() - .fullscreenWindow(() => { + //.maximizeWindow() + .perform(() => { if (preloadPlugins) { initModules(browser, () => { browser diff --git a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts index 9ca0bb89ba..ef6213ea23 100644 --- a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts +++ b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts @@ -163,7 +163,7 @@ const remixIdeThemes = { }, flatly: { primary: '#2C3E50', - secondary: '#95a5a6', + secondary: '#dadfe0', success: '#18BC9C', info: '#3498DB', warning: '#F39C12', @@ -171,7 +171,7 @@ const remixIdeThemes = { }, spacelab: { primary: '#446E9B', - secondary: '#999', + secondary: '#dadfe0', success: '#3CB521', info: '#3399F3', warning: '#D47500', @@ -179,7 +179,7 @@ const remixIdeThemes = { }, cyborg: { primary: '#2A9FD6', - secondary: '#555', + secondary: '#3c3939', success: '#77B300', info: '#93C', warning: '#F80', 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 b763f81a3c..4e8db57d56 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -6,6 +6,7 @@ import { CompilationResult } from '@remix-project/remix-solidity' import CodeParserGasService from './services/code-parser-gas-service' import CodeParserCompiler from './services/code-parser-compiler' import CodeParserAntlrService from './services/code-parser-antlr-service' +import CodeParserImports, { CodeParserImportsData } from './services/code-parser-imports' import React from 'react' import { Profile } from '@remixproject/plugin-utils' import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from 'dist/libs/remix-analyzer/src/types' @@ -15,7 +16,7 @@ import { ParseResult } from './types/antlr-types' const profile: Profile = { name: 'codeParser', - methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'], + methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates', 'getImports'], events: [], version: '0.0.1' } @@ -70,12 +71,15 @@ export class CodeParser extends Plugin { gasService: CodeParserGasService compilerService: CodeParserCompiler antlrService: CodeParserAntlrService + importService: CodeParserImports parseSolidity: (text: string) => Promise getLastNodeInLine: (ast: string) => Promise listAstNodes: () => Promise getANTLRBlockAtPosition: (position: any, text?: string) => Promise getCurrentFileAST: (text?: string) => Promise + getImports: () => Promise + constructor(astWalker: any) { super(profile) @@ -94,7 +98,7 @@ export class CodeParser extends Plugin { } const showGasSettings = await this.call('config', 'getAppParameter', 'show-gas') const showErrorSettings = await this.call('config', 'getAppParameter', 'display-errors') - if(showGasSettings || showErrorSettings || completionSettings) { + if (showGasSettings || showErrorSettings || completionSettings) { await this.compilerService.compile() } } @@ -104,13 +108,14 @@ export class CodeParser extends Plugin { this.gasService = new CodeParserGasService(this) this.compilerService = new CodeParserCompiler(this) this.antlrService = new CodeParserAntlrService(this) + this.importService = new CodeParserImports(this) this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService) this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService) this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService) this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService) this.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService) - + this.getImports = this.importService.getImports.bind(this.importService) this.on('editor', 'didChangeFile', async (file) => { await this.call('editor', 'discardLineTexts') @@ -119,8 +124,17 @@ export class CodeParser extends Plugin { this.on('filePanel', 'setWorkspace', async () => { await this.call('fileDecorator', 'clearFileDecorators') + await this.importService.setFileTree() }) + this.on('fileManager', 'fileAdded', async () => { + await this.importService.setFileTree() + }) + this.on('fileManager', 'fileRemoved', async () => { + await this.importService.setFileTree() + }) + + this.on('fileManager', 'currentFileChanged', async () => { await this.call('editor', 'discardLineTexts') @@ -135,8 +149,6 @@ export class CodeParser extends Plugin { } - - /** * * @returns @@ -145,10 +157,6 @@ export class CodeParser extends Plugin { return this.compilerAbstract } - - - - getSubNodes(node: T): number[] { return node.nodeType == "ContractDefinition" && node.contractDependencies; } 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 4a31356052..96daa42f4a 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 @@ -8,22 +8,23 @@ import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' import { sourceMappingDecoder } from '@remix-project/remix-debug' import { CompilerRetriggerMode } from '@remix-project/remix-solidity-ts'; import { MarkerSeverity } from 'monaco-editor'; +import { findLinesInStringWithMatch, SearchResultLine } from '@remix-ui/search' type errorMarker = { message: string severity: MarkerSeverity position: { - start: { - line: number - column: number - }, - end: { - line: number - column: number - } + start: { + line: number + column: number + }, + end: { + line: number + column: number + } }, file: string - } +} export default class CodeParserCompiler { plugin: CodeParser compiler: any // used to compile the current file seperately from the main compiler @@ -43,36 +44,47 @@ export default class CodeParserCompiler { this.errorState = true const result = new CompilerAbstract('soljson', data, source, input) let allErrors: errorMarker[] = [] - if (data.errors) { - const sources = result.getSourceCode().sources - for (const error of data.errors) { - - const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content) - const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({ - start: error.sourceLocation.start, - length: error.sourceLocation.end - error.sourceLocation.start - }, lineBreaks) - - const filePath = error.sourceLocation.file - - allErrors = [...allErrors, { - message: error.formattedMessage, - severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, - position: { - start: { - line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, - column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 - }, - end: { - line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, - column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 + if (data.errors || data.error) { + const file = await this.plugin.call('fileManager', 'getCurrentFile') + const currentFileContent = await this.plugin.call('fileManager', 'readFile', file) + const sources = result.getSourceCode().sources || [] + if (data.error) { + if (data.error.formattedMessage) { + // mark this file as error + const errorMarker = await this.createErrorMarker(data.error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } }) + allErrors = [...allErrors, errorMarker] + } + } else { + for (const error of data.errors) { + if (!error.sourceLocation) { + // mark this file as error + const errorMarker = await this.createErrorMarker(error, file, { start: { line: 0, column: 0 }, end: { line: 0, column: 100 } }) + allErrors = [...allErrors, errorMarker] + } else { + const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content) + const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({ + start: error.sourceLocation.start, + length: error.sourceLocation.end - error.sourceLocation.start + }, lineBreaks) + + + const filePath = error.sourceLocation.file + const fileTarget = await this.plugin.call('fileManager', 'getUrlFromPath', filePath) + + const importFilePositions = await this.getPositionForImportErrors(fileTarget.file, currentFileContent) + for (const importFilePosition of importFilePositions) { + for (const line of importFilePosition.lines) { + allErrors = [...allErrors, await this.createErrorMarker(error, file, line.position)] + } } + + allErrors = [...allErrors, await this.createErrorMarker(error, filePath, lineColumn)] } - , file: filePath - }] + } } + const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') - if(displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors) + if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors) this.addDecorators(allErrors, sources) } else { await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) @@ -131,7 +143,7 @@ export default class CodeParserCompiler { "*": ["evm.gasEstimates"] } }, - "evmVersion": state.evmVersion && state.evmVersion.toString() || "byzantium", + "evmVersion": state.evmVersion && state.evmVersion.toString() || "berlin", } } @@ -143,14 +155,14 @@ export default class CodeParserCompiler { this.compiler.compile(sources, this.plugin.currentFile) } } catch (e) { - // do nothing + // do nothing } } async addDecorators(allErrors: errorMarker[], sources: any) { const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors') - if(!displayErrors) return - const errorsPerFiles: {[fileName: string]: errorMarker[]} = {} + if (!displayErrors) return + const errorsPerFiles: { [fileName: string]: errorMarker[] } = {} for (const error of allErrors) { if (!errorsPerFiles[error.file]) { errorsPerFiles[error.file] = [] @@ -164,7 +176,7 @@ export default class CodeParserCompiler { } // sort errorPerFiles by error priority - const sortedErrorsPerFiles: {[fileName: string]: errorMarker[]} = {} + const sortedErrorsPerFiles: { [fileName: string]: errorMarker[] } = {} for (const fileName in errorsPerFiles) { const errors = errorsPerFiles[fileName] errors.sort((a, b) => { @@ -178,10 +190,11 @@ export default class CodeParserCompiler { const decorators: fileDecoration[] = [] for (const fileName in sortedErrorsPerFiles) { const errors = sortedErrorsPerFiles[fileName] + const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName) const decorator: fileDecoration = { - path: fileName, + path: fileTarget.file, isDirectory: false, - fileStateType: errors[0].severity == MarkerSeverity.Error? fileDecorationType.Error : fileDecorationType.Warning, + fileStateType: errors[0].severity == MarkerSeverity.Error ? fileDecorationType.Error : fileDecorationType.Warning, fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning', fileStateIconClass: '', fileStateIcon: '', @@ -193,8 +206,9 @@ export default class CodeParserCompiler { decorators.push(decorator) } for (const fileName of filesWithOutErrors) { + const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName) const decorator: fileDecoration = { - path: fileName, + path: fileTarget.file, isDirectory: false, fileStateType: fileDecorationType.None, fileStateLabelClass: '', @@ -211,8 +225,27 @@ export default class CodeParserCompiler { } + async createErrorMarker(error: any, filePath: string, lineColumn): Promise { + return { + message: error.formattedMessage, + severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning, + position: { + start: { + line: ((lineColumn.start && lineColumn.start.line) || 0) + 1, + column: ((lineColumn.start && lineColumn.start.column) || 0) + 1 + }, + end: { + line: ((lineColumn.end && lineColumn.end.line) || 0) + 1, + column: ((lineColumn.end && lineColumn.end.column) || 0) + 1 + } + } + , file: filePath + } + } + async clearDecorators(sources: any) { const decorators: fileDecoration[] = [] + if (!sources) return for (const fileName of Object.keys(sources)) { const decorator: fileDecoration = { path: fileName, @@ -232,4 +265,13 @@ export default class CodeParserCompiler { await this.plugin.call('fileDecorator', 'setFileDecorators', decorators) } + async getPositionForImportErrors(importedFileName: string, text: string) { + const re = new RegExp(importedFileName, 'gi') + const result: SearchResultLine[] = findLinesInStringWithMatch( + text, + re + ) + return result + } + } \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts new file mode 100644 index 0000000000..9eaef92dd9 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts @@ -0,0 +1,82 @@ +'use strict' +import { CodeParser } from "../code-parser"; + +export type CodeParserImportsData= { + files?: string[], + modules?: string[], + packages?: string[], +} + +export default class CodeParserImports { + plugin: CodeParser + + data: CodeParserImportsData = {} + constructor(plugin: CodeParser) { + this.plugin = plugin + this.init() + } + + async getImports(){ + return this.data + } + + async init() { + // @ts-ignore + const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt') + this.data.modules = txt.default.split('\n') + .filter(x => x !== '') + .map(x => x.replace('./node_modules/', '')) + .filter(x => { + if(x.includes('@openzeppelin')) { + return !x.includes('mock') + }else{ + return true + } + }) + + // get unique first words of the values in the array + this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))] + } + + setFileTree = async () => { + this.data.files = await this.getDirectory('/') + this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git')) + } + + getDirectory = async (dir: string) => { + let result = [] + const files = await this.plugin.call('fileManager', 'readdir', dir) + const fileArray = this.normalize(files) + for (const fi of fileArray) { + if (fi) { + const type = fi.data.isDirectory + if (type === true) { + result = [...result, ...(await this.getDirectory(`${fi.filename}`))] + } else { + result = [...result, fi.filename] + } + } + } + return result + } + + normalize = filesList => { + const folders = [] + const files = [] + Object.keys(filesList || {}).forEach(key => { + if (filesList[key].isDirectory) { + folders.push({ + filename: key, + data: filesList[key] + }) + } else { + files.push({ + filename: key, + data: filesList[key] + }) + } + }) + return [...folders, ...files] + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css index 30026bb48b..ced466267d 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css @@ -25,7 +25,7 @@ --gray:#555; --gray-dark:#222; --primary:#2a9fd6; - --secondary:#555; + --secondary:#3c3939; --success:#77b300; --info:#93c; --warning:#f80; @@ -5595,7 +5595,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#2180ac!important } .bg-secondary { - background-color:#555!important + background-color:#3c3939!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:#3c3c3c!important @@ -8358,7 +8358,7 @@ a.text-warning:focus,a.text-warning:hover { color:#b35f00!important } .text-danger { - color:#c00!important + color:#ff4242!important } a.text-danger:focus,a.text-danger:hover { color:maroon!important @@ -8594,7 +8594,7 @@ legend { color:#555 } .nav-pills .nav-link.active,.nav-tabs .nav-link.active { - background-color:#2a9fd6 + background-color:#034767 } .breadcrumb a { color:#fff diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css index c62ab28e0f..5940867b25 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css @@ -24,7 +24,7 @@ --gray:#95a5a6; --gray-dark:#343a40; --primary:#2c3e50; - --secondary:#95a5a6; + --secondary:#dadfe0; --success:#18bc9c; --info:#3498db; --warning:#f39c12; @@ -4460,7 +4460,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#1a252f!important } .bg-secondary { - background-color:#95a5a6!important + background-color:#dadfe0!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:#798d8f!important diff --git a/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css b/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css index 4639bcdad8..b8e90b0107 100644 --- a/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css +++ b/apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css @@ -25,7 +25,7 @@ --gray:#777; --gray-dark:#333; --primary:#446e9b; - --secondary:#999; + --secondary:#dadfe0; --success:#3cb521; --info:#3399f3; --warning:#d47500; @@ -5596,7 +5596,7 @@ a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary: background-color:#345578!important } .bg-secondary { - background-color:#999!important + background-color:#dadfe0!important } a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover { background-color:gray!important diff --git a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css index e039facfec..28503c8562 100644 --- a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css +++ b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css @@ -3641,7 +3641,7 @@ input[type="submit"].btn-block { .nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { color: #fff; - background-color: var(--body-bg); + background-color: #2a2c3f; border-color: #3f4455; } .nav-tabs .dropdown-menu { 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..4d69093b1c 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -1,5 +1,12 @@ import { IRange } from "monaco-editor"; import monaco from "../../../types/monaco"; +import path from "path"; + +type CodeParserImportsData = { + files?: string[], + modules?: string[], + packages?: string[], +} export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ @@ -176,13 +183,6 @@ export function getCompletionSnippets(range: IRange, monaco): monaco.languages.C insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, range }, - { - label: 'import', - kind: monaco.languages.CompletionItemKind.Snippet, - insertText: 'import "${1:library}";', - insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, - range - }, { label: 'SPDX-License-Identifier', kind: monaco.languages.CompletionItemKind.Snippet, @@ -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 => { @@ -390,6 +390,89 @@ export function GeCompletionUnits(range: IRange, monaco): monaco.languages.Compl return completionItems; } +export function GetImports(range: IRange + , monaco, data: CodeParserImportsData + , word: string +): monaco.languages.CompletionItem[] { + let list = [] + if (!word.startsWith('@')) { + word = word.replace('"', ''); + const nextPaths = [...new Set(data.files + .filter((item) => item.startsWith(word)) + .map((item) => item.replace(word, '').split('/')[0]))] + + list = [...list, ...nextPaths + .filter((item) => !item.endsWith('.sol')) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Folder, + range: range, + label: `${item}`, + insertText: `${item}`, + } + })] + + + list = [...list, + ...data.files + .filter((item) => item.startsWith(word)) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.File, + range: range, + label: `${item}`, + insertText: `${item.replace(word, '')}`, + } + })] + } + if (word === '@' || word === '') { + list = [...list, ...data.packages.map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Module, + range: range, + label: `${item}`, + insertText: word === '@' ? `${item.replace('@', '')}` : `${item}`, + } + })] + } + if (word.startsWith('@') && word.length > 1) { + const nextPaths = [...new Set(data.modules + .filter((item) => item.startsWith(word)) + .map((item) => item.replace(word, '').split('/')[0]))] + + list = [...list, ...nextPaths + .filter((item) => !item.endsWith('.sol')) + .map((item) => { + return { + kind: monaco.languages.CompletionItemKind.Folder, + range: range, + label: `${item}`, + insertText: `${item}`, + } + })] + + list = [...list + , ...data.modules + .filter((item) => item.startsWith(word)) + .map((item) => { + // remove the first part if it starts with @ + let label = item; + if (label.startsWith('@')) { + label = label.substring(label.indexOf('/') + 1); + } + const filename = path.basename(label) + return { + kind: monaco.languages.CompletionItemKind.Reference, + range: range, + label: `${filename}: ${label}`, + insertText: `${item.replace(word, '')}`, + } + }) + ] + } + return list; +}; + export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] { return [ { @@ -668,7 +751,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); @@ -680,4 +763,4 @@ export function getContextualAutoCompleteBTypeName(word: string, range: IRange, return getAddressCompletionItems(range, monaco); } return []; -} \ No newline at end of file +} diff --git a/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt b/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt new file mode 100644 index 0000000000..94615103e0 --- /dev/null +++ b/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt @@ -0,0 +1,198 @@ +./node_modules/@openzeppelin/contracts/access/AccessControl.sol +./node_modules/@openzeppelin/contracts/access/AccessControlCrossChain.sol +./node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol +./node_modules/@openzeppelin/contracts/access/IAccessControl.sol +./node_modules/@openzeppelin/contracts/access/IAccessControlEnumerable.sol +./node_modules/@openzeppelin/contracts/access/Ownable.sol +./node_modules/@openzeppelin/contracts/crosschain/amb/CrossChainEnabledAMB.sol +./node_modules/@openzeppelin/contracts/crosschain/amb/LibAMB.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL1.sol +./node_modules/@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL2.sol +./node_modules/@openzeppelin/contracts/crosschain/CrossChainEnabled.sol +./node_modules/@openzeppelin/contracts/crosschain/errors.sol +./node_modules/@openzeppelin/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol +./node_modules/@openzeppelin/contracts/crosschain/optimism/LibOptimism.sol +./node_modules/@openzeppelin/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +./node_modules/@openzeppelin/contracts/finance/PaymentSplitter.sol +./node_modules/@openzeppelin/contracts/finance/VestingWallet.sol +./node_modules/@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +./node_modules/@openzeppelin/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorProposalThreshold.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorSettings.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotes.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotesComp.sol +./node_modules/@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +./node_modules/@openzeppelin/contracts/governance/extensions/IGovernorTimelock.sol +./node_modules/@openzeppelin/contracts/governance/Governor.sol +./node_modules/@openzeppelin/contracts/governance/IGovernor.sol +./node_modules/@openzeppelin/contracts/governance/TimelockController.sol +./node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol +./node_modules/@openzeppelin/contracts/governance/utils/Votes.sol +./node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol +./node_modules/@openzeppelin/contracts/interfaces/draft-IERC2612.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155MetadataURI.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1271.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1363Spender.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC165.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC1820Registry.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC20.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC20Metadata.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC2981.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC4626.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Metadata.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC721Receiver.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777Recipient.sol +./node_modules/@openzeppelin/contracts/interfaces/IERC777Sender.sol +./node_modules/@openzeppelin/contracts/metatx/ERC2771Context.sol +./node_modules/@openzeppelin/contracts/metatx/MinimalForwarder.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol +./node_modules/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol +./node_modules/@openzeppelin/contracts/proxy/Clones.sol +./node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol +./node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol +./node_modules/@openzeppelin/contracts/proxy/Proxy.sol +./node_modules/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +./node_modules/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +./node_modules/@openzeppelin/contracts/proxy/utils/Initializable.sol +./node_modules/@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol +./node_modules/@openzeppelin/contracts/security/Pausable.sol +./node_modules/@openzeppelin/contracts/security/PullPayment.sol +./node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol +./node_modules/@openzeppelin/contracts/token/common/ERC2981.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/ERC1155.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/IERC1155.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol +./node_modules/@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesComp.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol +./node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol +./node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol +./node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +./node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol +./node_modules/@openzeppelin/contracts/token/ERC20/utils/TokenTimelock.sol +./node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol +./node_modules/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol +./node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol +./node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol +./node_modules/@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +./node_modules/@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol +./node_modules/@openzeppelin/contracts/token/ERC777/ERC777.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol +./node_modules/@openzeppelin/contracts/token/ERC777/IERC777Sender.sol +./node_modules/@openzeppelin/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol +./node_modules/@openzeppelin/contracts/utils/Address.sol +./node_modules/@openzeppelin/contracts/utils/Arrays.sol +./node_modules/@openzeppelin/contracts/utils/Base64.sol +./node_modules/@openzeppelin/contracts/utils/Checkpoints.sol +./node_modules/@openzeppelin/contracts/utils/Context.sol +./node_modules/@openzeppelin/contracts/utils/Counters.sol +./node_modules/@openzeppelin/contracts/utils/Create2.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol +./node_modules/@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol +./node_modules/@openzeppelin/contracts/utils/escrow/ConditionalEscrow.sol +./node_modules/@openzeppelin/contracts/utils/escrow/Escrow.sol +./node_modules/@openzeppelin/contracts/utils/escrow/RefundEscrow.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165Checker.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC165Storage.sol +./node_modules/@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC1820Implementer.sol +./node_modules/@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol +./node_modules/@openzeppelin/contracts/utils/math/Math.sol +./node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol +./node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol +./node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol +./node_modules/@openzeppelin/contracts/utils/math/SignedSafeMath.sol +./node_modules/@openzeppelin/contracts/utils/Multicall.sol +./node_modules/@openzeppelin/contracts/utils/StorageSlot.sol +./node_modules/@openzeppelin/contracts/utils/Strings.sol +./node_modules/@openzeppelin/contracts/utils/structs/BitMaps.sol +./node_modules/@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol +./node_modules/@openzeppelin/contracts/utils/structs/EnumerableMap.sol +./node_modules/@openzeppelin/contracts/utils/structs/EnumerableSet.sol +./node_modules/@openzeppelin/contracts/utils/Timers.sol +./node_modules/@openzeppelin/contracts/vendor/amb/IAMB.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IArbSys.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IBridge.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IInbox.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IMessageProvider.sol +./node_modules/@openzeppelin/contracts/vendor/arbitrum/IOutbox.sol +./node_modules/@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol +./node_modules/@openzeppelin/contracts/vendor/optimism/ICrossDomainMessenger.sol +./node_modules/@openzeppelin/contracts/vendor/polygon/IFxMessageProcessor.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/IUniswapV3PoolDeployer.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolActions.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolEvents.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolImmutables.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol +./node_modules/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol +./node_modules/@uniswap/v3-core/contracts/libraries/BitMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FixedPoint128.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FixedPoint96.sol +./node_modules/@uniswap/v3-core/contracts/libraries/FullMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/LiquidityMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Oracle.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Position.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SafeCast.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SqrtPriceMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/SwapMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/Tick.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TickBitmap.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TickMath.sol +./node_modules/@uniswap/v3-core/contracts/libraries/TransferHelper.sol +./node_modules/@uniswap/v3-core/contracts/libraries/UnsafeMath.sol diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 745c5e395b..7202b1076a 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,84 @@ 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('.') - let dotCompleted = false + if (context.triggerCharacter === '"' || context.triggerCharacter === '@' || context.triggerCharacter === '/') { - // 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 - 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 lastpart = line.substring(0, position.column - 1).split(';').pop() - const nameOfLastTypedExpression = lastNodeInExpression.name || lastNodeInExpression.memberName - const dotCompletions = await this.getDotCompletions(position, nameOfLastTypedExpression, range) - nodes = [...nodes, ...dotCompletions.nodes] - suggestions = [...suggestions, ...dotCompletions.suggestions] + if (lastpart.startsWith('import')) { + const imports = await this.props.plugin.call('codeParser', 'getImports') + if (context.triggerCharacter === '"' || context.triggerCharacter === '@') { + suggestions = [...suggestions, + ...GetImports(range, this.monaco, imports, context.triggerCharacter), + ] + } else if (context.triggerCharacter === '/') { + const word = line.split('"')[1] + suggestions = [...suggestions, + ...GetImports(range, this.monaco, imports, word), + ] + } else { + return + } } - } else { + } 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) + if (context.triggerCharacter === '.') { + const lineTextBeforeCursor: string = line.substring(0, position.column - 1) + const lastNodeInExpression = await this.getLastNodeInExpression(lineTextBeforeCursor) + const expressionElements = lineTextBeforeCursor.split('.') + + 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 +242,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider } suggestions.push(completion) - } + } } return { @@ -242,9 +264,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 +292,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 +301,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider return true } } - if(node.outSideBlock){ return true } + if (node.outSideBlock) { return true } return false }) @@ -387,9 +409,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)] } } diff --git a/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts index d6236cb5e3..9d6daee730 100644 --- a/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/definitionProvider.ts @@ -13,8 +13,22 @@ export class RemixDefinitionProvider implements monaco.languages.DefinitionProvi // eslint-disable-next-line @typescript-eslint/no-unused-vars async provideDefinition(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Promise { const cursorPosition = this.props.editorAPI.getCursorPosition() - const jumpLocation = await this.jumpToDefinition(cursorPosition) - + let jumpLocation = await this.jumpToDefinition(cursorPosition) + if (!jumpLocation || !jumpLocation.fileName) { + const line = model.getLineContent(position.lineNumber) + const lastpart = line.substring(0, position.column - 1).split(';').pop() + if (lastpart.startsWith('import')) { + const importPath = line.substring(lastpart.indexOf('"') + 1) + const importPath2 = importPath.substring(0, importPath.indexOf('"')) + jumpLocation = { + startLineNumber: 1, + startColumn: 1, + endColumn: 1, + endLineNumber: 1, + fileName: importPath2 + } + } + } return [{ uri: this.monaco.Uri.parse(jumpLocation.fileName), range: { 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 715ecf9c7a..d51cd7f0d0 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -158,6 +158,7 @@ export const EditorUI = (props: EditorUIProps) => { const infoColor = formatColor('--info') const darkColor = formatColor('--dark') const secondaryColor = formatColor('--secondary') + const primaryColor = formatColor('--primary') const textColor = formatColor('--text') || darkColor const textbackground = formatColor('--text-background') || lightColor @@ -266,7 +267,8 @@ export const EditorUI = (props: EditorUIProps) => { 'editorSuggestWidget.background': lightColor, 'editorSuggestWidget.selectedBackground': secondaryColor, 'editorSuggestWidget.selectedForeground': textColor, - 'editorSuggestWidget.highlightForeground': infoColor, + 'editorSuggestWidget.highlightForeground': primaryColor, + 'editorSuggestWidget.focusHighlightForeground': infoColor, 'editor.lineHighlightBorder': secondaryColor, 'editor.lineHighlightBackground': textbackground === darkColor ? lightColor : secondaryColor, 'editorGutter.background': lightColor, diff --git a/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx index 75c3698f1f..37fa018c92 100644 --- a/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx +++ b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx @@ -15,7 +15,7 @@ export function RemixPluginPanel (props: RemixPanelProps) { <> {props.header}
-
+
{Object.values(props.plugins).map((pluginRecord) => { return })} diff --git a/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx b/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx index b15d0e52ed..793d2ea076 100644 --- a/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx +++ b/libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx @@ -52,7 +52,7 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
{to.displayName} :

{to.description || No description Provided}

{pluginMessage()} - { sensitiveCall ?

You are going to process a sensitive call. Please make sure you trust this plugin.

: '' } + { sensitiveCall ?

Make sure you trust this plugin before processing this call.

: '' }
{ !sensitiveCall &&
diff --git a/libs/remix-ui/search/src/index.ts b/libs/remix-ui/search/src/index.ts index 8617e2ddff..72588397c4 100644 --- a/libs/remix-ui/search/src/index.ts +++ b/libs/remix-ui/search/src/index.ts @@ -1 +1,3 @@ -export { SearchTab } from './lib/components/Search'; \ No newline at end of file +export { SearchTab } from './lib/components/Search'; +export * from './lib/components/results/SearchHelper'; +export * from './lib/types'; \ No newline at end of file diff --git a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx index 84aab5e5d0..e0379d1b11 100644 --- a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx +++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx @@ -80,9 +80,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version) return tmp ? tmp[1] : version } - if (version != '' && !semver.gt(truncateVersion(version), '0.4.12')) { + if (version && version != '' && !semver.gt(truncateVersion(version), '0.4.12')) { setIsSupportedVersion(false) - setRunButtonTitle('Sselect Solidity compiler version greater than 0.4.12.') + setRunButtonTitle('Select Solidity compiler version greater than 0.4.12.') } else { setIsSupportedVersion(true) setRunButtonTitle('Run static analysis') diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index afe4c54897..4bca319f0e 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -186,7 +186,7 @@ export const TabsUI = (props: TabsUIProps) => { }} > - {props.tabs.map((tab, i) => {renderTab(tab, i)})} + {props.tabs.map((tab, i) => {renderTab(tab, i)})} {props.tabs.map((tab) => )} diff --git a/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css b/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css index db1165b494..44e67f30fd 100644 --- a/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css +++ b/libs/remix-ui/workspace/src/lib/css/remix-ui-workspace.css @@ -7,7 +7,7 @@ } .remixui_fileexplorer { position : relative; - overflow-y : auto; + overflow-y : hidden; } .remixui_fileExplorerTree { cursor : default; diff --git a/package.json b/package.json index cb6a863b2b..90740ac3d5 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "test-browser": "npm-run-all -lpr selenium make-mock-compiler serve browsertest", "watch": "watchify apps/remix-ide/src/index.js -dv -p browserify-reload -o apps/remix-ide/build/app.js --exclude solc", "reinstall": "rm ./node-modules/ -rf && rm yarn.lock && rm ./build/ -rf && yarn install & yarn run build", - "ganache-cli": "npx ganache-cli" + "ganache-cli": "npx ganache-cli", + "build-contracts": "find ./node_modules/@openzeppelin/contracts | grep -i '.sol' > libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt && find ./node_modules/@uniswap/v3-core/contracts | grep -i '.sol' >> libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt" }, "browserify": { "transform": [ @@ -263,6 +264,8 @@ "@types/ws": "^7.2.4", "@typescript-eslint/eslint-plugin": "^4.32.0", "@typescript-eslint/parser": "^4.32.0", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v3-core": "^1.0.1", "ace-mode-lexon": "^1.*.*", "ace-mode-move": "0.0.1", "ace-mode-solidity": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index 498e79f2a1..1f51deb7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4813,6 +4813,16 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@uniswap/v2-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"