From 566f8ae9e24cd25494118a0462eada3ddb967d37 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 21 Jul 2022 12:39:21 +0530 Subject: [PATCH 001/194] remove provider plugins from UI list --- apps/remix-ide/src/remixAppManager.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 159f704e1d..abb99372a0 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -4,14 +4,16 @@ import { QueryParams } from '@remix-project/remix-lib' import { IframePlugin } from '@remixproject/engine-web' const _paq = window._paq = window._paq || [] +// requiredModule removes the plugin from the plugin manager list on UI const requiredModules = [ // services + layout views + system views 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', - 'hardhat-provider', 'compileAndRun', 'search', 'recorder'] + 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'compileAndRun', 'search', 'recorder'] -const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) +// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) +const dependentModules = ['hardhat', 'truffle', 'slither'] const sensitiveCalls = { 'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'], @@ -20,6 +22,7 @@ const sensitiveCalls = { } export function isNative(name) { + // nativePlugin allows to bypass the permission request const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider'] return nativePlugins.includes(name) || requiredModules.includes(name) } From 2baa4680e3150fd97181ff4723739f5deba6f56e Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 21 Jul 2022 12:42:05 +0530 Subject: [PATCH 002/194] line change --- apps/remix-ide/src/remixAppManager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index abb99372a0..332b119542 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -10,7 +10,8 @@ const requiredModules = [ // services + layout views + system views 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', - 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'compileAndRun', 'search', 'recorder'] + 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider', + 'compileAndRun', 'search', 'recorder'] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['hardhat', 'truffle', 'slither'] @@ -23,7 +24,8 @@ const sensitiveCalls = { export function isNative(name) { // nativePlugin allows to bypass the permission request - const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting', + 'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider'] return nativePlugins.includes(name) || requiredModules.includes(name) } From 5ad402fc417e8512e2c9c2bb98552bf1a2d02e31 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:04 +0200 Subject: [PATCH 003/194] add test --- apps/remix-ide/src/app/panels/tab-proxy.js | 6 ++ .../src/app/plugins/file-decorator.ts | 60 +++++++++++ libs/remix-ui/file-decorators/.babelrc | 4 + libs/remix-ui/file-decorators/.eslintrc | 19 ++++ libs/remix-ui/file-decorators/src/index.ts | 3 + .../lib/components/file-decoration-icon.tsx | 51 ++++++++++ .../file-decoration-custom-icon.tsx | 13 +++ .../file-decoration-error-icon.tsx | 14 +++ .../file-decoration-tooltip.tsx | 33 +++++++ .../file-decoration-warning-icon.tsx | 11 +++ .../file-decorators/src/lib/helper/index.tsx | 11 +++ .../file-decorators/src/lib/types/index.ts | 29 ++++++ libs/remix-ui/file-decorators/tsconfig.json | 16 +++ .../file-decorators/tsconfig.lib.json | 13 +++ libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 99 +++++++++++++++---- libs/remix-ui/workspace/src/index.ts | 1 + .../workspace/src/lib/actions/events.ts | 12 ++- .../workspace/src/lib/actions/payload.ts | 7 ++ .../src/lib/components/file-explorer.tsx | 3 +- .../src/lib/components/file-label.tsx | 21 +++- .../src/lib/components/file-render.tsx | 18 +++- .../workspace/src/lib/reducers/workspace.ts | 31 ++++-- .../workspace/src/lib/remix-ui-workspace.tsx | 2 + .../remix-ui/workspace/src/lib/types/index.ts | 2 + 24 files changed, 444 insertions(+), 35 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/file-decorator.ts create mode 100644 libs/remix-ui/file-decorators/.babelrc create mode 100644 libs/remix-ui/file-decorators/.eslintrc create mode 100644 libs/remix-ui/file-decorators/src/index.ts create mode 100644 libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/helper/index.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/types/index.ts create mode 100644 libs/remix-ui/file-decorators/tsconfig.json create mode 100644 libs/remix-ui/file-decorators/tsconfig.lib.json diff --git a/apps/remix-ide/src/app/panels/tab-proxy.js b/apps/remix-ide/src/app/panels/tab-proxy.js index 82758c169f..3821788e18 100644 --- a/apps/remix-ide/src/app/panels/tab-proxy.js +++ b/apps/remix-ide/src/app/panels/tab-proxy.js @@ -169,6 +169,10 @@ export class TabProxy extends Plugin { this.on('manager', 'pluginDeactivated', (profile) => { this.removeTab(profile.name) }) + + this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => { + this.tabsApi.setFileDecorations(items) + }) try { this.themeQuality = (await this.call('theme', 'currentTheme') ).quality @@ -306,6 +310,7 @@ export class TabProxy extends Plugin { updateComponent(state) { return { this.tabsApi = api } this.dispatch({ + plugin: this, loadedTabs: this.loadedTabs, onSelect, onClose, diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts new file mode 100644 index 0000000000..60a14ef47f --- /dev/null +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -0,0 +1,60 @@ +'use strict' + +import { default as deepequal } from 'deep-equal' // eslint-disable-line + +import { Plugin } from '@remixproject/engine' +import { fileDecoration } from '@remix-ui/file-decorators' + +const profile = { + name: 'fileDecorator', + desciption: 'Keeps decorators of the files', + methods: ['setFileDecorators', 'clearFileDecorators'], + events: ['fileDecoratorsChanged'], + version: '0.0.1' +} + +export class FileDecorator extends Plugin { + private _fileStates: fileDecoration[] = [] + constructor() { + super(profile) + } + /** + * + * @param fileStates Array of file states + */ + async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { + const workspace = await this.call('filePanel', 'getCurrentWorkspace') + function sortByPath( a: fileDecoration, b: fileDecoration ) { + if ( a.path < b.path ){ + return -1; + } + if ( a.path > b.path ){ + return 1; + } + return 0; + } + + const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] + // clear all file states in the previous state of this owner on the files called + fileStatesPayload.forEach((state) => { + state.workspace = workspace + }) + const filteredState = this._fileStates.filter((state) => { + const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { + return payloadFileState.owner == state.owner && payloadFileState.path == state.path + }) + return index == -1 + }) + const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) + + if (!deepequal(newState, this._fileStates)) { + this._fileStates = newState + this.emit('fileDecoratorsChanged', this._fileStates) + } + } + + async clearFileDecorators() { + this._fileStates = [] + this.emit('fileDecoratorsChanged', []) + } +} \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/.babelrc b/libs/remix-ui/file-decorators/.babelrc new file mode 100644 index 0000000000..09d67939cc --- /dev/null +++ b/libs/remix-ui/file-decorators/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@nrwl/react/babel"], + "plugins": [] +} diff --git a/libs/remix-ui/file-decorators/.eslintrc b/libs/remix-ui/file-decorators/.eslintrc new file mode 100644 index 0000000000..0d43d424e3 --- /dev/null +++ b/libs/remix-ui/file-decorators/.eslintrc @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": "../../../.eslintrc.json", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error" + } +} diff --git a/libs/remix-ui/file-decorators/src/index.ts b/libs/remix-ui/file-decorators/src/index.ts new file mode 100644 index 0000000000..a116c66cd3 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/index.ts @@ -0,0 +1,3 @@ +export { fileDecoration, fileDecorationType, FileType } from './lib/types/index' +export { FileDecorationIcons } from './lib/components/file-decoration-icon' + diff --git a/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx new file mode 100644 index 0000000000..f05cea9f65 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx @@ -0,0 +1,51 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' + +import { fileDecoration, fileDecorationType, FileType } from '../types' +import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon' +import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon' +import FileDecorationTooltip from './filedecorationicons/file-decoration-tooltip' +import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon' + +export type fileDecorationProps = { + file: FileType, + fileDecorations: fileDecoration[] +} + +export const FileDecorationIcons = (props: fileDecorationProps) => { + const [states, setStates] = useState([]) + useEffect(() => { + //console.log(props.file) + //console.log(props.fileState) + setStates(props.fileDecorations.filter((fileDecoration) => fileDecoration.path === props.file.path || `${fileDecoration.workspace.name}/${fileDecoration.path}` === props.file.path)) + }, [props.fileDecorations]) + + + const getTags = function () { + if (states && states.length) { + const elements: JSX.Element[] = [] + + for (const [index, state] of states.entries()) { + switch (state.fileStateType) { + case fileDecorationType.Error: + elements.push(}/>) + break + case fileDecorationType.Warning: + elements.push(}/>) + break + case fileDecorationType.Custom: + elements.push(}/>) + break + } + } + + return elements + } + } + + return <> + {getTags()} + +} + +export default FileDecorationIcons \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx new file mode 100644 index 0000000000..c157bc66c0 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx @@ -0,0 +1,13 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { fileDecoration } from '../../types' + +const FileDecorationCustomIcon = (props: { + fileDecoration: fileDecoration +}) => { + return <> + {props.fileDecoration.fileStateIcon} + +} + +export default FileDecorationCustomIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx new file mode 100644 index 0000000000..2bb61fd97f --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx @@ -0,0 +1,14 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' + +import { fileDecoration } from '../../types' + +const FileDecorationErrorIcon = (props: { + fileDecoration: fileDecoration +}) => { + return <> + {props.fileDecoration.text} + +} + +export default FileDecorationErrorIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx new file mode 100644 index 0000000000..7c9149c14d --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { OverlayTrigger, Tooltip } from "react-bootstrap"; +import { fileDecoration } from "../../types"; + +const FileDecorationTooltip = (props: { + fileDecoration: fileDecoration, + icon: JSX.Element + index: number +}, +) => { + const getComments = function (fileDecoration: fileDecoration) { + if (fileDecoration.commment) { + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } + } + + return + <>{getComments(props.fileDecoration)} + + } + >
{props.icon}
+ +} + + +export default FileDecorationTooltip; \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx new file mode 100644 index 0000000000..1c027c9854 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx @@ -0,0 +1,11 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { fileDecoration } from '../../types' + +const FileDecorationWarningIcon = (props: { + fileDecoration: fileDecoration +}) => { + return <>{props.fileDecoration.text} +} + +export default FileDecorationWarningIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx new file mode 100644 index 0000000000..1afe91b0cf --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx @@ -0,0 +1,11 @@ +import React from "react" +import { fileDecoration } from "../types" + +export const getComments = function (fileDecoration: fileDecoration) { + if(fileDecoration.commment){ + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } +} \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts new file mode 100644 index 0000000000..35178721e0 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -0,0 +1,29 @@ +export enum fileDecorationType { + Error = 'ERROR', + Warning = 'WARNING', + Custom = 'CUSTOM', + None = 'NONE' + } + + export type fileDecoration = { + path: string, + isDirectory: boolean, + fileStateType: fileDecorationType, + fileStateLabelClass: string, + fileStateIconClass: string, + fileStateIcon: string | HTMLDivElement | JSX.Element, + bubble: boolean, + text?: string, + owner: string, + workspace?: any + tooltip?: string + commment?: string[] | string + } + + export interface FileType { + path: string, + name?: string, + isDirectory?: boolean, + type?: 'folder' | 'file' | 'gist', + child?: File[] + } \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/tsconfig.json b/libs/remix-ui/file-decorators/tsconfig.json new file mode 100644 index 0000000000..d52e31ad74 --- /dev/null +++ b/libs/remix-ui/file-decorators/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/remix-ui/file-decorators/tsconfig.lib.json b/libs/remix-ui/file-decorators/tsconfig.lib.json new file mode 100644 index 0000000000..b560bc4dec --- /dev/null +++ b/libs/remix-ui/file-decorators/tsconfig.lib.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} 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 499d0d1eb1..67c7224c96 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -1,25 +1,63 @@ + +import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' +import { Plugin } from '@remixproject/engine' + import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import './remix-ui-tabs.css' /* eslint-disable-next-line */ export interface TabsUIProps { - tabs: Array - onSelect: (index: number) => void - onClose: (index: number) => void - onZoomOut: () => void - onZoomIn: () => void - onReady: (api: any) => void - themeQuality: string + tabs: Array + plugin: Plugin, + onSelect: (index: number) => void + onClose: (index: number) => void + onZoomOut: () => void + onZoomIn: () => void + onReady: (api: any) => void + themeQuality: string } export interface TabsUIApi { - activateTab: (namee: string) => void - active: () => string + activateTab: (namee: string) => void + active: () => string +} + +interface ITabsState { + selectedIndex: number, + fileDecorations: fileDecoration[], +} + +interface ITabsAction { + type: string, + payload: any, +} + + +const initialTabsState: ITabsState = { + selectedIndex: -1, + fileDecorations: [], +} + +const tabsReducer = (state: ITabsState, action: ITabsAction) => { + switch (action.type) { + case 'SELECT_INDEX': + return { + ...state, + selectedIndex: action.payload, + } + case 'SET_FILE_DECORATIONS': + return { + ...state, + fileDecorations: action.payload as fileDecoration[], + } + default: + return state + } } export const TabsUI = (props: TabsUIProps) => { - const [selectedIndex, setSelectedIndex] = useState(-1) + const [tabsState, dispatch] = useReducer(tabsReducer, initialTabsState); const currentIndexRef = useRef(-1) const tabsRef = useRef({}) const tabsElement = useRef(null) @@ -28,10 +66,24 @@ export const TabsUI = (props: TabsUIProps) => { tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks useEffect(() => { - if (props.tabs[selectedIndex]) { - tabsRef.current[selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }) + if (props.tabs[tabsState.selectedIndex]) { + tabsRef.current[tabsState.selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }) } - }, [selectedIndex]) + }, [tabsState.selectedIndex]) + + + + const getFileDecorationClasses = (tab: any) => { + const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => { + if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true + }) + return fileDecoration && fileDecoration.fileStateLabelClass + } + + const getFileDecorationIcons = (tab: any) => { + return + } + const renderTab = (tab, index) => { const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass @@ -40,8 +92,9 @@ export const TabsUI = (props: TabsUIProps) => { return (
{ tabsRef.current[index] = el }} className={classNameTab} data-id={index === currentIndexRef.current ? 'tab-active' : ''} title={tab.tooltip}> - {tab.icon ? () : ()} - {tab.title} + {tab.icon ? () : ()} + {tab.title} + {getFileDecorationIcons(tab)} { props.onClose(index); event.stopPropagation() }}> @@ -56,7 +109,11 @@ export const TabsUI = (props: TabsUIProps) => { const activateTab = (name: string) => { const index = tabs.current.findIndex((tab) => tab.name === name) currentIndexRef.current = index - setSelectedIndex(index) + dispatch({ type: 'SELECT_INDEX', payload: index }) + } + + const setFileDecorations = (fileStates: fileDecoration[]) => { + dispatch({ type: 'SET_FILE_DECORATIONS', payload: fileStates }) } const transformScroll = (event) => { @@ -71,21 +128,23 @@ export const TabsUI = (props: TabsUIProps) => { useEffect(() => { props.onReady({ activateTab, - active + active, + setFileDecorations }) + return () => { tabsElement.current.removeEventListener('wheel', transformScroll) } }, []) return (
-
+
props.onZoomOut()}> props.onZoomIn()}>
{ if (tabsElement.current) return tabsElement.current = domEl @@ -94,7 +153,7 @@ export const TabsUI = (props: TabsUIProps) => { onSelect={(index) => { props.onSelect(index) currentIndexRef.current = index - setSelectedIndex(index) + dispatch({ type: 'SELECT_INDEX', payload: index }) }} > diff --git a/libs/remix-ui/workspace/src/index.ts b/libs/remix-ui/workspace/src/index.ts index 166467d115..c9e5b5355b 100644 --- a/libs/remix-ui/workspace/src/index.ts +++ b/libs/remix-ui/workspace/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/providers/FileSystemProvider' export * from './lib/contexts' +export { FileType } from './lib/types/index' \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index a8bde65fd5..62092df30f 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -1,7 +1,8 @@ +import { fileDecoration } from '@remix-ui/file-decorators' import { extractParentFromKey } from '@remix-ui/helper' import React from 'react' import { action, WorkspaceTemplate } from '../types' -import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload' +import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode, setFileDecorationSuccess } from './payload' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' const LOCALHOST = ' - connect to localhost - ' @@ -38,6 +39,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => { uploadFile(target, dir, cb) }) + plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => { + setFileDecorators(items) + }) + plugin.on('remixd', 'rootFolderChanged', async (path: string) => { rootFolderChanged(path) }) @@ -202,3 +207,8 @@ const fileRenamed = async (oldPath: string) => { const rootFolderChanged = async (path) => { await dispatch(rootFolderChangedSuccess(path)) } + +const setFileDecorators = async (items: fileDecoration[], cb?: (err: Error, result?: string | number | boolean | Record) => void) => { + await dispatch(setFileDecorationSuccess(items)) + cb && cb(null, true) +} diff --git a/libs/remix-ui/workspace/src/lib/actions/payload.ts b/libs/remix-ui/workspace/src/lib/actions/payload.ts index b663f60cfe..8c3fb8fc18 100644 --- a/libs/remix-ui/workspace/src/lib/actions/payload.ts +++ b/libs/remix-ui/workspace/src/lib/actions/payload.ts @@ -1,3 +1,4 @@ +import { fileDecoration } from '@remix-ui/file-decorators' import { action } from '../types' export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => { @@ -240,6 +241,12 @@ export const fsInitializationCompleted = () => { } } +export const setFileDecorationSuccess = (items: fileDecoration[]) => { + return { + type: 'SET_FILE_DECORATION_SUCCESS', + payload: items + } +} export const cloneRepositoryRequest = () => { return { type: 'CLONE_REPOSITORY_REQUEST' diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index a0351ea3a1..3ccefdb2db 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -12,7 +12,7 @@ import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath } import { FileRender } from './file-render' export const FileExplorer = (props: FileExplorerProps) => { - const { name, contextMenuItems, removedContextMenuItems, files } = props + const { name, contextMenuItems, removedContextMenuItems, files, fileState } = props const [state, setState] = useState({ ctrlKey: false, newFileName: '', @@ -432,6 +432,7 @@ export const FileExplorer = (props: FileExplorerProps) => { { files[props.name] && Object.keys(files[props.name]).map((key, index) => void } export const FileLabel = (props: FileLabelProps) => { - const { file, focusEdit, editModeOff } = props + const { file, focusEdit, editModeOff, fileDecorations } = props const [isEditable, setIsEditable] = useState(false) + const [fileStateClasses, setFileStateClasses] = useState('') const labelRef = useRef(null) useEffect(() => { @@ -24,6 +27,18 @@ export const FileLabel = (props: FileLabelProps) => { } }, [file.path, focusEdit]) + useEffect(() => { + const state = props.fileDecorations.find((state: fileDecoration) => { + if(state.path === props.file.path) return true + if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true + }) + if (state && state.fileStateLabelClass) { + setFileStateClasses(state.fileStateLabelClass) + } else{ + setFileStateClasses('') + } + }, [fileDecorations]) + useEffect(() => { if (labelRef.current) { setTimeout(() => { @@ -57,10 +72,10 @@ export const FileLabel = (props: FileLabelProps) => { > - { file.name } + {file.name}
) diff --git a/libs/remix-ui/workspace/src/lib/components/file-render.tsx b/libs/remix-ui/workspace/src/lib/components/file-render.tsx index ac0054e96b..164689899b 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-render.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-render.tsx @@ -6,6 +6,11 @@ import { TreeView, TreeViewItem } from '@remix-ui/tree-view' import { getPathIcon } from '@remix-ui/helper' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { FileLabel } from './file-label' +import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' + + + + export interface RenderFileProps { file: FileType, @@ -19,6 +24,7 @@ export interface RenderFileProps { handleClickFolder: (path: string, type: string) => void, handleClickFile: (path: string, type: string) => void, handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void + fileDecorations: fileDecoration[] } export const FileRender = (props: RenderFileProps) => { @@ -76,7 +82,7 @@ export const FileRender = (props: RenderFileProps) => { iconX='pr-3 fa fa-folder' iconY='pr-3 fa fa-folder-open' key={`${file.path + props.index}`} - label={} + label={} onClick={handleFolderClick} onContextMenu={handleContextMenu} labelClass={labelClass} @@ -89,6 +95,7 @@ export const FileRender = (props: RenderFileProps) => { file.child ? { Object.keys(file.child).map((key, index) => { } + label={ + <> +
+ + +
+ + } onClick={handleFileClick} onContextMenu={handleContextMenu} icon={icon} diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 39f56a202f..0a774e6b03 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -1,9 +1,10 @@ import { extractNameFromKey } from '@remix-ui/helper' import { action, FileType } from '../types' import * as _ from 'lodash' +import { fileDecoration } from '@remix-ui/file-decorators' interface Action { - type: string - payload: any + type: string + payload: any } export interface BrowserState { browser: { @@ -25,7 +26,8 @@ export interface BrowserState { registeredMenuItems: action[], removedMenuItems: action[], error: string - } + }, + fileState: fileDecoration[] }, localhost: { sharedFolder: string, @@ -40,7 +42,8 @@ export interface BrowserState { registeredMenuItems: action[], removedMenuItems: action[], error: string - } + }, + fileState: [] }, mode: 'browser' | 'localhost', notification: { @@ -75,7 +78,8 @@ export const browserInitialState: BrowserState = { registeredMenuItems: [], removedMenuItems: [], error: null - } + }, + fileState: [] }, localhost: { sharedFolder: '', @@ -90,14 +94,15 @@ export const browserInitialState: BrowserState = { registeredMenuItems: [], removedMenuItems: [], error: null - } + }, + fileState: [] }, mode: 'browser', notification: { title: '', message: '', - actionOk: () => {}, - actionCancel: () => {}, + actionOk: () => { }, + actionCancel: () => { }, labelOk: '', labelCancel: '' }, @@ -650,6 +655,16 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } } + case 'SET_FILE_DECORATION_SUCCESS': { + return { + ...state, + browser: { + ...state.browser, + fileState: action.payload + } + } + } + default: throw new Error() } diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index e25811df68..353e4ccf91 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -307,6 +307,7 @@ export function Workspace () { contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} files={global.fs.browser.files} + fileState={global.fs.browser.fileState} expandPath={global.fs.browser.expandPath} focusEdit={global.fs.focusEdit} focusElement={global.fs.focusElement} @@ -343,6 +344,7 @@ export function Workspace () { contextMenuItems={global.fs.localhost.contextMenu.registeredMenuItems} removedContextMenuItems={global.fs.localhost.contextMenu.removedMenuItems} files={global.fs.localhost.files} + fileState={[]} expandPath={global.fs.localhost.expandPath} focusEdit={global.fs.focusEdit} focusElement={global.fs.focusElement} diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index fb169db5e8..93b6d9f8c9 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -1,5 +1,6 @@ import React from 'react' import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel' +import { fileDecoration } from '@remix-ui/file-decorators'; export type action = { name: string, type?: Array<'folder' | 'gist' | 'file'>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean } export interface JSONStandardInput { @@ -73,6 +74,7 @@ export interface FileExplorerProps { contextMenuItems: MenuItems, removedContextMenuItems: MenuItems, files: { [x: string]: Record }, + fileState: fileDecoration[], expandPath: string[], focusEdit: string, focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[], From 4abdb51b6c9123f918da95590d7b2db2335f50e9 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:15 +0200 Subject: [PATCH 004/194] add test --- .../src/tests/editorAutoComplete.test.ts | 518 ++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts new file mode 100644 index 0000000000..8dc7bd4440 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts @@ -0,0 +1,518 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' +import examples from '../examples/editor-test-contracts' + +const autoCompleteLineElement = (name: string) => { + return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` +} + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + 'Should add test and base files #group2': function (browser: NightwatchBrowser) { + browser.addFile(examples.testContract.name, examples.testContract) + .addFile(examples.baseContract.name, examples.baseContract) + .addFile(examples.import1Contract.name, examples.import1Contract) + .addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) + .addFile(examples.secondimport.name, examples.secondimport) + .addFile(examples.importbase.name, examples.importbase) + .openFile(examples.testContract.name) + }, + 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) { + browser.scrollToLine(36) + const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" + browser.waitForElementVisible('#editorView') + .useXpath() + .click(path).pause(1000) + }, + 'Should complete variable declaration types in a function definition #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uint25') + }) + .waitForElementPresent(autoCompleteLineElement('uint256')) + .click(autoCompleteLineElement('uint256')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' abc') + .sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow + .sendKeys(', testb') + }) + .waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"TestBookDefinition"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' memo') + }) + .waitForElementPresent(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' btextbook') + .sendKeys(this.Keys.ENTER) + .sendKeys(', BaseB') + }) + .waitForElementPresent(autoCompleteLineElement('"BaseBook"')) + .click(autoCompleteLineElement('"BaseBook"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' stor') + }) + .waitForElementPresent(autoCompleteLineElement('storage')) + .click(autoCompleteLineElement('storage')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' localbbook') + }).pause(3000) + }, + 'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) { + + const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" + browser + .useXpath() + .click(path).pause(1000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + // right arrow key + sendKeys(this.Keys.ARROW_RIGHT). + sendKeys(this.Keys.ARROW_RIGHT) + }) + }, + 'Should autcomplete address types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('addre') + }) + .waitForElementPresent(autoCompleteLineElement('address')) + .click(autoCompleteLineElement('address')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' someaddress;') + .sendKeys(this.Keys.ENTER) + }).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('someaddress.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .waitForElementVisible(autoCompleteLineElement('code')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autcomplete array types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uin') + }) + .waitForElementPresent(autoCompleteLineElement('uint')) + .click(autoCompleteLineElement('uint')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('[] mem') + }) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' somearray;') + } + ).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + .sendKeys('somearray.') + }) + .waitForElementVisible(autoCompleteLineElement('push')) + .waitForElementVisible(autoCompleteLineElement('pop')) + .waitForElementVisible(autoCompleteLineElement('length')) + .click(autoCompleteLineElement('length')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should see and autocomplete second import because it was imported by the first import #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('secondi') + }) + .waitForElementPresent(autoCompleteLineElement('secondimport')) + .click(autoCompleteLineElement('secondimport')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' sec;') + .sendKeys(this.Keys.ENTER) + }).pause(3000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('sec.') + }) + .waitForElementVisible(autoCompleteLineElement('secondimportstring')) + .click(autoCompleteLineElement('secondimportstring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should see and autocomplete imported local class #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('import') + }) + .waitForElementPresent(autoCompleteLineElement('importedcontract')) + .click(autoCompleteLineElement('importedcontract')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('externalimport')) + .waitForElementVisible(autoCompleteLineElement('importbasestring')) + .waitForElementVisible(autoCompleteLineElement('importedbook')) + .waitForElementVisible(autoCompleteLineElement('importpublicstring')) + .waitForElementVisible(autoCompleteLineElement('publicimport')) + // no private + .waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) + .waitForElementNotPresent(autoCompleteLineElement('privateimport')) + // no internal + .waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) + .waitForElementNotPresent(autoCompleteLineElement('internalimport')) + .click(autoCompleteLineElement('importbasestring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should autocomplete derived and local event when not using this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit base') + }) + .waitForElementVisible(autoCompleteLineElement('BaseEvent')) + .click(autoCompleteLineElement('BaseEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('msg.sender') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit MyEv') + }) + .waitForElementVisible(autoCompleteLineElement('MyEvent')) + .click(autoCompleteLineElement('MyEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should type and get msg options #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('msg.') + }) + .waitForElementVisible(autoCompleteLineElement('sender')) + .waitForElementVisible(autoCompleteLineElement('data')) + .waitForElementVisible(autoCompleteLineElement('value')) + .waitForElementVisible(autoCompleteLineElement('gas')) + .waitForElementVisible(autoCompleteLineElement('sig')) + .click(autoCompleteLineElement('sender')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('code')) + .waitForElementVisible(autoCompleteLineElement('codehash')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get book #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('bo') + }) + .waitForElementVisible(autoCompleteLineElement('book')) + .click(autoCompleteLineElement('book')) + }, + 'Should autcomplete derived struct #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get basebook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('base') + }) + .waitForElementVisible(autoCompleteLineElement('basebook')) + .click(autoCompleteLineElement('basebook')) + }, + 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped localbbook #group2': function (browser: NightwatchBrowser) { + browser.pause(4000). + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('localb') + }) + .waitForElementVisible(autoCompleteLineElement('localbbook')) + .click(autoCompleteLineElement('localbbook')) + }, + 'Should autcomplete derived struct from block localbbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped btextbook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('btext') + }) + .waitForElementVisible(autoCompleteLineElement('btextbook')) + .click(autoCompleteLineElement('btextbook')) + }, + 'Should autcomplete derived struct from block btextbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should find private and internal local functions #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('my') + }) + .waitForElementVisible(autoCompleteLineElement('myprivatefunction')) + .waitForElementVisible(autoCompleteLineElement('myinternalfunction')) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('myinternalfunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('intern') + }) + .waitForElementVisible(autoCompleteLineElement('internalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('internalstring')) + .waitForElementVisible(autoCompleteLineElement('internalbasestring')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('internal keyword')) + .click(autoCompleteLineElement('internalbasefunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should not find external functions without this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('extern') + }) + .waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) + .waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('external keyword')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + }) + }, + 'Should find external functions using this. #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('this.') + }) + .waitForElementVisible(autoCompleteLineElement('externalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('myexternalfunction')) + }, + 'Should find public functions and vars using this. but not private & other types of nodes #group2': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) + .waitForElementVisible(autoCompleteLineElement('"publicstring"')) + .waitForElementVisible(autoCompleteLineElement('"basebook"')) + .waitForElementVisible(autoCompleteLineElement('"mybook"')) + .waitForElementVisible(autoCompleteLineElement('"testing"')) + // but no private functions or vars or other types of nodes + .waitForElementNotPresent(autoCompleteLineElement('"private"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) + .waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"publicbasefunction"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autocomplete local and derived ENUMS #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('BaseEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + .sendKeys('MyEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + } +} + From 9a6160b6a59ec152e1ed74ff8850448de1b04de3 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:23 +0200 Subject: [PATCH 005/194] add tests --- .../src/tests/file_decorator.test.ts | 97 +++++++++++++++++++ apps/remix-ide/src/app.js | 7 +- .../src/app/plugins/file-decorator.ts | 42 +++++--- .../file-decoration-custom-icon.tsx | 2 +- .../file-decoration-error-icon.tsx | 2 +- .../file-decoration-warning-icon.tsx | 2 +- nx.json | 3 + tsconfig.base.json | 1 + workspace.json | 15 +++ 9 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide-e2e/src/tests/file_decorator.test.ts diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts new file mode 100644 index 0000000000..40e78991b3 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -0,0 +1,97 @@ + +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + }, + + 'Test decorators with script': function (browser: NightwatchBrowser) { + browser + .openFile('contracts') + .openFile('contracts/2_Owner.sol') + .openFile('contracts/1_Storage.sol') + .openFile('contracts/3_Ballot.sol') + .addFile('scripts/decorators.ts', { content: testScript }) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .useXpath() + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') + .useCss() + .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) + .useXpath() + .moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0,0) + .waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]') + .waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner') + } + +} +const testScript = ` +(async () => { + remix.call('fileDecorator' as any, 'clearFileDecorators') + let decorator: any = { + path: 'contracts/2_Owner.sol', + isDirectory: false, + fileStateType: 'ERROR', + fileStateLabelClass: 'text-danger', + fileStateIconClass: '', + fileStateIcon: '', + text: '2', + owner: 'code-parser', + bubble: true, + commment: 'error on owner', + } + let decorator2: any = { + path: 'contracts/2_Owner.sol', + isDirectory: false, + fileStateType: 'CUSTOM', + fileStateLabelClass: 'text-success', + fileStateIconClass: 'text-success', + fileStateIcon: 'U', + text: '', + owner: 'code-parser', + bubble: true, + commment: 'modified', + } + remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) + + decorator = { + path: 'contracts/1_Storage.sol', + isDirectory: false, + fileStateType: 'WARNING', + fileStateLabelClass: 'text-warning', + fileStateIconClass: '', + fileStateIcon: '', + text: '2', + owner: 'code-parser', + bubble: true, + commment: 'warning on storage', + } + remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + + decorator = { + path: 'contracts/3_Ballot.sol', + isDirectory: false, + fileStateType: 'CUSTOM', + fileStateLabelClass: '', + fileStateIconClass: '', + fileStateIcon: 'customtext', + text: 'w', + owner: 'dgit', + bubble: true, + commment: '', + } + remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + + remix.call('fileDecorator' as any, 'clearFileDecorators', 'dgit') + + })()` \ No newline at end of file diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index d9c93fd4f6..4270ecce92 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -31,6 +31,7 @@ import { FoundryProvider } from './app/tabs/foundry-provider' import { ExternalHttpProvider } from './app/tabs/external-http-provider' import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider' import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider' +import { FileDecorator } from './app/plugins/file-decorator' const isElectron = require('is-electron') @@ -156,6 +157,9 @@ class AppComponent { // ----------------- Storage plugin --------------------------------- const storagePlugin = new StoragePlugin() + // ------- FILE DECORATOR PLUGIN ------------------ + const fileDecorator = new FileDecorator() + //----- search const search = new SearchPlugin() @@ -239,6 +243,7 @@ class AppComponent { fetchAndCompile, dGitProvider, storagePlugin, + fileDecorator, hardhatProvider, ganacheProvider, foundryProvider, @@ -363,7 +368,7 @@ class AppComponent { await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await this.appManager.activatePlugin(['home']) await this.appManager.activatePlugin(['settings', 'config']) - await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) + await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder']) diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 60a14ef47f..5332db1508 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -18,22 +18,14 @@ export class FileDecorator extends Plugin { constructor() { super(profile) } + + /** * * @param fileStates Array of file states */ async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { const workspace = await this.call('filePanel', 'getCurrentWorkspace') - function sortByPath( a: fileDecoration, b: fileDecoration ) { - if ( a.path < b.path ){ - return -1; - } - if ( a.path > b.path ){ - return 1; - } - return 0; - } - const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] // clear all file states in the previous state of this owner on the files called fileStatesPayload.forEach((state) => { @@ -46,15 +38,37 @@ export class FileDecorator extends Plugin { return index == -1 }) const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) - + if (!deepequal(newState, this._fileStates)) { this._fileStates = newState this.emit('fileDecoratorsChanged', this._fileStates) } } - async clearFileDecorators() { - this._fileStates = [] - this.emit('fileDecoratorsChanged', []) + async clearFileDecorators(owner? : string) { + if(!owner) { + this._fileStates = [] + this.emit('fileDecoratorsChanged', []) + } else { + const filteredState = this._fileStates.filter((state) => { + return state.owner != owner + }) + const newState = [...filteredState].sort(sortByPath) + + if (!deepequal(newState, this._fileStates)) { + this._fileStates = newState + this.emit('fileDecoratorsChanged', this._fileStates) + } + } + } +} + +const sortByPath = (a: fileDecoration, b: fileDecoration) => { + if (a.path < b.path) { + return -1; + } + if (a.path > b.path) { + return 1; } + return 0; } \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx index c157bc66c0..2cfec2ac97 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx @@ -5,7 +5,7 @@ import { fileDecoration } from '../../types' const FileDecorationCustomIcon = (props: { fileDecoration: fileDecoration }) => { - return <> + return <> {props.fileDecoration.fileStateIcon} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx index 2bb61fd97f..5a9c48b555 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx @@ -7,7 +7,7 @@ const FileDecorationErrorIcon = (props: { fileDecoration: fileDecoration }) => { return <> - {props.fileDecoration.text} + {props.fileDecoration.text} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx index 1c027c9854..9bfd368506 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx @@ -5,7 +5,7 @@ import { fileDecoration } from '../../types' const FileDecorationWarningIcon = (props: { fileDecoration: fileDecoration }) => { - return <>{props.fileDecoration.text} + return <>{props.fileDecoration.text} } export default FileDecorationWarningIcon \ No newline at end of file diff --git a/nx.json b/nx.json index 13cd26c2b2..734bd55a4c 100644 --- a/nx.json +++ b/nx.json @@ -170,6 +170,9 @@ "remix-ui-permission-handler": { "tags": [] }, + "remix-ui-file-decorators": { + "tags": [] + }, "remix-ui-tooltip-popup": { "tags": [] } diff --git a/tsconfig.base.json b/tsconfig.base.json index e97aaf9aff..9174b23427 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -88,6 +88,7 @@ "@remix-ui/permission-handler": [ "libs/remix-ui/permission-handler/src/index.ts" ], + "@remix-ui/file-decorators": ["libs/remix-ui/file-decorators/src/index.ts"], "@remix-ui/tooltip-popup": ["libs/remix-ui/tooltip-popup/src/index.ts"] } }, diff --git a/workspace.json b/workspace.json index b1ec5f63d5..fd0935ee57 100644 --- a/workspace.json +++ b/workspace.json @@ -1270,6 +1270,21 @@ } } } + }, + "remix-ui-file-decorators": { + "root": "libs/remix-ui/file-decorators", + "sourceRoot": "libs/remix-ui/file-decorators/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["libs/remix-ui/file-decorators/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "!libs/remix-ui/file-decorators/**/*"] + } + } + } } }, "cli": { From 0cfe5c10f3d8fc1f5201eba914484da586d48275 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:40:27 +0200 Subject: [PATCH 006/194] rm test --- .../src/tests/editorAutoComplete.test.ts | 518 ------------------ 1 file changed, 518 deletions(-) delete mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts deleted file mode 100644 index 8dc7bd4440..0000000000 --- a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts +++ /dev/null @@ -1,518 +0,0 @@ -'use strict' -import { NightwatchBrowser } from 'nightwatch' -import init from '../helpers/init' -import examples from '../examples/editor-test-contracts' - -const autoCompleteLineElement = (name: string) => { - return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` -} - -module.exports = { - '@disabled': true, - before: function (browser: NightwatchBrowser, done: VoidFunction) { - init(browser, done, 'http://127.0.0.1:8080', false) - }, - 'Should add test and base files #group2': function (browser: NightwatchBrowser) { - browser.addFile(examples.testContract.name, examples.testContract) - .addFile(examples.baseContract.name, examples.baseContract) - .addFile(examples.import1Contract.name, examples.import1Contract) - .addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) - .addFile(examples.secondimport.name, examples.secondimport) - .addFile(examples.importbase.name, examples.importbase) - .openFile(examples.testContract.name) - }, - 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) { - browser.scrollToLine(36) - const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" - browser.waitForElementVisible('#editorView') - .useXpath() - .click(path).pause(1000) - }, - 'Should complete variable declaration types in a function definition #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('uint25') - }) - .waitForElementPresent(autoCompleteLineElement('uint256')) - .click(autoCompleteLineElement('uint256')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' abc') - .sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow - .sendKeys(', testb') - }) - .waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) - .click(autoCompleteLineElement('"TestBookDefinition"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' memo') - }) - .waitForElementPresent(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('memory')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' btextbook') - .sendKeys(this.Keys.ENTER) - .sendKeys(', BaseB') - }) - .waitForElementPresent(autoCompleteLineElement('"BaseBook"')) - .click(autoCompleteLineElement('"BaseBook"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' stor') - }) - .waitForElementPresent(autoCompleteLineElement('storage')) - .click(autoCompleteLineElement('storage')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' localbbook') - }).pause(3000) - }, - 'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) { - - const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" - browser - .useXpath() - .click(path).pause(1000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - // right arrow key - sendKeys(this.Keys.ARROW_RIGHT). - sendKeys(this.Keys.ARROW_RIGHT) - }) - }, - 'Should autcomplete address types': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('addre') - }) - .waitForElementPresent(autoCompleteLineElement('address')) - .click(autoCompleteLineElement('address')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' someaddress;') - .sendKeys(this.Keys.ENTER) - }).pause(2000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('someaddress.') - }) - .waitForElementVisible(autoCompleteLineElement('balance')) - .waitForElementVisible(autoCompleteLineElement('send')) - .waitForElementVisible(autoCompleteLineElement('transfer')) - .waitForElementVisible(autoCompleteLineElement('code')) - .click(autoCompleteLineElement('balance')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should autcomplete array types': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('uin') - }) - .waitForElementPresent(autoCompleteLineElement('uint')) - .click(autoCompleteLineElement('uint')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('[] mem') - }) - .waitForElementVisible(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('memory')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' somearray;') - } - ).pause(2000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - .sendKeys('somearray.') - }) - .waitForElementVisible(autoCompleteLineElement('push')) - .waitForElementVisible(autoCompleteLineElement('pop')) - .waitForElementVisible(autoCompleteLineElement('length')) - .click(autoCompleteLineElement('length')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should see and autocomplete second import because it was imported by the first import #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('secondi') - }) - .waitForElementPresent(autoCompleteLineElement('secondimport')) - .click(autoCompleteLineElement('secondimport')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' sec;') - .sendKeys(this.Keys.ENTER) - }).pause(3000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('sec.') - }) - .waitForElementVisible(autoCompleteLineElement('secondimportstring')) - .click(autoCompleteLineElement('secondimportstring')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - - }, - 'Should see and autocomplete imported local class #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('import') - }) - .waitForElementPresent(autoCompleteLineElement('importedcontract')) - .click(autoCompleteLineElement('importedcontract')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('externalimport')) - .waitForElementVisible(autoCompleteLineElement('importbasestring')) - .waitForElementVisible(autoCompleteLineElement('importedbook')) - .waitForElementVisible(autoCompleteLineElement('importpublicstring')) - .waitForElementVisible(autoCompleteLineElement('publicimport')) - // no private - .waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) - .waitForElementNotPresent(autoCompleteLineElement('privateimport')) - // no internal - .waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) - .waitForElementNotPresent(autoCompleteLineElement('internalimport')) - .click(autoCompleteLineElement('importbasestring')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - - }, - 'Should autocomplete derived and local event when not using this. #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('emit base') - }) - .waitForElementVisible(autoCompleteLineElement('BaseEvent')) - .click(autoCompleteLineElement('BaseEvent')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys('msg.sender') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium - .sendKeys('3232') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.ENTER) - }) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('emit MyEv') - }) - .waitForElementVisible(autoCompleteLineElement('MyEvent')) - .click(autoCompleteLineElement('MyEvent')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys('3232') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.ENTER) - }) - }, - - 'Should type and get msg options #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('msg.') - }) - .waitForElementVisible(autoCompleteLineElement('sender')) - .waitForElementVisible(autoCompleteLineElement('data')) - .waitForElementVisible(autoCompleteLineElement('value')) - .waitForElementVisible(autoCompleteLineElement('gas')) - .waitForElementVisible(autoCompleteLineElement('sig')) - .click(autoCompleteLineElement('sender')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('balance')) - .waitForElementVisible(autoCompleteLineElement('code')) - .waitForElementVisible(autoCompleteLineElement('codehash')) - .waitForElementVisible(autoCompleteLineElement('send')) - .waitForElementVisible(autoCompleteLineElement('transfer')) - .click(autoCompleteLineElement('balance')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER) - }) - }, - 'Should bo and get book #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('bo') - }) - .waitForElementVisible(autoCompleteLineElement('book')) - .click(autoCompleteLineElement('book')) - }, - 'Should autcomplete derived struct #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should bo and get basebook #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('base') - }) - .waitForElementVisible(autoCompleteLineElement('basebook')) - .click(autoCompleteLineElement('basebook')) - }, - 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should block scoped localbbook #group2': function (browser: NightwatchBrowser) { - browser.pause(4000). - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('localb') - }) - .waitForElementVisible(autoCompleteLineElement('localbbook')) - .click(autoCompleteLineElement('localbbook')) - }, - 'Should autcomplete derived struct from block localbbook #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should block scoped btextbook #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('btext') - }) - .waitForElementVisible(autoCompleteLineElement('btextbook')) - .click(autoCompleteLineElement('btextbook')) - }, - 'Should autcomplete derived struct from block btextbook #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should find private and internal local functions #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('my') - }) - .waitForElementVisible(autoCompleteLineElement('myprivatefunction')) - .waitForElementVisible(autoCompleteLineElement('myinternalfunction')) - .waitForElementVisible(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('myinternalfunction')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER) - }) - }, - 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('intern') - }) - .waitForElementVisible(autoCompleteLineElement('internalbasefunction')) - .waitForElementVisible(autoCompleteLineElement('internalstring')) - .waitForElementVisible(autoCompleteLineElement('internalbasestring')) - // keyword internal - .waitForElementVisible(autoCompleteLineElement('internal keyword')) - .click(autoCompleteLineElement('internalbasefunction')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - - 'Should not find external functions without this. #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('extern') - }) - .waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) - .waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) - // keyword internal - .waitForElementVisible(autoCompleteLineElement('external keyword')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - }) - }, - 'Should find external functions using this. #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('this.') - }) - .waitForElementVisible(autoCompleteLineElement('externalbasefunction')) - .waitForElementVisible(autoCompleteLineElement('myexternalfunction')) - }, - 'Should find public functions and vars using this. but not private & other types of nodes #group2': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) - .waitForElementVisible(autoCompleteLineElement('"publicstring"')) - .waitForElementVisible(autoCompleteLineElement('"basebook"')) - .waitForElementVisible(autoCompleteLineElement('"mybook"')) - .waitForElementVisible(autoCompleteLineElement('"testing"')) - // but no private functions or vars or other types of nodes - .waitForElementNotPresent(autoCompleteLineElement('"private"')) - .waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) - .waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) - .waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) - .click(autoCompleteLineElement('"publicbasefunction"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should autocomplete local and derived ENUMS #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('BaseEnum.') - }) - .waitForElementVisible(autoCompleteLineElement('SMALL')) - .waitForElementVisible(autoCompleteLineElement('MEDIUM')) - .waitForElementVisible(autoCompleteLineElement('LARGE')) - .click(autoCompleteLineElement('SMALL')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - .sendKeys('MyEnum.') - }) - .waitForElementVisible(autoCompleteLineElement('SMALL')) - .waitForElementVisible(autoCompleteLineElement('MEDIUM')) - .waitForElementVisible(autoCompleteLineElement('LARGE')) - .click(autoCompleteLineElement('SMALL')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - } -} - From 4007753dbe87517eeeb1919242425534403ef180 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:49:43 +0200 Subject: [PATCH 007/194] typo fix --- apps/remix-ide-e2e/src/tests/file_decorator.test.ts | 8 ++++---- .../filedecorationicons/file-decoration-tooltip.tsx | 6 +++--- libs/remix-ui/file-decorators/src/lib/helper/index.tsx | 6 +++--- libs/remix-ui/file-decorators/src/lib/types/index.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts index 40e78991b3..2aa226397c 100644 --- a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -48,7 +48,7 @@ const testScript = ` text: '2', owner: 'code-parser', bubble: true, - commment: 'error on owner', + comment: 'error on owner', } let decorator2: any = { path: 'contracts/2_Owner.sol', @@ -60,7 +60,7 @@ const testScript = ` text: '', owner: 'code-parser', bubble: true, - commment: 'modified', + comment: 'modified', } remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) @@ -74,7 +74,7 @@ const testScript = ` text: '2', owner: 'code-parser', bubble: true, - commment: 'warning on storage', + comment: 'warning on storage', } remix.call('fileDecorator' as any, 'setFileDecorators', decorator) @@ -88,7 +88,7 @@ const testScript = ` text: 'w', owner: 'dgit', bubble: true, - commment: '', + comment: '', } remix.call('fileDecorator' as any, 'setFileDecorators', decorator) diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx index 7c9149c14d..1d87846192 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx @@ -9,9 +9,9 @@ const FileDecorationTooltip = (props: { }, ) => { const getComments = function (fileDecoration: fileDecoration) { - if (fileDecoration.commment) { - const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] - return commments.map((comment, index) => { + if (fileDecoration.comment) { + const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] + return comments.map((comment, index) => { return
{comment}

}) } diff --git a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx index 1afe91b0cf..10bad7d3fc 100644 --- a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx +++ b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx @@ -2,9 +2,9 @@ import React from "react" import { fileDecoration } from "../types" export const getComments = function (fileDecoration: fileDecoration) { - if(fileDecoration.commment){ - const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] - return commments.map((comment, index) => { + if(fileDecoration.comment){ + const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] + return comments.map((comment, index) => { return
{comment}

}) } diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts index 35178721e0..ef6620434a 100644 --- a/libs/remix-ui/file-decorators/src/lib/types/index.ts +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -17,7 +17,7 @@ export enum fileDecorationType { owner: string, workspace?: any tooltip?: string - commment?: string[] | string + comment?: string[] | string } export interface FileType { From cd1db3f59cf137a834c4c39421ac8ff5367948c1 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 21 Jul 2022 14:02:17 +0530 Subject: [PATCH 008/194] remixd readme updated --- libs/remixd/README.md | 64 +++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/libs/remixd/README.md b/libs/remixd/README.md index 12726f5560..c0ed97a6a0 100644 --- a/libs/remixd/README.md +++ b/libs/remixd/README.md @@ -1,59 +1,83 @@ # Remixd -`remixd` is a tool that intend to be used with [Remix IDE](https://github.com/ethereum/remix-project) (aka. Browser-Solidity). It allows a websocket connection between -`Remix IDE` (web application) and the local computer. +`remixd` is an NPM module that intends to be used with [Remix IDE](https://remix.ethereum.org/) web and desktop applications. It establishes a two-way websocket connection between the local computer and Remix IDE for a particular project directory. -Practically Remix IDE makes available a folder shared by `remixd`. +`remixd` can be used to setup a development environment with other popular frameworks like Hardhat, Truffle, Slither etc. -More details are explained in this [tutorial](https://remix-ide.readthedocs.io/en/latest/remixd.html). +More details are explained in the [documentation](https://remix-ide.readthedocs.io/en/latest/remixd.html). -Alternatively `remixd` can be used to setup a development environment that can be used with other popular frameworks like Embark, Truffle, Ganache, etc.. +## Installation -`remixd` needs `npm` and `node` +`npm install -g @remix-project/remixd` -## INSTALLATION +NOTE: When the remixd NPM module is installed, it also installs [Slither](https://github.com/crytic/slither), [solc-select](https://github.com/crytic/solc-select#quickstart) and sets [solc](https://docs.soliditylang.org/en/latest/installing-solidity.html) to latest version i.e. 0.8.15 currently. -`yarn global add @remix-project/remixd` +ALSO NOTE: Python3.6+ (pip3) needs to already be installed on the System. In case of any discrepany, Slither can also installed along with other dependencies using command: +``` +> remixd -i slither +``` + +_(This packaging of Slither with the remixd module is supported since Remixd v0.6.3)_ -### Warning for old users +### Warning for quite old users There is a new version of remixd with a new npm address: https://npmjs.com/package/@remix-project/remixd If you were using the old one you need to: 1. uninstall the old one: `npm uninstall -g remixd` - 2. install the new: `yarn global add @remix-project/remixd` + 2. install the new: `npm install -g @remix-project/remixd` +## remixd command -## HELP SECTION +The remixd command without options shares present working directory and the shared Remix domain will be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org +The remixd command is: +``` +> remixd ``` -Usage: remixd -s -Provide a two-way connection between the local computer and Remix IDE +If you are using Remix from localhost or you are not running the command from your working directory, you’ll need to use the command with flags. + +``` +> remixd -h +Usage: remixd [options] + +Establish a two-way websocket connection between the local computer and Remix IDE for a folder Options: -v, --version output the version number - -u, --remix-ide URL of remix instance allowed to connect to this web sockect connection - -s, --shared-folder Folder to share with Remix IDE + -u, --remix-ide URL of remix instance allowed to connect + -s, --shared-folder Folder to share with Remix IDE (Default: CWD) + -i, --install Module name to install locally (Supported: ["slither"]) -r, --read-only Treat shared folder as read-only (experimental) -h, --help output usage information Example: - remixd -s ./ -u http://localhost:8080 + remixd -s ./shared_project -u http://localhost:8080 ``` -## SHARE A FOLDER +## Share a project directory -`remixd -s --remix-ide https://remix.ethereum.org` +`remixd -s ./shared_project -u https://remix.ethereum.org` The current user should have `read/write` access to the folder (at least `read` access). It is important to notice that changes made to the current file in `Remix IDE` are automatically saved to the local computer every 5000 ms. There is no `Save` action. But the `Ctrl-Z` (undo) can be used. -Furthermore : +Furthermore: - No copy of the shared folder are kept in the browser storage. - - It is not possible to create a file from `Remix IDE` (that might change). + - Clicking on the new folder or new file icon under localhost will create a new file or folder in the shared folder. - If a folder does not contain any file, the folder will not be displayed in the explorer (that might change). - Symbolic links are not forwarded to Remix IDE. + +## Ports Usage +remixd creates a websocket connections with Remix IDE on different ports. Ports are defined according to specific purpose. Port usage details are as: + +- **65520** : For `remixd` websocket listener, to share a project from local device with Remix IDE. Shared folder will be loaded in the Remix IDE File Explorer workspace named localhost [See more](https://remix-ide.readthedocs.io/en/latest/remixd.html) +- **65522** : For `Hardhat` websocket listener, to enable the Hardhat Compilation using Remix IDE Solidity Compiler plugin, if shared folder is a Hardhat project [See more](https://remix-ide.readthedocs.io/en/latest/hardhat.html) +- **65523** : For `Slither` websocket listener, to enable the Slither Analysis using Remix IDE Solidity Static Analysis plugin [See more](https://remix-ide.readthedocs.io/en/latest/slither.html) +- **65524** : For `Truffle` websocket listener, to enable the Truffle Compilation using Remix IDE Solidity Compiler plugin, if shared folder is a Truffle project [See more](https://remix-ide.readthedocs.io/en/latest/truffle.html) + +Note: Please make sure your system is secured enough and these ports are not opened nor forwarded. From 44d2c301ce6552f9886eaad902ed989e1d1b1dba Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 21 Jul 2022 14:22:30 +0530 Subject: [PATCH 009/194] badges --- libs/remixd/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/remixd/README.md b/libs/remixd/README.md index c0ed97a6a0..bb563fe131 100644 --- a/libs/remixd/README.md +++ b/libs/remixd/README.md @@ -1,6 +1,11 @@ # Remixd -`remixd` is an NPM module that intends to be used with [Remix IDE](https://remix.ethereum.org/) web and desktop applications. It establishes a two-way websocket connection between the local computer and Remix IDE for a particular project directory. +[![npm version](https://badge.fury.io/js/%40remix-project%2Fremixd.svg)](https://www.npmjs.com/package/@remix-project/remixd) +[![npm](https://img.shields.io/npm/dt/@remix-project/remixd.svg?label=Total%20Downloads&logo=npm)](https://www.npmjs.com/package/@remix-project/remixd) +[![npm](https://img.shields.io/npm/dw/@remix-project/remixd.svg?logo=npm)](https://www.npmjs.com/package/@remix-project/remixd) + + +`@remix-project/remixd` is an NPM module that intends to be used with [Remix IDE](https://remix.ethereum.org/) web and desktop applications. It establishes a two-way websocket connection between the local computer and Remix IDE for a particular project directory. `remixd` can be used to setup a development environment with other popular frameworks like Hardhat, Truffle, Slither etc. From 01290d390b494d150308b36386c95b021bbbe40f Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 11:53:33 +0200 Subject: [PATCH 010/194] updates --- .../src/tests/file_decorator.test.ts | 96 ++++++++++++------- .../src/app/plugins/file-decorator.ts | 39 ++++---- .../file-decorators/src/lib/types/index.ts | 2 +- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts index 2aa226397c..9b47bbc61f 100644 --- a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -11,31 +11,50 @@ module.exports = { 'Test decorators with script': function (browser: NightwatchBrowser) { browser - .openFile('contracts') - .openFile('contracts/2_Owner.sol') - .openFile('contracts/1_Storage.sol') - .openFile('contracts/3_Ballot.sol') - .addFile('scripts/decorators.ts', { content: testScript }) - .pause(2000) - .executeScript('remix.exeCurrent()') - .pause(4000) - .useXpath() - .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') - .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') - .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') - .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') - .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') - .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') - .useCss() - .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) - .useXpath() - .moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0,0) - .waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]') - .waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner') + .openFile('contracts') + .openFile('contracts/2_Owner.sol') + .openFile('contracts/1_Storage.sol') + .openFile('contracts/3_Ballot.sol') + .addFile('scripts/decorators.ts', { content: testScriptSet }) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .useXpath() + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') + .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext') + .waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext') + .moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0) + .waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]') + .waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner') + }, + + 'clear ballot decorator': function (browser: NightwatchBrowser) { + browser + .useCss() + .addFile('scripts/clearballot.ts', { content: testScriptClearBallot }) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) + }, + 'clear all decorators': function (browser: NightwatchBrowser) { + browser + .addFile('scripts/clearall.ts', { content: testScriptClear }) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000) + .waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000) } + } -const testScript = ` +const testScriptSet = ` (async () => { remix.call('fileDecorator' as any, 'clearFileDecorators') let decorator: any = { @@ -46,7 +65,6 @@ const testScript = ` fileStateIconClass: '', fileStateIcon: '', text: '2', - owner: 'code-parser', bubble: true, comment: 'error on owner', } @@ -58,11 +76,10 @@ const testScript = ` fileStateIconClass: 'text-success', fileStateIcon: 'U', text: '', - owner: 'code-parser', bubble: true, comment: 'modified', } - remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) + await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) decorator = { path: 'contracts/1_Storage.sol', @@ -72,11 +89,10 @@ const testScript = ` fileStateIconClass: '', fileStateIcon: '', text: '2', - owner: 'code-parser', bubble: true, comment: 'warning on storage', } - remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) decorator = { path: 'contracts/3_Ballot.sol', @@ -85,13 +101,25 @@ const testScript = ` fileStateLabelClass: '', fileStateIconClass: '', fileStateIcon: 'customtext', - text: 'w', - owner: 'dgit', + text: 'with text', bubble: true, - comment: '', + comment: 'custom comment', } - remix.call('fileDecorator' as any, 'setFileDecorators', decorator) - - remix.call('fileDecorator' as any, 'clearFileDecorators', 'dgit') + await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) - })()` \ No newline at end of file + })()` + + +const testScriptClearBallot = ` + (async () => { + + await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol') + + })()` + +const testScriptClear = ` + (async () => { + await remix.call('fileDecorator' as any, 'clearAllFileDecorators') + + + })()` \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 5332db1508..5fc495bcf1 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -8,7 +8,7 @@ import { fileDecoration } from '@remix-ui/file-decorators' const profile = { name: 'fileDecorator', desciption: 'Keeps decorators of the files', - methods: ['setFileDecorators', 'clearFileDecorators'], + methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'], events: ['fileDecoratorsChanged'], version: '0.0.1' } @@ -25,15 +25,17 @@ export class FileDecorator extends Plugin { * @param fileStates Array of file states */ async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { + const { from } = this.currentRequest const workspace = await this.call('filePanel', 'getCurrentWorkspace') const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] // clear all file states in the previous state of this owner on the files called fileStatesPayload.forEach((state) => { state.workspace = workspace + state.owner = from }) const filteredState = this._fileStates.filter((state) => { const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { - return payloadFileState.owner == state.owner && payloadFileState.path == state.path + return from == state.owner && payloadFileState.path == state.path }) return index == -1 }) @@ -45,21 +47,26 @@ export class FileDecorator extends Plugin { } } - async clearFileDecorators(owner? : string) { - if(!owner) { - this._fileStates = [] - this.emit('fileDecoratorsChanged', []) - } else { - const filteredState = this._fileStates.filter((state) => { - return state.owner != owner - }) - const newState = [...filteredState].sort(sortByPath) - - if (!deepequal(newState, this._fileStates)) { - this._fileStates = newState - this.emit('fileDecoratorsChanged', this._fileStates) - } + async clearFileDecorators(path?: string) { + const { from } = this.currentRequest + if (!from) return + + const filteredState = this._fileStates.filter((state) => { + if(state.owner != from) return true + if(path && state.path != path) return true + }) + const newState = [...filteredState].sort(sortByPath) + + if (!deepequal(newState, this._fileStates)) { + this._fileStates = newState + this.emit('fileDecoratorsChanged', this._fileStates) } + + } + + async clearAllFileDecorators() { + this._fileStates = [] + this.emit('fileDecoratorsChanged', []) } } diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts index ef6620434a..fa783ec6e5 100644 --- a/libs/remix-ui/file-decorators/src/lib/types/index.ts +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -14,7 +14,7 @@ export enum fileDecorationType { fileStateIcon: string | HTMLDivElement | JSX.Element, bubble: boolean, text?: string, - owner: string, + owner?: string, workspace?: any tooltip?: string comment?: string[] | string From 6b89a38fe8bcad4ee7786840dbd8931a8f71fb14 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 11:57:25 +0200 Subject: [PATCH 011/194] clear on workspace change --- apps/remix-ide/src/app/plugins/file-decorator.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 5fc495bcf1..c81c1894ff 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -19,6 +19,11 @@ export class FileDecorator extends Plugin { super(profile) } + onActivation(): void { + this.on('filePanel', 'setWorkspace', async () => { + await this.clearAllFileDecorators() + }) + } /** * From ccba9a8d168ec26d502078e94866c186f876be4b Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:06:42 +0200 Subject: [PATCH 012/194] cherry pick editor --- apps/remix-ide/src/app/editor/editor.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 97dc63f988..7725387688 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -13,7 +13,7 @@ const profile = { name: 'editor', description: 'service - editor', version: packageJson.version, - methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition'] + methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open'] } class Editor extends Plugin { @@ -26,8 +26,8 @@ class Editor extends Plugin { remixDark: 'remix-dark' } - this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } - this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } + this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } + this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } // Init this.event = new EventManager() @@ -567,6 +567,18 @@ class Editor extends Plugin { this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations) } } + + async addLineText (lineText, filePath) { + filePath = filePath || this.currentFile + await this.addDecoration(lineText, filePath, 'lineTextPerFile') + } + + discardLineTexts() { + const { from } = this.currentRequest + for (const session in this.sessions) { + this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations) + } + } } module.exports = Editor From 4772a2fdb58a68d8255e8a0ffb0df50d71cc50bd Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:33:37 +0200 Subject: [PATCH 013/194] types --- .../editor/src/lib/remix-ui-editor.tsx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) 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 46a5e76d9b..59637a2438 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -4,6 +4,7 @@ import Editor, { loader } from '@monaco-editor/react' import { reducerActions, reducerListener, initialState } from './actions/editor' import { language, conf } from './syntax' import { cairoLang, cairoConf } from './cairoSyntax' +import { IMarkdownString } from 'monaco-editor' import './remix-ui-editor.css' import { loadTypes } from './web-types' @@ -39,6 +40,25 @@ type sourceMarker = { hide: boolean } +export type lineText = { + position: { + start: { + line: number + column: number + }, + end: { + line: number + column: number + } + }, + from?: string // plugin name + content: string + className: string + afterContentClassName: string + hide: boolean, + hoverMessage: IMarkdownString | IMarkdownString[] +} + loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } }) export type DecorationsReturn = { @@ -259,7 +279,7 @@ export const EditorUI = (props: EditorUIProps) => { } }, [props.currentFile]) - const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => { + const convertToMonacoDecoration = (decoration: lineText | sourceAnnotation | sourceMarker, typeOfDecoration: string) => { if (typeOfDecoration === 'sourceAnnotationsPerFile') { decoration = decoration as sourceAnnotation return { @@ -289,6 +309,19 @@ export const EditorUI = (props: EditorUIProps) => { } } } + if (typeOfDecoration === 'lineTextPerFile') { + const lineTextDecoration = decoration as lineText + return { + type: typeOfDecoration, + range: new monacoRef.current.Range(lineTextDecoration.position.start.line + 1, lineTextDecoration.position.start.column + 1, lineTextDecoration.position.start.line + 1, 1024), + options: { + after: { content: ` ${lineTextDecoration.content}`, inlineClassName: `${lineTextDecoration.className}` }, + afterContentClassName: `${lineTextDecoration.afterContentClassName}`, + hoverMessage : lineTextDecoration.hoverMessage + }, + + } + } } props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => { From 36211af9f8c072789cb8c521c740e9f506802389 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:34:23 +0200 Subject: [PATCH 014/194] rm open call --- apps/remix-ide/src/app/editor/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 7725387688..34b9c172e5 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -13,7 +13,7 @@ const profile = { name: 'editor', description: 'service - editor', version: packageJson.version, - methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open'] + methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition'] } class Editor extends Plugin { From c11dd9e0452a5b156f504db4d44971e0cf196da4 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 13:28:22 +0200 Subject: [PATCH 015/194] add tests --- .../src/tests/editor_line_text.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 apps/remix-ide-e2e/src/tests/editor_line_text.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts new file mode 100644 index 0000000000..d0d079cf68 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts @@ -0,0 +1,69 @@ +'use strict' + +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', true) + }, + 'Should add line texts': function (browser: NightwatchBrowser) { + browser + .openFile('contracts') + .openFile('contracts/1_Storage.sol') + .addFile('scripts/addlinetext.ts', {content: addLineText}) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .openFile('contracts/1_Storage.sol') + .useXpath() + .waitForElementVisible("//*[@class='view-line' and contains(.,'contract')]//span//span[contains(.,'mylinetext1')]") + .waitForElementVisible("//*[@class='view-line' and contains(.,'function')]//span//span[contains(.,'mylinetext2')]") + } +} + +const addLineText = ` +(async () => { + + await remix.call('editor', 'discardLineTexts' as any) + let linetext = { + content: 'mylinetext1', + position: { + start: { + line: 9, + column: 1, + } + }, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + hoverMessage: [{ + value: 'hovering1', + }, + ], + } + + await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol') + + + linetext = { + content: 'mylinetext2', + position: { + start: { + line: 17, + column: 1, + } + }, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + hoverMessage: [{ + value: 'hovering2', + }, + ], + } + + await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol') + +})()` \ No newline at end of file From 842aa08f7c0bcb3cc39bdd0b97048ee2d2e94ff0 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 26 Jul 2022 19:09:01 +0530 Subject: [PATCH 016/194] save file with .yul extension if language is yul in URL --- libs/remix-ui/workspace/src/lib/actions/workspace.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 37533c87c6..221e0e1a09 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -75,7 +75,8 @@ export const createWorkspaceTemplate = async (workspaceName: string, template: W export type UrlParametersType = { gist: string, code: string, - url: string + url: string, + language: string } export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault') => { @@ -91,7 +92,7 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe if (params.code) { const hash = bufferToHex(keccakFromString(params.code)) - path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' + path = 'contract-' + hash.replace('0x', '').substring(0, 10) + (params.language && params.language.toLowerCase() === 'yul' ? '.yul': '.sol') content = atob(params.code) await workspaceProvider.set(path, content) } From 1c0ad8084299db972cb32d6d82a1336c76702db9 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 26 Jul 2022 19:30:23 +0530 Subject: [PATCH 017/194] e2e added --- apps/remix-ide-e2e/src/tests/url.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index 00885338b4..2d7554215e 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -76,6 +76,22 @@ module.exports = { .openFile('ethereum/remix-project/apps/remix-ide/contracts/app/solidity/mode.sol') }, + 'Should load the code from language & code params': function (browser: NightwatchBrowser) { + browser + .pause(5000) + .url('http://127.0.0.1:8080/#language=yul&version=soljson-v0.8.7+commit.e28d00a7.js&code=Ly8gQSBjb250cmFjdCBjb25zaXN0cyBvZiBhIHNpbmdsZSBvYmplY3Qgd2l0aCBzdWItb2JqZWN0cyByZXByZXNlbnRpbmcKLy8gdGhlIGNvZGUgdG8gYmUgZGVwbG95ZWQgb3Igb3RoZXIgY29udHJhY3RzIGl0IGNhbiBjcmVhdGUuCi8vIFRoZSBzaW5nbGUgImNvZGUiIG5vZGUgaXMgdGhlIGV4ZWN1dGFibGUgY29kZSBvZiB0aGUgb2JqZWN0LgovLyBFdmVyeSAob3RoZXIpIG5hbWVkIG9iamVjdCBvciBkYXRhIHNlY3Rpb24gaXMgc2VyaWFsaXplZCBhbmQKLy8gbWFkZSBhY2Nlc3NpYmxlIHRvIHRoZSBzcGVjaWFsIGJ1aWx0LWluIGZ1bmN0aW9ucyBkYXRhY29weSAvIGRhdGFvZmZzZXQgLyBkYXRhc2l6ZQovLyBUaGUgY3VycmVudCBvYmplY3QsIHN1Yi1vYmplY3RzIGFuZCBkYXRhIGl0ZW1zIGluc2lkZSB0aGUgY3VycmVudCBvYmplY3QKLy8gYXJlIGluIHNjb3BlLgpvYmplY3QgIkNvbnRyYWN0MSIgewogICAgLy8gVGhpcyBpcyB0aGUgY29uc3RydWN0b3IgY29kZSBvZiB0aGUgY29udHJhY3QuCiAgICBjb2RlIHsKICAgICAgICBmdW5jdGlvbiBhbGxvY2F0ZShzaXplKSAtPiBwdHIgewogICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgaWYgaXN6ZXJvKHB0cikgeyBwdHIgOj0gMHg2MCB9CiAgICAgICAgICAgIG1zdG9yZSgweDQwLCBhZGQocHRyLCBzaXplKSkKICAgICAgICB9CgogICAgICAgIC8vIGZpcnN0IGNyZWF0ZSAiQ29udHJhY3QyIgogICAgICAgIGxldCBzaXplIDo9IGRhdGFzaXplKCJDb250cmFjdDIiKQogICAgICAgIGxldCBvZmZzZXQgOj0gYWxsb2NhdGUoc2l6ZSkKICAgICAgICAvLyBUaGlzIHdpbGwgdHVybiBpbnRvIGNvZGVjb3B5IGZvciBFVk0KICAgICAgICBkYXRhY29weShvZmZzZXQsIGRhdGFvZmZzZXQoIkNvbnRyYWN0MiIpLCBzaXplKQogICAgICAgIC8vIGNvbnN0cnVjdG9yIHBhcmFtZXRlciBpcyBhIHNpbmdsZSBudW1iZXIgMHgxMjM0CiAgICAgICAgbXN0b3JlKGFkZChvZmZzZXQsIHNpemUpLCAweDEyMzQpCiAgICAgICAgcG9wKGNyZWF0ZShvZmZzZXQsIGFkZChzaXplLCAzMiksIDApKQoKICAgICAgICAvLyBub3cgcmV0dXJuIHRoZSBydW50aW1lIG9iamVjdCAodGhlIGN1cnJlbnRseQogICAgICAgIC8vIGV4ZWN1dGluZyBjb2RlIGlzIHRoZSBjb25zdHJ1Y3RvciBjb2RlKQogICAgICAgIHNpemUgOj0gZGF0YXNpemUoIkNvbnRyYWN0MV9kZXBsb3llZCIpCiAgICAgICAgb2Zmc2V0IDo9IGFsbG9jYXRlKHNpemUpCiAgICAgICAgLy8gVGhpcyB3aWxsIHR1cm4gaW50byBhIG1lbW9yeS0+bWVtb3J5IGNvcHkgZm9yIEV3YXNtIGFuZAogICAgICAgIC8vIGEgY29kZWNvcHkgZm9yIEVWTQogICAgICAgIGRhdGFjb3B5KG9mZnNldCwgZGF0YW9mZnNldCgiQ29udHJhY3QxX2RlcGxveWVkIiksIHNpemUpCiAgICAgICAgcmV0dXJuKG9mZnNldCwgc2l6ZSkKICAgIH0KCiAgICBkYXRhICJUYWJsZTIiIGhleCI0MTIzIgoKICAgIG9iamVjdCAiQ29udHJhY3QxX2RlcGxveWVkIiB7CiAgICAgICAgY29kZSB7CiAgICAgICAgICAgIGZ1bmN0aW9uIGFsbG9jYXRlKHNpemUpIC0+IHB0ciB7CiAgICAgICAgICAgICAgICBwdHIgOj0gbWxvYWQoMHg0MCkKICAgICAgICAgICAgICAgIGlmIGlzemVybyhwdHIpIHsgcHRyIDo9IDB4NjAgfQogICAgICAgICAgICAgICAgbXN0b3JlKDB4NDAsIGFkZChwdHIsIHNpemUpKQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBydW50aW1lIGNvZGUKCiAgICAgICAgICAgIG1zdG9yZSgwLCAiSGVsbG8sIFdvcmxkISIpCiAgICAgICAgICAgIHJldHVybigwLCAweDIwKQogICAgICAgIH0KICAgIH0KCiAgICAvLyBFbWJlZGRlZCBvYmplY3QuIFVzZSBjYXNlIGlzIHRoYXQgdGhlIG91dHNpZGUgaXMgYSBmYWN0b3J5IGNvbnRyYWN0LAogICAgLy8gYW5kIENvbnRyYWN0MiBpcyB0aGUgY29kZSB0byBiZSBjcmVhdGVkIGJ5IHRoZSBmYWN0b3J5CiAgICBvYmplY3QgIkNvbnRyYWN0MiIgewogICAgICAgIGNvZGUgewogICAgICAgICAgICAvLyBjb2RlIGhlcmUgLi4uCiAgICAgICAgfQoKICAgICAgICBvYmplY3QgIkNvbnRyYWN0Ml9kZXBsb3llZCIgewogICAgICAgICAgICBjb2RlIHsKICAgICAgICAgICAgICAgIC8vIGNvZGUgaGVyZSAuLi4KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgZGF0YSAiVGFibGUxIiBoZXgiNDEyMyIKICAgIH0KfQ&optimize=false&runs=200&evmVersion=null') + .refresh() + .pause(5000) + .clickLaunchIcon('filePanel') + .currentWorkspaceIs('code-sample') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontract-eaa022e37e.yul"]', 6000) + .openFile('contract-eaa022e37e.yul') + .getEditorValue((content) => { + browser.assert.ok(content && content.indexOf( + 'object "Contract1" {') !== -1) + }) + }, + 'Should load using URL compiler params': function (browser: NightwatchBrowser) { browser .pause(5000) From e7fdf9aa19e79154587bf7bf8db04a2824ef48dd Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 26 Jul 2022 14:15:24 +0530 Subject: [PATCH 018/194] allow yul deployment --- libs/remix-core-plugin/src/lib/compiler-metadata.ts | 2 +- libs/remix-core-plugin/src/lib/editor-context-listener.ts | 2 +- libs/remix-ui/run-tab/src/lib/actions/events.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/remix-core-plugin/src/lib/compiler-metadata.ts b/libs/remix-core-plugin/src/lib/compiler-metadata.ts index 2f065e769f..9a69e616f4 100644 --- a/libs/remix-core-plugin/src/lib/compiler-metadata.ts +++ b/libs/remix-core-plugin/src/lib/compiler-metadata.ts @@ -87,7 +87,7 @@ export class CompilerMetadata extends Plugin { let parsedMetadata try { - parsedMetadata = JSON.parse(contract.object.metadata) + parsedMetadata = contract.object && contract.object.metadata ? JSON.parse(contract.object.metadata) : null } catch (e) { console.log(e) } 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 6fd5674e8a..3785c8ffd1 100644 --- a/libs/remix-core-plugin/src/lib/editor-context-listener.ts +++ b/libs/remix-core-plugin/src/lib/editor-context-listener.ts @@ -92,7 +92,7 @@ export class EditorContextListener extends Plugin { this._stopHighlighting() this.currentPosition = cursorPosition this.currentFile = file - if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) { + if (compilationResult && compilationResult.data && compilationResult.data.sources && compilationResult.data.sources[file]) { const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file]) this.nodes = nodes if (nodes && nodes.length && nodes[nodes.length - 1]) { diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index 4355f4e87e..e47724aef0 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -95,7 +95,6 @@ export const setupEvents = (plugin: RunTab, dispatch: React.Dispatch) => { const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispatch, file, source, languageVersion, data, input?) => { // TODO check whether the tab is configured const compiler = new CompilerAbstract(languageVersion, data, source, input) - plugin.compilersArtefacts[languageVersion] = compiler plugin.compilersArtefacts.__last = compiler @@ -103,9 +102,8 @@ const broadcastCompilationResult = async (plugin: RunTab, dispatch: React.Dispat return { name: languageVersion, alias: contract.name, file: contract.file, compiler } }) const index = contracts.findIndex(contract => contract.alias === plugin.REACT_API.contracts.currentContract) - if ((index < 0) && (contracts.length > 0)) dispatch(setCurrentContract(contracts[0].alias)) - const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources[file] ? data.sources[file].ast : {}) + const isUpgradeable = await plugin.call('openzeppelin-proxy', 'isConcerned', data.sources && data.sources[file] ? data.sources[file].ast : {}) if (isUpgradeable) { const options = await plugin.call('openzeppelin-proxy', 'getProxyOptions', data, file) From ad54a0c5d80aa81498caeb68130a3378cee210ff Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 26 Jul 2022 18:50:39 +0530 Subject: [PATCH 019/194] e2e tests added --- apps/remix-ide-e2e/src/tests/ballot.test.ts | 36 +++++++++++++++++++ .../src/lib/compiler-container.tsx | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index 1c8383f047..04fd37077f 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -134,6 +134,27 @@ module.exports = { .sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER) .openFile('Untitled.sol') .verifyContracts(['Ballot'], {wait: 2000, runs: '300'}) + }, + + 'Compile and deploy sample yul file': function (browser: NightwatchBrowser) { + browser + .addFile('sample.yul', {content: yulSample}) + .clickLaunchIcon('solidity') + .waitForElementVisible('*[data-id="scConfigExpander"]') + .click('*[data-id="scManualConfiguration"]') + .waitForElementVisible('select[id="compilierLanguageSelector"]', 10000) + .click('select[id="compilierLanguageSelector"]') + .click('select[id="compilierLanguageSelector"] option[value=Yul]') + .waitForElementContainsText('[data-id="compiledContracts"]', 'Contract', 60000) + .clickLaunchIcon('udapp') + .click('*[data-id="Deploy - transact (not payable)"]') + .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) + .testFunction('last', + { + status: 'true Transaction mined and execution succeed', + to: 'Contract.(constructor)' + }) + .pause(3000) .end() } } @@ -387,4 +408,19 @@ const configFile = ` "evmVersion": "byzantium" } } +` + +const yulSample = ` +object "Contract" { + code { + function power(base, exponent) -> result + { + result := 1 + for { let i := 0 } lt(i, exponent) { i := add(i, 1) } + { + result := mul(result, base) + } + } + } +} ` \ No newline at end of file diff --git a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx index 80d5e88e9f..34613067a2 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx @@ -765,7 +765,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
- +
From 15de89b71aecda654389c05f3f17e2f67a20b414 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Wed, 27 Jul 2022 12:30:36 +0530 Subject: [PATCH 020/194] e2e fix --- apps/remix-ide-e2e/src/tests/ballot.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index 04fd37077f..fc090988e1 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -149,12 +149,8 @@ module.exports = { .clickLaunchIcon('udapp') .click('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .testFunction('last', - { - status: 'true Transaction mined and execution succeed', - to: 'Contract.(constructor)' - }) - .pause(3000) + .journalLastChildIncludes('Contract.(constructor)') + .journalLastChildIncludes('data: 0x602...0565b') .end() } } From fd18da31504f5b8bf77908229d5ca67c2dcfc4a7 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Wed, 27 Jul 2022 13:43:11 +0530 Subject: [PATCH 021/194] bump remixd --- libs/remixd/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remixd/package.json b/libs/remixd/package.json index 9cee73f6e9..f46b7c0d97 100644 --- a/libs/remixd/package.json +++ b/libs/remixd/package.json @@ -1,6 +1,6 @@ { "name": "@remix-project/remixd", - "version": "0.6.4", + "version": "0.6.5", "description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)", "main": "index.js", "types": "./index.d.ts", From c0fb88e8c733665d9f306c0713f382fb4b0be5eb Mon Sep 17 00:00:00 2001 From: David Disu Date: Mon, 25 Jul 2022 15:09:58 +0100 Subject: [PATCH 022/194] Enable deploy with proxy url params --- .../src/lib/constants/uups.ts | 3 +- .../src/lib/openzeppelin-proxy.ts | 52 +++++++++++-------- .../src/lib/components/contractGUI.tsx | 22 +++++--- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/libs/remix-core-plugin/src/lib/constants/uups.ts b/libs/remix-core-plugin/src/lib/constants/uups.ts index 606d62dd77..66d28acc59 100644 --- a/libs/remix-core-plugin/src/lib/constants/uups.ts +++ b/libs/remix-core-plugin/src/lib/constants/uups.ts @@ -102,4 +102,5 @@ export const UUPSupgradeAbi = { "outputs": [], "stateMutability": "nonpayable", "type": "function" -} \ No newline at end of file +} +export const EnableProxyURLParam = 'deployProxy' \ No newline at end of file diff --git a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts index ef03092cb8..25aa04c91e 100644 --- a/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts +++ b/libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts @@ -1,6 +1,6 @@ import { Plugin } from '@remixproject/engine' import { ContractAST, ContractSources, DeployOptions } from '../types/contract' -import { UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups' +import { EnableProxyURLParam, UUPS, UUPSABI, UUPSBytecode, UUPSfunAbi, UUPSupgradeAbi } from './constants/uups' const proxyProfile = { name: 'openzeppelin-proxy', @@ -33,31 +33,39 @@ export class OpenZeppelinProxy extends Plugin { async getProxyOptions (data: ContractSources, file: string): Promise<{ [name: string]: DeployOptions }> { const contracts = data.contracts[file] const ast = data.sources[file].ast - const inputs = {} if (this.kind === 'UUPS') { - Object.keys(contracts).map(name => { - if (ast) { - const UUPSSymbol = ast.exportedSymbols[UUPS] ? ast.exportedSymbols[UUPS][0] : null - - ast.absolutePath === file && ast.nodes.map((node) => { - if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) { - const abi = contracts[name].abi - const initializeInput = abi.find(node => node.name === 'initialize') - - inputs[name] = { - options: [{ title: 'Deploy with Proxy', active: false }, { title: 'Upgrade with Proxy', active: false }], - initializeOptions: { - inputs: initializeInput, - initializeInputs: initializeInput ? this.blockchain.getInputs(initializeInput) : null - } + const options = await (this.getUUPSContractOptions(contracts, ast, file)) + + return options + } + } + + async getUUPSContractOptions (contracts, ast, file) { + const options = {} + + await Promise.all(Object.keys(contracts).map(async (name) => { + if (ast) { + const UUPSSymbol = ast.exportedSymbols[UUPS] ? ast.exportedSymbols[UUPS][0] : null + + await Promise.all(ast.absolutePath === file && ast.nodes.map(async (node) => { + if (node.name === name && node.linearizedBaseContracts.includes(UUPSSymbol)) { + const abi = contracts[name].abi + const initializeInput = abi.find(node => node.name === 'initialize') + const isDeployWithProxyEnabled: boolean = await this.call('config', 'getAppParameter', EnableProxyURLParam) || false + + options[name] = { + options: [{ title: 'Deploy with Proxy', active: isDeployWithProxyEnabled }, { title: 'Upgrade with Proxy', active: false }], + initializeOptions: { + inputs: initializeInput, + initializeInputs: initializeInput ? this.blockchain.getInputs(initializeInput) : null } } - }) - } - }) - } - return inputs + } + })) + } + })) + return options } async executeUUPSProxy(implAddress: string, args: string | string [] = '', initializeABI, implementationContractObject): Promise { diff --git a/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx index 7fe409e511..9cc5b061b2 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx @@ -24,6 +24,16 @@ export function ContractGUI (props: ContractGUIProps) { const initializeFields = useRef>([]) const basicInputRef = useRef() + useEffect(() => { + if (props.deployOption && Array.isArray(props.deployOption)) { + if (props.deployOption[0] && props.deployOption[0].title === 'Deploy with Proxy') { + handleDeployProxySelect(props.deployOption[0].active) + } else if (props.deployOption[1] && props.deployOption[1].title === 'Deploy with Proxy') { + handleUpgradeImpSelect(props.deployOption[1].active) + } + } + }, [props.deployOption]) + useEffect(() => { if (props.title) { setTitle(props.title) @@ -179,9 +189,7 @@ export function ContractGUI (props: ContractGUIProps) { setToggleDeployProxy(!toggleDeployProxy) } - const handleDeployProxySelect = (e) => { - const value = e.target.checked - + const handleDeployProxySelect = (value: boolean) => { if (value) setToggleUpgradeImp(false) setToggleDeployProxy(value) setDeployState({ upgrade: false, deploy: value }) @@ -191,9 +199,7 @@ export function ContractGUI (props: ContractGUIProps) { setToggleUpgradeImp(!toggleUpgradeImp) } - const handleUpgradeImpSelect = (e) => { - const value = e.target.checked - + const handleUpgradeImpSelect = (value: boolean) => { setToggleUpgradeImp(value) if (value) { setToggleDeployProxy(false) @@ -264,7 +270,7 @@ export function ContractGUI (props: ContractGUIProps) { data-id="contractGUIDeployWithProxy" className="form-check-input custom-control-input" type="checkbox" - onChange={handleDeployProxySelect} + onChange={(e) => handleDeployProxySelect(e.target.checked)} checked={deployState.deploy} />