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 9acd434728..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 @@ -39,41 +40,51 @@ export default class CodeParserCompiler { init() { this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => { - //console.log('onAstFinished', success, data, source, input, version) this.plugin.call('editor', 'clearAnnotations') 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) { - //console.log(error) - 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) @@ -144,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] = [] @@ -165,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) => { @@ -179,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: '', @@ -194,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: '', @@ -212,9 +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 + if (!sources) return for (const fileName of Object.keys(sources)) { const decorator: fileDecoration = { path: fileName, @@ -234,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 index d2261c3154..9eaef92dd9 100644 --- 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 @@ -23,8 +23,6 @@ export default class CodeParserImports { async init() { // @ts-ignore const txt = await import('raw-loader!libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt') - - // eslint-disable-next-line @typescript-eslint/no-unused-vars this.data.modules = txt.default.split('\n') .filter(x => x !== '') .map(x => x.replace('./node_modules/', '')) @@ -38,13 +36,11 @@ export default class CodeParserImports { // get unique first words of the values in the array this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))] - console.log(this.data) } setFileTree = async () => { this.data.files = await this.getDirectory('/') - this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps')) - console.log(this.data) + this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git')) } getDirectory = async (dir: string) => { 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 ac8e3b9a61..4d69093b1c 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -183,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, 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 index 94615103e0..8342638bf6 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt +++ b/libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt @@ -1,3 +1,34 @@ +./node_modules/@hq20/contracts/contracts/access/AccessControlBasic.sol +./node_modules/@hq20/contracts/contracts/access/Administered.sol +./node_modules/@hq20/contracts/contracts/access/AuthorizedAccess.sol +./node_modules/@hq20/contracts/contracts/access/Community.sol +./node_modules/@hq20/contracts/contracts/access/Democracy.sol +./node_modules/@hq20/contracts/contracts/access/Hierarchy.sol +./node_modules/@hq20/contracts/contracts/classifieds/Classifieds.sol +./node_modules/@hq20/contracts/contracts/dao/DAO.sol +./node_modules/@hq20/contracts/contracts/dao/VentureEth.sol +./node_modules/@hq20/contracts/contracts/energy/EnergyMarket.sol +./node_modules/@hq20/contracts/contracts/exchange/UniswapExchange.sol +./node_modules/@hq20/contracts/contracts/exchange/UniswapFactory.sol +./node_modules/@hq20/contracts/contracts/introspection/erc165/ERC20Whitelisted.sol +./node_modules/@hq20/contracts/contracts/introspection/erc165/IWhitelist.sol +./node_modules/@hq20/contracts/contracts/introspection/erc165/WhitelistERC165.sol +./node_modules/@hq20/contracts/contracts/introspection/erc165/WhitelistInterfaceId.sol +./node_modules/@hq20/contracts/contracts/issuance/Issuance.sol +./node_modules/@hq20/contracts/contracts/issuance/IssuanceEth.sol +./node_modules/@hq20/contracts/contracts/lists/DoubleLinkedList.sol +./node_modules/@hq20/contracts/contracts/lists/LinkedList.sol +./node_modules/@hq20/contracts/contracts/math/DecimalMath.sol +./node_modules/@hq20/contracts/contracts/state/StateMachine.sol +./node_modules/@hq20/contracts/contracts/token/ERC20DividendableEth.sol +./node_modules/@hq20/contracts/contracts/token/ERC20Mintable.sol +./node_modules/@hq20/contracts/contracts/token/ERC721Mintable.sol +./node_modules/@hq20/contracts/contracts/token/IERC20Detailed.sol +./node_modules/@hq20/contracts/contracts/token/IERC20Mintable.sol +./node_modules/@hq20/contracts/contracts/utils/SafeCast.sol +./node_modules/@hq20/contracts/contracts/voting/Democratic.sol +./node_modules/@hq20/contracts/contracts/voting/OneManOneVote.sol +./node_modules/@hq20/contracts/contracts/voting/OneTokenOneVote.sol ./node_modules/@openzeppelin/contracts/access/AccessControl.sol ./node_modules/@openzeppelin/contracts/access/AccessControlCrossChain.sol ./node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol diff --git a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts index 78455fbe22..dafcd10011 100644 --- a/libs/remix-ui/editor/src/lib/providers/completionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/completionProvider.ts @@ -28,16 +28,16 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider startColumn: word.startColumn, endColumn: word.endColumn }; - console.log(word) + const line = model.getLineContent(position.lineNumber) let nodes: AstNode[] = [] let suggestions: monaco.languages.CompletionItem[] = [] - console.log('context', context.triggerCharacter) + if (context.triggerCharacter === '"' || context.triggerCharacter === '@' || context.triggerCharacter === '/') { const lastpart = line.substring(0, position.column - 1).split(';').pop() - console.log('lastpart', lastpart) + if (lastpart.startsWith('import')) { const imports = await this.props.plugin.call('codeParser', 'getImports') if (context.triggerCharacter === '"' || context.triggerCharacter === '@') { @@ -46,7 +46,6 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider ] } else if (context.triggerCharacter === '/') { const word = line.split('"')[1] - console.log(word) suggestions = [...suggestions, ...GetImports(range, this.monaco, imports, word), ] 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/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/package.json b/package.json index e73b61e6ee..e41139dd69 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "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", - "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" + "build-contracts": "find ./node_modules/@hq20/contracts | grep -i '.sol' > libs/remix-ui/editor/src/lib/providers/completion/contracts/contracts.txt && 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": [