Merge branch 'master' of https://github.com/ethereum/remix-project into fixcompiler

pull/4063/head
filip mertens 1 year ago
commit be85eaad61
  1. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  2. 9
      apps/remix-ide/src/app.js
  3. 8
      apps/remix-ide/src/app/panels/layout.ts
  4. 47
      apps/remix-ide/src/app/plugins/openaigpt.tsx
  5. 2
      libs/remix-debug/src/init.ts
  6. 303
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  7. 16
      libs/remix-ui/renderer/src/lib/renderer.tsx
  8. 100
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  9. 17
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  10. 191
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  11. 3
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
  12. 1
      package.json
  13. 15
      yarn.lock

@ -238,8 +238,8 @@ module.exports = {
}) })
.addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol']) .addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -261,8 +261,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]') .waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedLog_test.sol', sources[0]['tests/ballotFailedLog_test.sol']) .addFile('tests/ballotFailedLog_test.sol', sources[0]['tests/ballotFailedLog_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -278,8 +278,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]') .waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol']) .addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000) .waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]') .click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction') .click('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000) .waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000) .waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000)

@ -46,6 +46,7 @@ import {FileDecorator} from './app/plugins/file-decorator'
import {CodeFormat} from './app/plugins/code-format' import {CodeFormat} from './app/plugins/code-format'
import {SolidityUmlGen} from './app/plugins/solidity-umlgen' import {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import {ContractFlattener} from './app/plugins/contractFlattener' import {ContractFlattener} from './app/plugins/contractFlattener'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -180,6 +181,9 @@ class AppComponent {
// ----------------- ContractFlattener ---------------------------- // ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener() const contractFlattener = new ContractFlattener()
// ----------------- Open AI --------------------------------------
const openaigpt = new OpenAIGpt()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -297,7 +301,8 @@ class AppComponent {
search, search,
solidityumlgen, solidityumlgen,
contractFlattener, contractFlattener,
solidityScript solidityScript,
openaigpt
]) ])
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
@ -410,7 +415,7 @@ class AppComponent {
]) ])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script', 'openaigpt'])
this.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { this.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => {
// for e2e tests // for e2e tests

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = { const profile: Profile = {
name: 'layout', name: 'layout',
description: 'layout', description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel'] methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal']
} }
interface panelState { interface panelState {
@ -109,6 +109,12 @@ export class Layout extends Plugin {
this.maximised[current] = true this.maximised[current] = true
} }
async maximizeTerminal() {
this.panels.terminal.minimized = false
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async resetSidePanel () { async resetSidePanel () {
this.event.emit('resetsidepanel') this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus') const current = await this.call('sidePanel', 'currentFocus')

@ -0,0 +1,47 @@
import { Plugin } from '@remixproject/engine'
import { CreateChatCompletionResponse } from 'openai'
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'openaigpt',
displayName: 'openaigpt',
description: 'openaigpt',
methods: ['message'],
events: [],
maintainedBy: 'Remix',
}
export class OpenAIGpt extends Plugin {
constructor() {
super(profile)
}
async message(prompt): Promise<CreateChatCompletionResponse> {
this.call('layout', 'maximizeTerminal')
this.call('terminal', 'log', 'Waiting for GPT answer...')
let result
try {
result = await (
await fetch('https://openai-gpt.remixproject.org', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
})
).json()
} catch (e) {
this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` })
return
}
if (result && result.choices && result.choices.length) {
this.call('terminal', 'log', { type: 'typewritersuccess', value: result.choices[0].message.content })
} else {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'No response...' })
}
return result.data
}
}

@ -19,7 +19,7 @@ export function setProvider (web3, url) {
export function web3DebugNode (network) { export function web3DebugNode (network) {
const web3DebugNodes = { const web3DebugNodes = {
Main: 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym', Main: 'https://eth.getblock.io/68069907-1d3c-466e-a533-a943afd935c6/mainnet',
Rinkeby: 'https://remix-rinkeby.ethdevops.io', Rinkeby: 'https://remix-rinkeby.ethdevops.io',
Ropsten: 'https://remix-ropsten.ethdevops.io', Ropsten: 'https://remix-ropsten.ethdevops.io',
Goerli: 'https://remix-goerli.ethdevops.io', Goerli: 'https://remix-goerli.ethdevops.io',

@ -1,30 +1,30 @@
import React, {useState, useRef, useEffect, useReducer} from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import {isArray} from 'lodash' import { isArray } from 'lodash'
import Editor, {loader, Monaco} from '@monaco-editor/react' import Editor, { loader, Monaco } from '@monaco-editor/react'
import {AlertModal} from '@remix-ui/app' import { AlertModal } from '@remix-ui/app'
import {reducerActions, reducerListener, initialState} from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
import {solidityTokensProvider, solidityLanguageConfig} from './syntaxes/solidity' import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import {cairoTokensProvider, cairoLanguageConfig} from './syntaxes/cairo' import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import {zokratesTokensProvider, zokratesLanguageConfig} from './syntaxes/zokrates' import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import {moveTokenProvider, moveLanguageConfig} from './syntaxes/move' import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import {monacoTypes} from '@remix-ui/editor' import { monacoTypes } from '@remix-ui/editor'
import {loadTypes} from './web-types' import { loadTypes } from './web-types'
import {retrieveNodesAtPosition} from './helpers/retrieveNodesAtPosition' import { retrieveNodesAtPosition } from './helpers/retrieveNodesAtPosition'
import {RemixHoverProvider} from './providers/hoverProvider' import { RemixHoverProvider } from './providers/hoverProvider'
import {RemixReferenceProvider} from './providers/referenceProvider' import { RemixReferenceProvider } from './providers/referenceProvider'
import {RemixCompletionProvider} from './providers/completionProvider' import { RemixCompletionProvider } from './providers/completionProvider'
import {RemixHighLightProvider} from './providers/highlightProvider' import { RemixHighLightProvider } from './providers/highlightProvider'
import {RemixDefinitionProvider} from './providers/definitionProvider' import { RemixDefinitionProvider } from './providers/definitionProvider'
import {RemixCodeActionProvider} from './providers/codeActionProvider' import { RemixCodeActionProvider } from './providers/codeActionProvider'
import './remix-ui-editor.css' import './remix-ui-editor.css'
import {circomLanguageConfig, circomTokensProvider} from './syntaxes/circom' import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import {IPosition} from 'monaco-editor' import { IPosition } from 'monaco-editor'
enum MarkerSeverity { enum MarkerSeverity {
Hint = 1, Hint = 1,
Info = 2, Info = 2,
Warning = 4, Warning = 4,
Error = 8 Error = 8,
} }
type sourceAnnotation = { type sourceAnnotation = {
@ -86,7 +86,7 @@ type errorMarker = {
file: string file: string
} }
loader.config({paths: {vs: 'assets/js/monaco-editor/min/vs'}}) loader.config({ paths: { vs: 'assets/js/monaco-editor/min/vs' } })
export type DecorationsReturn = { export type DecorationsReturn = {
currentDecorations: Array<string> currentDecorations: Array<string>
@ -108,7 +108,8 @@ export type EditorAPIType = {
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: errorMarker[], from: string) => void addErrorMarker: (errors: errorMarker[], from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void clearErrorMarkers: (sources: string[] | { [fileName: string]: any }, from: string) => void
getPositionAt: (offset: number) => monacoTypes.IPosition
} }
/* eslint-disable-next-line */ /* eslint-disable-next-line */
@ -152,6 +153,7 @@ export const EditorUI = (props: EditorUIProps) => {
const pasteCodeRef = useRef(false) const pasteCodeRef = useRef(false)
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null) const monacoRef = useRef<Monaco>(null)
const currentFunction = useRef('')
const currentFileRef = useRef('') const currentFileRef = useRef('')
const currentUrlRef = useRef('') const currentUrlRef = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor // const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
@ -194,88 +196,88 @@ export const EditorUI = (props: EditorUIProps) => {
base: themeType, base: themeType,
inherit: true, // can also be false to completely replace the builtin rules inherit: true, // can also be false to completely replace the builtin rules
rules: [ rules: [
{background: darkColor.replace('#', '')}, { background: darkColor.replace('#', '') },
{foreground: textColor.replace('#', '')}, { foreground: textColor.replace('#', '') },
// global variables // global variables
{token: 'keyword.abi', foreground: blueColor}, { token: 'keyword.abi', foreground: blueColor },
{token: 'keyword.block', foreground: blueColor}, { token: 'keyword.block', foreground: blueColor },
{token: 'keyword.bytes', foreground: blueColor}, { token: 'keyword.bytes', foreground: blueColor },
{token: 'keyword.msg', foreground: blueColor}, { token: 'keyword.msg', foreground: blueColor },
{token: 'keyword.tx', foreground: blueColor}, { token: 'keyword.tx', foreground: blueColor },
// global functions // global functions
{token: 'keyword.assert', foreground: blueColor}, { token: 'keyword.assert', foreground: blueColor },
{token: 'keyword.require', foreground: blueColor}, { token: 'keyword.require', foreground: blueColor },
{token: 'keyword.revert', foreground: blueColor}, { token: 'keyword.revert', foreground: blueColor },
{token: 'keyword.blockhash', foreground: blueColor}, { token: 'keyword.blockhash', foreground: blueColor },
{token: 'keyword.keccak256', foreground: blueColor}, { token: 'keyword.keccak256', foreground: blueColor },
{token: 'keyword.sha256', foreground: blueColor}, { token: 'keyword.sha256', foreground: blueColor },
{token: 'keyword.ripemd160', foreground: blueColor}, { token: 'keyword.ripemd160', foreground: blueColor },
{token: 'keyword.ecrecover', foreground: blueColor}, { token: 'keyword.ecrecover', foreground: blueColor },
{token: 'keyword.addmod', foreground: blueColor}, { token: 'keyword.addmod', foreground: blueColor },
{token: 'keyword.mulmod', foreground: blueColor}, { token: 'keyword.mulmod', foreground: blueColor },
{token: 'keyword.selfdestruct', foreground: blueColor}, { token: 'keyword.selfdestruct', foreground: blueColor },
{token: 'keyword.type ', foreground: blueColor}, { token: 'keyword.type ', foreground: blueColor },
{token: 'keyword.gasleft', foreground: blueColor}, { token: 'keyword.gasleft', foreground: blueColor },
// specials // specials
{token: 'keyword.super', foreground: infoColor}, { token: 'keyword.super', foreground: infoColor },
{token: 'keyword.this', foreground: infoColor}, { token: 'keyword.this', foreground: infoColor },
{token: 'keyword.virtual', foreground: infoColor}, { token: 'keyword.virtual', foreground: infoColor },
// for state variables // for state variables
{token: 'keyword.constants', foreground: grayColor}, { token: 'keyword.constants', foreground: grayColor },
{token: 'keyword.override', foreground: grayColor}, { token: 'keyword.override', foreground: grayColor },
{token: 'keyword.immutable', foreground: grayColor}, { token: 'keyword.immutable', foreground: grayColor },
// data location // data location
{token: 'keyword.memory', foreground: locationColor}, { token: 'keyword.memory', foreground: locationColor },
{token: 'keyword.storage', foreground: locationColor}, { token: 'keyword.storage', foreground: locationColor },
{token: 'keyword.calldata', foreground: locationColor}, { token: 'keyword.calldata', foreground: locationColor },
// for Events // for Events
{token: 'keyword.indexed', foreground: yellowColor}, { token: 'keyword.indexed', foreground: yellowColor },
{token: 'keyword.anonymous', foreground: yellowColor}, { token: 'keyword.anonymous', foreground: yellowColor },
// for functions // for functions
{token: 'keyword.external', foreground: successColor}, { token: 'keyword.external', foreground: successColor },
{token: 'keyword.internal', foreground: successColor}, { token: 'keyword.internal', foreground: successColor },
{token: 'keyword.private', foreground: successColor}, { token: 'keyword.private', foreground: successColor },
{token: 'keyword.public', foreground: successColor}, { token: 'keyword.public', foreground: successColor },
{token: 'keyword.view', foreground: successColor}, { token: 'keyword.view', foreground: successColor },
{token: 'keyword.pure', foreground: successColor}, { token: 'keyword.pure', foreground: successColor },
{token: 'keyword.payable', foreground: successColor}, { token: 'keyword.payable', foreground: successColor },
{token: 'keyword.nonpayable', foreground: successColor}, { token: 'keyword.nonpayable', foreground: successColor },
// Errors // Errors
{token: 'keyword.Error', foreground: dangerColor}, { token: 'keyword.Error', foreground: dangerColor },
{token: 'keyword.Panic', foreground: dangerColor}, { token: 'keyword.Panic', foreground: dangerColor },
// special functions // special functions
{token: 'keyword.fallback', foreground: pinkColor}, { token: 'keyword.fallback', foreground: pinkColor },
{token: 'keyword.receive', foreground: pinkColor}, { token: 'keyword.receive', foreground: pinkColor },
{token: 'keyword.constructor', foreground: pinkColor}, { token: 'keyword.constructor', foreground: pinkColor },
// identifiers // identifiers
{token: 'keyword.identifier', foreground: warningColor}, { token: 'keyword.identifier', foreground: warningColor },
{token: 'keyword.for', foreground: warningColor}, { token: 'keyword.for', foreground: warningColor },
{token: 'keyword.break', foreground: warningColor}, { token: 'keyword.break', foreground: warningColor },
{token: 'keyword.continue', foreground: warningColor}, { token: 'keyword.continue', foreground: warningColor },
{token: 'keyword.while', foreground: warningColor}, { token: 'keyword.while', foreground: warningColor },
{token: 'keyword.do', foreground: warningColor}, { token: 'keyword.do', foreground: warningColor },
{token: 'keyword.delete', foreground: warningColor}, { token: 'keyword.delete', foreground: warningColor },
{token: 'keyword.if', foreground: yellowColor}, { token: 'keyword.if', foreground: yellowColor },
{token: 'keyword.else', foreground: yellowColor}, { token: 'keyword.else', foreground: yellowColor },
{token: 'keyword.throw', foreground: orangeColor}, { token: 'keyword.throw', foreground: orangeColor },
{token: 'keyword.catch', foreground: orangeColor}, { token: 'keyword.catch', foreground: orangeColor },
{token: 'keyword.try', foreground: orangeColor}, { token: 'keyword.try', foreground: orangeColor },
// returns // returns
{token: 'keyword.returns', foreground: greenColor}, { token: 'keyword.returns', foreground: greenColor },
{token: 'keyword.return', foreground: greenColor} { token: 'keyword.return', foreground: greenColor },
], ],
colors: { colors: {
// see https://code.visualstudio.com/api/references/theme-color for more settings // see https://code.visualstudio.com/api/references/theme-color for more settings
@ -294,8 +296,8 @@ export const EditorUI = (props: EditorUIProps) => {
'menu.background': textbackground, 'menu.background': textbackground,
'menu.selectionBackground': secondaryColor, 'menu.selectionBackground': secondaryColor,
'menu.selectionForeground': textColor, 'menu.selectionForeground': textColor,
'menu.selectionBorder': secondaryColor 'menu.selectionBorder': secondaryColor,
} },
}) })
monacoRef.current.editor.setTheme(themeName) monacoRef.current.editor.setTheme(themeName)
} }
@ -313,7 +315,7 @@ export const EditorUI = (props: EditorUIProps) => {
const file = editorModelsState[props.currentFile] const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model) editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ editorRef.current.updateOptions({
readOnly: editorModelsState[props.currentFile].readOnly readOnly: editorModelsState[props.currentFile].readOnly,
}) })
if (file.language === 'sol') { if (file.language === 'sol') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
@ -337,10 +339,10 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
isWholeLine: false, isWholeLine: false,
glyphMarginHoverMessage: { glyphMarginHoverMessage: {
value: (decoration.from ? `from ${decoration.from}:\n` : '') + decoration.text value: (decoration.from ? `from ${decoration.from}:\n` : '') + decoration.text,
}, },
glyphMarginClassName: `fal fa-exclamation-square text-${decoration.type === 'error' ? 'danger' : decoration.type === 'warning' ? 'warning' : 'info'}` glyphMarginClassName: `fal fa-exclamation-square text-${decoration.type === 'error' ? 'danger' : decoration.type === 'warning' ? 'warning' : 'info'}`,
} },
} }
} }
if (typeOfDecoration === 'markerPerFile') { if (typeOfDecoration === 'markerPerFile') {
@ -363,8 +365,8 @@ export const EditorUI = (props: EditorUIProps) => {
), ),
options: { options: {
isWholeLine, isWholeLine,
inlineClassName: `${isWholeLine ? 'alert-info' : 'inline-class'} border-0 highlightLine${decoration.position.start.line + 1}` inlineClassName: `${isWholeLine ? 'alert-info' : 'inline-class'} border-0 highlightLine${decoration.position.start.line + 1}`,
} },
} }
} }
if (typeOfDecoration === 'lineTextPerFile') { if (typeOfDecoration === 'lineTextPerFile') {
@ -380,11 +382,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
after: { after: {
content: ` ${lineTextDecoration.content}`, content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}` inlineClassName: `${lineTextDecoration.className}`,
}, },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`, afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage hoverMessage: lineTextDecoration.hoverMessage,
} },
} }
} }
if (typeOfDecoration === 'lineTextPerFile') { if (typeOfDecoration === 'lineTextPerFile') {
@ -400,11 +402,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
after: { after: {
content: ` ${lineTextDecoration.content}`, content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}` inlineClassName: `${lineTextDecoration.className}`,
}, },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`, afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage hoverMessage: lineTextDecoration.hoverMessage,
} },
} }
} }
} }
@ -414,7 +416,7 @@ export const EditorUI = (props: EditorUIProps) => {
if (!model) if (!model)
return { return {
currentDecorations: [], currentDecorations: [],
registeredDecorations: [] registeredDecorations: [],
} }
const decorations = [] const decorations = []
const newRegisteredDecorations = [] const newRegisteredDecorations = []
@ -428,7 +430,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
return { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations), currentDecorations: model.deltaDecorations(currentDecorations, decorations),
registeredDecorations: newRegisteredDecorations registeredDecorations: newRegisteredDecorations,
} }
} }
@ -436,7 +438,7 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorModelsState[filePath]?.model const model = editorModelsState[filePath]?.model
if (!model) if (!model)
return { return {
currentDecorations: [] currentDecorations: [],
} }
const decorations = [] const decorations = []
if (registeredDecorations) { if (registeredDecorations) {
@ -447,17 +449,17 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
return { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations) currentDecorations: model.deltaDecorations(currentDecorations, decorations),
} }
} }
const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => { const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model const model = editorModelsState[filePath]?.model
if (!model) return {currentDecorations: []} if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration) const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
return { return {
currentDecorations: model.deltaDecorations([], [monacoDecoration]), currentDecorations: model.deltaDecorations([], [monacoDecoration]),
registeredDecorations: [{value: decoration, type: typeOfDecoration}] registeredDecorations: [{ value: decoration, type: typeOfDecoration }],
} }
} }
@ -478,7 +480,7 @@ export const EditorUI = (props: EditorUIProps) => {
const errorServerityMap = { const errorServerityMap = {
error: MarkerSeverity.Error, error: MarkerSeverity.Error,
warning: MarkerSeverity.Warning, warning: MarkerSeverity.Warning,
info: MarkerSeverity.Info info: MarkerSeverity.Info,
} }
if (model) { if (model) {
const markerData: monacoTypes.editor.IMarkerData = { const markerData: monacoTypes.editor.IMarkerData = {
@ -487,7 +489,7 @@ export const EditorUI = (props: EditorUIProps) => {
startColumn: (error.position.start && error.position.start.column) || 0, startColumn: (error.position.start && error.position.start.column) || 0,
endLineNumber: (error.position.end && error.position.end.line) || 0, endLineNumber: (error.position.end && error.position.end.line) || 0,
endColumn: (error.position.end && error.position.end.column) || 0, endColumn: (error.position.end && error.position.end.column) || 0,
message: error.message message: error.message,
} }
if (!allMarkersPerfile[filePath]) { if (!allMarkersPerfile[filePath]) {
allMarkersPerfile[filePath] = [] allMarkersPerfile[filePath] = []
@ -503,7 +505,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}, from: string) => { props.editorAPI.clearErrorMarkers = async (sources: string[] | { [fileName: string]: any }, from: string) => {
if (sources) { if (sources) {
for (const source of Array.isArray(sources) ? sources : Object.keys(sources)) { for (const source of Array.isArray(sources) ? sources : Object.keys(sources)) {
const filePath = source const filePath = source
@ -572,9 +574,9 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(position.lineNumber, 1, position.lineNumber, 1), range: new monacoRef.current.Range(position.lineNumber, 1, position.lineNumber, 1),
options: { options: {
isWholeLine: false, isWholeLine: false,
glyphMarginClassName: 'fas fa-circle text-info' glyphMarginClassName: 'fas fa-circle text-info',
} },
} },
] ]
) )
prevState[currentFile][position.lineNumber] = decorationIds[0] prevState[currentFile][position.lineNumber] = decorationIds[0]
@ -626,7 +628,7 @@ export const EditorUI = (props: EditorUIProps) => {
</div> </div>
</div> </div>
</div> </div>
) ),
} }
props.plugin.call('notification', 'alert', modalContent) props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true pasteCodeRef.current = true
@ -649,7 +651,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal,
], ],
run: () => { run: () => {
editor.updateOptions({fontSize: editor.getOption(51) + 1}) editor.updateOptions({fontSize: editor.getOption(51) + 1})
@ -662,7 +664,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus,
], ],
run: () => { run: () => {
editor.updateOptions({fontSize: editor.getOption(51) - 1}) editor.updateOptions({fontSize: editor.getOption(51) - 1})
@ -675,12 +677,48 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'formatting', // create a new grouping contextMenuGroupId: 'formatting', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyF monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyF,
], ],
run: async () => { run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
await props.plugin.call('codeFormatter', 'format', file) await props.plugin.call('codeFormatter', 'format', file)
} },
}
let gptGenerateDocumentationAction
const executeGptGenerateDocumentationAction = {
id: 'generateDocumentation',
label: 'Generate documentation for this function',
contextMenuOrder: 0, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
const message = `
solidity code: ${content}
Generate the documentation for the function ${currentFunction.current} using the Doxygen style syntax
`
await props.plugin.call('openaigpt', 'message', message)
},
}
let gptExplainFunctionAction
const executegptExplainFunctionAction = {
id: 'explainFunction',
label: 'Explain this function',
contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
const message = `
solidity code: ${content}
Explain the function ${currentFunction.current}
`
await props.plugin.call('openaigpt', 'message', message)
},
} }
const freeFunctionCondition = editor.createContextKey('freeFunctionCondition', false) const freeFunctionCondition = editor.createContextKey('freeFunctionCondition', false)
@ -693,10 +731,10 @@ export const EditorUI = (props: EditorUIProps) => {
precondition: 'freeFunctionCondition', precondition: 'freeFunctionCondition',
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR,
], ],
run: async () => { run: async () => {
const {nodesAtPosition} = await retrieveNodesAtPosition(props.editorAPI, props.plugin) const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin)
// find the contract and get the nodes of the contract and the base contracts and imports // find the contract and get the nodes of the contract and the base contracts and imports
if (nodesAtPosition && isArray(nodesAtPosition) && nodesAtPosition.length) { if (nodesAtPosition && isArray(nodesAtPosition) && nodesAtPosition.length) {
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction') const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
@ -709,12 +747,14 @@ export const EditorUI = (props: EditorUIProps) => {
} else { } else {
props.plugin.call('notification', 'toast', 'Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.') props.plugin.call('notification', 'toast', 'Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.')
} }
} },
} }
editor.addAction(formatAction) editor.addAction(formatAction)
editor.addAction(zoomOutAction) editor.addAction(zoomOutAction)
editor.addAction(zoominAction) editor.addAction(zoominAction)
freeFunctionAction = editor.addAction(executeFreeFunctionAction) freeFunctionAction = editor.addAction(executeFreeFunctionAction)
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
gptExplainFunctionAction = editor.addAction(executegptExplainFunctionAction)
// we have to add the command because the menu action isn't always available (see onContextMenuHandlerForFreeFunction) // we have to add the command because the menu action isn't always available (see onContextMenuHandlerForFreeFunction)
editor.addCommand(monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR, () => executeFreeFunctionAction.run()) editor.addCommand(monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR, () => executeFreeFunctionAction.run())
@ -726,17 +766,34 @@ export const EditorUI = (props: EditorUIProps) => {
freeFunctionAction.dispose() freeFunctionAction.dispose()
freeFunctionAction = null freeFunctionAction = null
} }
if (gptGenerateDocumentationAction) {
gptGenerateDocumentationAction.dispose()
gptGenerateDocumentationAction = null
}
if (gptExplainFunctionAction) {
gptExplainFunctionAction.dispose()
gptExplainFunctionAction = null
}
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
if (!file.endsWith('.sol')) { if (!file.endsWith('.sol')) {
freeFunctionCondition.set(false) freeFunctionCondition.set(false)
return return
} }
const {nodesAtPosition} = await retrieveNodesAtPosition(props.editorAPI, props.plugin) const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin)
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction') const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
if (freeFunctionNode) { if (freeFunctionNode) {
executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM` executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM`
freeFunctionAction = editor.addAction(executeFreeFunctionAction) freeFunctionAction = editor.addAction(executeFreeFunctionAction)
} }
const functionImpl = nodesAtPosition.find((node) => node.kind === 'function')
if (functionImpl) {
currentFunction.current = functionImpl.name
executeGptGenerateDocumentationAction.label = `Generate documentation for the function "${functionImpl.name}"`
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
executegptExplainFunctionAction.label = `Explain the function "${functionImpl.name}"`
gptExplainFunctionAction = editor.addAction(executegptExplainFunctionAction)
}
freeFunctionCondition.set(!!freeFunctionNode) freeFunctionCondition.set(!!freeFunctionNode)
} }
contextmenu._onContextMenu = (...args) => { contextmenu._onContextMenu = (...args) => {
@ -757,7 +814,7 @@ export const EditorUI = (props: EditorUIProps) => {
editor.revealRange(input.options.selection) editor.revealRange(input.options.selection)
editor.setPosition({ editor.setPosition({
column: input.options.selection.startColumn, column: input.options.selection.startColumn,
lineNumber: input.options.selection.startLineNumber lineNumber: input.options.selection.startLineNumber,
}) })
} }
} catch (e) { } catch (e) {
@ -775,11 +832,11 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorWillMount(monaco) { function handleEditorWillMount(monaco) {
monacoRef.current = monaco monacoRef.current = monaco
// Register a new language // Register a new language
monacoRef.current.languages.register({id: 'remix-solidity'}) monacoRef.current.languages.register({ id: 'remix-solidity' })
monacoRef.current.languages.register({id: 'remix-cairo'}) monacoRef.current.languages.register({ id: 'remix-cairo' })
monacoRef.current.languages.register({id: 'remix-zokrates'}) monacoRef.current.languages.register({ id: 'remix-zokrates' })
monacoRef.current.languages.register({id: 'remix-move'}) monacoRef.current.languages.register({ id: 'remix-move' })
monacoRef.current.languages.register({id: 'remix-circom'}) monacoRef.current.languages.register({ id: 'remix-circom' })
// Register a tokens provider for the language // Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any) monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
@ -817,7 +874,7 @@ export const EditorUI = (props: EditorUIProps) => {
beforeMount={handleEditorWillMount} beforeMount={handleEditorWillMount}
options={{ options={{
glyphMargin: true, glyphMargin: true,
readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly,
}} }}
defaultValue={defaultEditorValue} defaultValue={defaultEditorValue}
/> />

@ -67,6 +67,21 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
} }
} }
const askGtp = async () => {
try {
const content = await plugin.call('fileManager', 'readFile', editorOptions.errFile)
const message = `
solidity code: ${content}
error message: ${messageText}
explain why the error occurred and how to fix it.
`
await plugin.call('openaigpt', 'message', message)
} catch (err) {
console.error('unable to askGtp')
console.error(err)
}
}
return ( return (
<> <>
{messageText && !close && ( {messageText && !close && (
@ -82,6 +97,7 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
<i className="fas fa-times"></i> <i className="fas fa-times"></i>
</div> </div>
<CopyToClipboard content={messageText} className={` p-0 m-0 far fa-copy ${classList}`} direction={'top'} /> <CopyToClipboard content={messageText} className={` p-0 m-0 far fa-copy ${classList}`} direction={'top'} />
<span onClick={() => { askGtp() }}>ASK GPT</span>
</div> </div>
)} )}
</> </>

@ -1,14 +1,14 @@
import React, {useState, useRef, useEffect, ReactElement} from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, ReactElement } from 'react' // eslint-disable-line
import {FormattedMessage, useIntl} from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import * as semver from 'semver' import * as semver from 'semver'
import {eachOfSeries} from 'async' // eslint-disable-line import { eachOfSeries } from 'async' // eslint-disable-line
import type Web3 from 'web3' import type Web3 from 'web3'
import {canUseWorker, urlFromVersion} from '@remix-project/remix-solidity' import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity'
import {Renderer} from '@remix-ui/renderer' // eslint-disable-line import { Renderer } from '@remix-ui/renderer' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import {format} from 'util' import { format } from 'util'
import './css/style.css' import './css/style.css'
import {CustomTooltip} from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
const _paq = ((window as any)._paq = (window as any)._paq || []) // eslint-disable-line @typescript-eslint/no-explicit-any const _paq = ((window as any)._paq = (window as any)._paq || []) // eslint-disable-line @typescript-eslint/no-explicit-any
@ -44,8 +44,8 @@ interface FinalResult {
export const SolidityUnitTesting = (props: Record<string, any>) => { export const SolidityUnitTesting = (props: Record<string, any>) => {
// eslint-disable-line @typescript-eslint/no-explicit-any // eslint-disable-line @typescript-eslint/no-explicit-any
const {helper, testTab, initialPath} = props const { helper, testTab, initialPath } = props
const {testTabLogic} = testTab const { testTabLogic } = testTab
const intl = useIntl() const intl = useIntl()
@ -55,8 +55,8 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
const [disableGenerateButton, setDisableGenerateButton] = useState<boolean>(false) const [disableGenerateButton, setDisableGenerateButton] = useState<boolean>(false)
const [disableStopButton, setDisableStopButton] = useState<boolean>(true) const [disableStopButton, setDisableStopButton] = useState<boolean>(true)
const [disableRunButton, setDisableRunButton] = useState<boolean>(false) const [disableRunButton, setDisableRunButton] = useState<boolean>(false)
const [runButtonTitle, setRunButtonTitle] = useState<string>(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle1'})) const [runButtonTitle, setRunButtonTitle] = useState<string>(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle1' }))
const [stopButtonLabel, setStopButtonLabel] = useState<string>(intl.formatMessage({id: 'solidityUnitTesting.stopButtonLabel1'})) const [stopButtonLabel, setStopButtonLabel] = useState<string>(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel1' }))
const [checkSelectAll, setCheckSelectAll] = useState<boolean>(true) const [checkSelectAll, setCheckSelectAll] = useState<boolean>(true)
const [testsOutput, setTestsOutput] = useState<ReactElement[]>([]) const [testsOutput, setTestsOutput] = useState<ReactElement[]>([])
@ -179,11 +179,11 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
await updateForNewCurrent(file) await updateForNewCurrent(file)
}) })
testTab.on('solidity', 'compilerLoaded', async (version: string, license: string) => { testTab.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
const {currentVersion} = testTab.compileTab.getCurrentCompilerConfig() const { currentVersion } = testTab.compileTab.getCurrentCompilerConfig()
if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) { if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) {
setDisableRunButton(true) setDisableRunButton(true)
setRunButtonTitle(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle2'})) setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle2' }))
} }
}) })
}, []) // eslint-disable-line }, []) // eslint-disable-line
@ -272,7 +272,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
finalLogs = finalLogs + '&emsp;' + formattedLog + '\n' finalLogs = finalLogs + '&emsp;' + formattedLog + '\n'
} }
_paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log'])
testTab.call('terminal', 'logHtml', {type: 'log', value: finalLogs}) testTab.call('terminal', 'logHtml', { type: 'log', value: finalLogs })
} }
const discardHighlight = async () => { const discardHighlight = async () => {
@ -285,11 +285,11 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
const file = split[2] const file = split[2]
const parsedLocation = { const parsedLocation = {
start: parseInt(split[0]), start: parseInt(split[0]),
length: parseInt(split[1]) length: parseInt(split[1]),
} }
const locationToHighlight = testTab.offsetToLineColumnConverter.offsetToLineColumnWithContent(parsedLocation, parseInt(file), filesContent[fileName].content) const locationToHighlight = testTab.offsetToLineColumnConverter.offsetToLineColumnWithContent(parsedLocation, parseInt(file), filesContent[fileName].content)
await testTab.call('editor', 'discardHighlight') await testTab.call('editor', 'discardHighlight')
await testTab.call('editor', 'highlight', locationToHighlight, fileName, '', {focus: true}) await testTab.call('editor', 'highlight', locationToHighlight, fileName, '', { focus: true })
} }
} }
@ -346,9 +346,9 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
if (!test.rendered) { if (!test.rendered) {
let debugBtn let debugBtn
if (test.debugTxHash) { if (test.debugTxHash) {
const {web3, debugTxHash} = test const { web3, debugTxHash } = test
debugBtn = ( debugBtn = (
<div id={test.value.replaceAll(' ', '_')} className="btn border btn btn-sm ml-1" style={{cursor: 'pointer'}} onClick={() => startDebug(debugTxHash, web3)}> <div id={test.value.replaceAll(' ', '_')} className="btn border btn btn-sm ml-1" style={{ cursor: 'pointer' }} onClick={() => startDebug(debugTxHash, web3)}>
<CustomTooltip placement={'top-start'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText="Start debugging"> <CustomTooltip placement={'top-start'} tooltipClasses="text-nowrap" tooltipId="info-recorder" tooltipText="Start debugging">
<i className="fas fa-bug"></i> <i className="fas fa-bug"></i>
</CustomTooltip> </CustomTooltip>
@ -465,17 +465,17 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
if (errors && errors.errors) { if (errors && errors.errors) {
errors.errors.forEach((err: any) => { errors.errors.forEach((err: any) => {
// eslint-disable-line @typescript-eslint/no-explicit-any // eslint-disable-line @typescript-eslint/no-explicit-any
const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{type: err.severity, errorType: err.type}} /> const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} />
setTestsOutput((prevCards) => [...prevCards, errorCard]) setTestsOutput((prevCards) => [...prevCards, errorCard])
}) })
} else if (errors && Array.isArray(errors) && (errors[0].message || errors[0].formattedMessage)) { } else if (errors && Array.isArray(errors) && (errors[0].message || errors[0].formattedMessage)) {
errors.forEach((err) => { errors.forEach((err) => {
const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{type: err.severity, errorType: err.type}} /> const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} />
setTestsOutput((prevCards) => [...prevCards, errorCard]) setTestsOutput((prevCards) => [...prevCards, errorCard])
}) })
} else if (errors && !errors.errors && !Array.isArray(errors)) { } else if (errors && !errors.errors && !Array.isArray(errors)) {
// To track error like this: https://github.com/ethereum/remix/pull/1438 // To track error like this: https://github.com/ethereum/remix/pull/1438
const errorCard: ReactElement = <Renderer message={errors.formattedMessage || errors.message} plugin={testTab} opt={{type: 'error'}} /> const errorCard: ReactElement = <Renderer message={errors.formattedMessage || errors.message} plugin={testTab} opt={{ type: 'error' }} />
setTestsOutput((prevCards) => [...prevCards, errorCard]) setTestsOutput((prevCards) => [...prevCards, errorCard])
} }
} }
@ -549,7 +549,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
passed: result.totalPassing, passed: result.totalPassing,
failed: result.totalFailing, failed: result.totalFailing,
timeTaken: totalTime, timeTaken: totalTime,
rendered: false rendered: false,
} }
testsResultByFilename[filename]['summary'] = testsSummary testsResultByFilename[filename]['summary'] = testsSummary
showTestsResult() showTestsResult()
@ -569,7 +569,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
if (_errors || hasBeenStopped.current || readyTestsNumber === runningTestsNumber) { if (_errors || hasBeenStopped.current || readyTestsNumber === runningTestsNumber) {
// All tests are ready or the operation has been canceled or there was a compilation error in one of the test files. // All tests are ready or the operation has been canceled or there was a compilation error in one of the test files.
setDisableStopButton(true) setDisableStopButton(true)
setStopButtonLabel(intl.formatMessage({id: 'solidityUnitTesting.stopButtonLabel1'})) setStopButtonLabel(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel1' }))
if (selectedTests.current?.length !== 0) { if (selectedTests.current?.length !== 0) {
setDisableRunButton(false) setDisableRunButton(false)
} }
@ -588,16 +588,16 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
.readFile(testFilePath) .readFile(testFilePath)
.then(async (content: string) => { .then(async (content: string) => {
const runningTests: Record<string, Record<string, string>> = {} const runningTests: Record<string, Record<string, string>> = {}
runningTests[testFilePath] = {content} runningTests[testFilePath] = { content }
filesContent[testFilePath] = {content} filesContent[testFilePath] = { content }
const {currentVersion, evmVersion, optimize, runs, isUrl} = testTab.compileTab.getCurrentCompilerConfig() const { currentVersion, evmVersion, optimize, runs, isUrl } = testTab.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = isUrl ? currentVersion : urlFromVersion(currentVersion) const currentCompilerUrl = isUrl ? currentVersion : urlFromVersion(currentVersion)
const compilerConfig = { const compilerConfig = {
currentCompilerUrl, currentCompilerUrl,
evmVersion, evmVersion,
optimize, optimize,
usingWorker: canUseWorker(currentVersion), usingWorker: canUseWorker(currentVersion),
runs runs,
} }
const deployCb = async (file: string, contractAddress: string) => { const deployCb = async (file: string, contractAddress: string) => {
const compilerData = await testTab.call('compilerArtefacts', 'getCompilerAbstract', file) const compilerData = await testTab.call('compilerArtefacts', 'getCompilerAbstract', file)
@ -624,7 +624,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
.then((result: any) => cb(null, result)) .then((result: any) => cb(null, result))
.catch((error: Error) => cb(error.message)) // eslint-disable-line @typescript-eslint/no-explicit-any .catch((error: Error) => cb(error.message)) // eslint-disable-line @typescript-eslint/no-explicit-any
}, },
{testFilePath: testFilePath} { testFilePath: testFilePath }
) )
}) })
.catch((error: Error) => { .catch((error: Error) => {
@ -660,16 +660,16 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
if (!isSolidityActive || !selectedTests.current.length) { if (!isSolidityActive || !selectedTests.current.length) {
setDisableRunButton(true) setDisableRunButton(true)
if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) { if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) {
setRunButtonTitle(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle3'})) setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle3' }))
} else { } else {
setRunButtonTitle(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle4'})) setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle4' }))
} }
} else setDisableRunButton(false) } else setDisableRunButton(false)
} }
const stopTests = () => { const stopTests = () => {
hasBeenStopped.current = true hasBeenStopped.current = true
setStopButtonLabel(intl.formatMessage({id: 'solidityUnitTesting.stopButtonLabel2'})) setStopButtonLabel(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel2' }))
setDisableStopButton(true) setDisableStopButton(true)
setDisableRunButton(true) setDisableRunButton(true)
} }
@ -687,12 +687,12 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
setCheckSelectAll(true) setCheckSelectAll(true)
setDisableRunButton(false) setDisableRunButton(false)
if ((readyTestsNumber === runningTestsNumber || hasBeenStopped.current) && stopButtonLabel.trim() === 'Stop') { if ((readyTestsNumber === runningTestsNumber || hasBeenStopped.current) && stopButtonLabel.trim() === 'Stop') {
setRunButtonTitle(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle1'})) setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle1' }))
} }
} else if (!selectedTests.current.length) { } else if (!selectedTests.current.length) {
setCheckSelectAll(false) setCheckSelectAll(false)
setDisableRunButton(true) setDisableRunButton(true)
setRunButtonTitle(intl.formatMessage({id: 'solidityUnitTesting.runButtonTitle5'})) setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle5' }))
} else setCheckSelectAll(false) } else setCheckSelectAll(false)
} }
@ -713,7 +713,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
const updateTestFileList = () => { const updateTestFileList = () => {
if (allTests.current?.length) { if (allTests.current?.length) {
testFiles = allTests.current.map((testFile: string) => { testFiles = allTests.current.map((testFile: string) => {
return {fileName: testFile, checked: true} return { fileName: testFile, checked: true }
}) })
setCheckSelectAll(true) setCheckSelectAll(true)
} else testFiles = [] } else testFiles = []
@ -755,7 +755,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
data-id="uiPathInput" data-id="uiPathInput"
name="utPath" name="utPath"
value={inputPathValue} value={inputPathValue}
style={{backgroundImage: 'var(--primary)'}} style={{ backgroundImage: 'var(--primary)' }}
onKeyDown={() => { onKeyDown={() => {
if (inputPathValue === '/') setInputPathValue('') if (inputPathValue === '/') setInputPathValue('')
}} }}
@ -838,35 +838,45 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
</button> </button>
</CustomTooltip> </CustomTooltip>
</div> </div>
<div className="d-flex align-items-center mx-3 pb-2 mt-2 border-bottom"> <div className="d-flex align-items-center ml-1 mr-3 pl-1 pb-2 mt-2 border-bottom custom-control custom-checkbox">
<input <input
id="checkAllTests" id="checkAllTests"
className="custom-control-input"
type="checkbox" type="checkbox"
data-id="testTabCheckAllTests"
onClick={checkAll} onClick={checkAll}
checked={checkSelectAll} checked={checkSelectAll}
onChange={() => {}} // eslint-disable-line onChange={() => {}} // eslint-disable-line
/> />
<label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> <label
data-id="testTabCheckAllTests"
htmlFor="checkAllTests"
className="form-check-label mb-0 ml-4 custom-control-label text-nowrap"
style={{ paddingTop: '0.125rem' }}
>
{' '} {' '}
<FormattedMessage id="solidityUnitTesting.selectAll" />{' '} <FormattedMessage id="solidityUnitTesting.selectAll" />{' '}
</label> </label>
</div> </div>
<div className="testList py-2 mt-0 border-bottom"> <div className="testList ml-1 pr-2 mt-0 border-bottom py-2">
{testFiles.length {testFiles.length
? testFiles.map((testFileObj: TestObject, index) => { ? testFiles.map((testFileObj: TestObject, index) => {
const elemId = `singleTest${testFileObj.fileName}` const elemId = `singleTest${testFileObj.fileName}`
return ( return (
<div className="d-flex align-items-center py-1" key={index}> <div className="d-flex align-items-center pl-1 custom-control custom-checkbox" key={index}>
<input <input
data-id="singleTest" className="singleTest custom-control-input"
className="singleTest"
id={elemId} id={elemId}
onChange={(e) => toggleCheckbox(e.target.checked, index)} onChange={(e) => toggleCheckbox(e.target.checked, index)}
type="checkbox" type="checkbox"
checked={testFileObj.checked} checked={testFileObj.checked}
/> />
<label className="singleTestLabel text-nowrap pl-2 mb-0" htmlFor={elemId}> <label
data-id="singleTest"
id={"id" + elemId}
className="singleTestLabel text-nowrap mb-0 form-check-label ml-4 custom-control-label text-nowrap"
htmlFor={elemId}
style={{ paddingTop: '0.125rem' }}
>
{testFileObj.fileName} {testFileObj.fileName}
</label> </label>
</div> </div>
@ -876,7 +886,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
</div> </div>
<div className="align-items-start flex-column mt-2 mx-3 mb-0"> <div className="align-items-start flex-column mt-2 mx-3 mb-0">
<span className="text-info h6" hidden={progressBarHidden}> <span className="text-info h6" hidden={progressBarHidden}>
<FormattedMessage id="solidityUnitTesting.progress" values={{readyTestsNumber, runningTestsNumber}} /> <FormattedMessage id="solidityUnitTesting.progress" values={{ readyTestsNumber, runningTestsNumber }} />
</span> </span>
<label className="text-warning h6" data-id="testTabTestsExecutionStopped" hidden={testsExecutionStoppedHidden}> <label className="text-warning h6" data-id="testTabTestsExecutionStopped" hidden={testsExecutionStoppedHidden}>
<FormattedMessage id="solidityUnitTesting.testTabTestsExecutionStopped" /> <FormattedMessage id="solidityUnitTesting.testTabTestsExecutionStopped" />

@ -1,4 +1,4 @@
import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes' import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, TYPEWRITERLOG, TYPEWRITERWARNING, TYPEWRITERSUCCESS, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes'
export const initialState = { export const initialState = {
journalBlocks: [ journalBlocks: [
@ -151,6 +151,21 @@ export const registerScriptRunnerReducer = (state, action) => {
...state, ...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider }) journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider })
} }
case TYPEWRITERLOG:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-log', provider: action.payload.provider })
}
case TYPEWRITERWARNING:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-warning', provider: action.payload.provider })
}
case TYPEWRITERSUCCESS:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-success', provider: action.payload.provider })
}
case INFO: case INFO:
return { return {
...state, ...state,

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import React, {useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent} from 'react' // eslint-disable-line import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import {FormattedMessage, useIntl} from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { import {
registerCommandAction, registerCommandAction,
registerLogScriptRunnerAction, registerLogScriptRunnerAction,
@ -8,15 +8,15 @@ import {
registerErrorScriptRunnerAction, registerErrorScriptRunnerAction,
registerWarnScriptRunnerAction, registerWarnScriptRunnerAction,
listenOnNetworkAction, listenOnNetworkAction,
initListeningOnNetwork initListeningOnNetwork,
} from './actions/terminalAction' } from './actions/terminalAction'
import {initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer} from './reducers/terminalReducer' import { initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer'
import {getKeyOf, getValueOf, Objectfilter, matched} from './utils/utils' import { getKeyOf, getValueOf, Objectfilter, matched } from './utils/utils'
import {allCommands, allPrograms} from './commands' // eslint-disable-line import { allCommands, allPrograms } from './commands' // eslint-disable-line
import TerminalWelcomeMessage from './terminalWelcome' // eslint-disable-line import TerminalWelcomeMessage from './terminalWelcome' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import {CustomTooltip} from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import './remix-ui-terminal.css' import './remix-ui-terminal.css'
import vm from 'vm' import vm from 'vm'
@ -26,8 +26,8 @@ import RenderUnKnownTransactions from './components/RenderUnknownTransactions' /
import RenderCall from './components/RenderCall' // eslint-disable-line import RenderCall from './components/RenderCall' // eslint-disable-line
import RenderKnownTransactions from './components/RenderKnownTransactions' // eslint-disable-line import RenderKnownTransactions from './components/RenderKnownTransactions' // eslint-disable-line
import parse from 'html-react-parser' import parse from 'html-react-parser'
import {EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION} from './types/terminalTypes' import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import {wrapScript} from './utils/wrapScript' import { wrapScript } from './utils/wrapScript'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
@ -35,7 +35,7 @@ export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
} }
export const RemixUiTerminal = (props: RemixUiTerminalProps) => { export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const {call, _deps, on, config, event, version} = props.plugin const { call, _deps, on, config, event, version } = props.plugin
const [_cmdIndex, setCmdIndex] = useState(-1) const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('') const [_cmdTemp, setCmdTemp] = useState('')
const [isOpen, setIsOpen] = useState<boolean>(true) const [isOpen, setIsOpen] = useState<boolean>(true)
@ -45,7 +45,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [toaster, setToaster] = useState(false) const [toaster, setToaster] = useState(false)
const [toastProvider, setToastProvider] = useState({ const [toastProvider, setToastProvider] = useState({
show: false, show: false,
fileName: '' fileName: '',
}) })
const [modalState, setModalState] = useState({ const [modalState, setModalState] = useState({
message: '', message: '',
@ -54,7 +54,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
cancelLabel: '', cancelLabel: '',
hide: true, hide: true,
cancelFn: () => {}, cancelFn: () => {},
handleHide: () => {} handleHide: () => {},
}) })
const [clearConsole, setClearConsole] = useState(false) const [clearConsole, setClearConsole] = useState(false)
@ -63,7 +63,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [autoCompletState, setAutoCompleteState] = useState({ const [autoCompletState, setAutoCompleteState] = useState({
activeSuggestion: 0, activeSuggestion: 0,
data: { data: {
_options: [] _options: [],
}, },
_startingElement: 0, _startingElement: 0,
autoCompleteSelectedItem: {}, autoCompleteSelectedItem: {},
@ -75,7 +75,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
text: '', text: '',
userInput: '', userInput: '',
extraCommands: [], extraCommands: [],
commandHistoryIndex: 0 commandHistoryIndex: 0,
}) })
const [searchInput, setSearchInput] = useState('') const [searchInput, setSearchInput] = useState('')
@ -84,6 +84,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// terminal inputRef // terminal inputRef
const inputEl = useRef(null) const inputEl = useRef(null)
const messagesEndRef = useRef(null) const messagesEndRef = useRef(null)
const typeWriterIndexes = useRef([])
// terminal dragable // terminal dragable
const panelRef = useRef(null) const panelRef = useRef(null)
@ -92,7 +93,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const intl = useIntl() const intl = useIntl()
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current.scrollIntoView({behavior: 'smooth'}) messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
} }
useEffect(() => { useEffect(() => {
@ -101,8 +102,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
scriptRunnerDispatch({ scriptRunnerDispatch({
type: 'html', type: 'html',
payload: { payload: {
message: [html ? (html.innerText ? html.innerText : html) : null] message: [html ? (html.innerText ? html.innerText : html) : null],
} },
}) })
}, },
@ -110,14 +111,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (typeof message === 'string') { if (typeof message === 'string') {
message = { message = {
value: message, value: message,
type: 'log' type: 'log',
} }
} }
scriptRunnerDispatch({ scriptRunnerDispatch({
type: message.type ? message.type : 'log', type: message.type ? message.type : 'log',
payload: {message: [message.value]} payload: { message: [message.value] },
}) })
} },
}) })
}, []) }, [])
@ -128,22 +129,22 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch) registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch) registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch)
registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch) registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch)
registerCommandAction('html', _blocksRenderer('html'), {activate: true}, dispatch) registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch)
registerCommandAction('log', _blocksRenderer('log'), {activate: true}, dispatch) registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch)
registerCommandAction('info', _blocksRenderer('info'), {activate: true}, dispatch) registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch)
registerCommandAction('warn', _blocksRenderer('warn'), {activate: true}, dispatch) registerCommandAction('warn', _blocksRenderer('warn'), { activate: true }, dispatch)
registerCommandAction('error', _blocksRenderer('error'), {activate: true}, dispatch) registerCommandAction('error', _blocksRenderer('error'), { activate: true }, dispatch)
registerCommandAction( registerCommandAction(
'script', 'script',
function execute(args, scopedCommands) { function execute(args, scopedCommands) {
const script = String(args[0]) const script = String(args[0])
_shell(script, scopedCommands, function (error, output) { _shell(script, scopedCommands, function (error, output) {
if (error) scriptRunnerDispatch({type: 'error', payload: {message: error}}) if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } })
if (output) scriptRunnerDispatch({type: 'script', payload: {message: '5'}}) if (output) scriptRunnerDispatch({ type: 'script', payload: { message: '5' } })
}) })
}, },
{activate: true}, { activate: true },
dispatch dispatch
) )
}, [autoCompletState.text]) }, [autoCompletState.text])
@ -169,16 +170,16 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
const provider = _deps.fileManager.fileProviderOf(file) const provider = _deps.fileManager.fileProviderOf(file)
console.log({provider}) console.log({ provider })
if (!provider) { if (!provider) {
// toolTip(`provider for path ${file} not found`) // toolTip(`provider for path ${file} not found`)
setToastProvider({show: true, fileName: file}) setToastProvider({ show: true, fileName: file })
if (cb) cb() if (cb) cb()
return return
} }
provider.get(file, (error, content) => { provider.get(file, (error, content) => {
console.log({content}) console.log({ content })
if (error) { if (error) {
// toolTip(error) // toolTip(error)
// TODO: pop up // TODO: pop up
@ -213,13 +214,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}, },
execute: (fileName, callback) => { execute: (fileName, callback) => {
return execute(fileName, callback) return execute(fileName, callback)
} },
} },
} }
try { try {
const cmds = vm.createContext(context) const cmds = vm.createContext(context)
const result = vm.runInContext(script, cmds) // eslint-disable-line const result = vm.runInContext(script, cmds) // eslint-disable-line
console.log({result}) console.log({ result })
return done(null, result) return done(null, result)
} catch (error) { } catch (error) {
return done(error.message) return done(error.message)
@ -247,7 +248,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// backspace or any key that should remove the autocompletion // backspace or any key that should remove the autocompletion
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: false showSuggestions: false,
})) }))
} }
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) { if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) {
@ -256,7 +257,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
...prevState, ...prevState,
activeSuggestion: 0, activeSuggestion: 0,
showSuggestions: false, showSuggestions: false,
userInput: Object.keys(autoCompletState.data._options[0]).toString() userInput: Object.keys(autoCompletState.data._options[0]).toString(),
})) }))
} else { } else {
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) { if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) {
@ -266,14 +267,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
showSuggestions: false, showSuggestions: false,
userInput: autoCompletState.data._options[autoCompletState.activeSuggestion] userInput: autoCompletState.data._options[autoCompletState.activeSuggestion]
? Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString() ? Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString()
: inputEl.current.value : inputEl.current.value,
})) }))
} else { } else {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: 0, activeSuggestion: 0,
showSuggestions: false, showSuggestions: false,
userInput: autoCompletState.data._options.length === 1 ? Object.keys(autoCompletState.data._options[0]).toString() : inputEl.current.value userInput: autoCompletState.data._options.length === 1 ? Object.keys(autoCompletState.data._options[0]).toString() : inputEl.current.value,
})) }))
} }
} }
@ -290,22 +291,22 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setCmdTemp('') setCmdTemp('')
const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim() const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
if (script.length) { if (script.length) {
cmdHistoryDispatch({type: 'cmdHistory', payload: {script}}) cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } })
newstate.commands.script(wrapScript(script)) newstate.commands.script(wrapScript(script))
} }
setAutoCompleteState((prevState) => ({...prevState, userInput: ''})) setAutoCompleteState((prevState) => ({ ...prevState, userInput: '' }))
inputEl.current.innerText = '' inputEl.current.innerText = ''
inputEl.current.focus() inputEl.current.focus()
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: false showSuggestions: false,
})) }))
} }
} else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && autoCompletState.userInput === '') { } else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && autoCompletState.userInput === '') {
event.preventDefault() event.preventDefault()
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
userInput: newstate._commandHistory[0] userInput: newstate._commandHistory[0],
})) }))
} else if (event.which === 38 && autoCompletState.showSuggestions) { } else if (event.which === 38 && autoCompletState.showSuggestions) {
event.preventDefault() event.preventDefault()
@ -315,7 +316,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: suggestionCount - 1, activeSuggestion: suggestionCount - 1,
userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString() userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString(),
})) }))
} else if (event.which === 38 && !autoCompletState.showSuggestions) { } else if (event.which === 38 && !autoCompletState.showSuggestions) {
// <arrowUp> // <arrowUp>
@ -332,7 +333,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: suggestionCount + 1, activeSuggestion: suggestionCount + 1,
userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion + 1]).toString() userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion + 1]).toString(),
})) }))
} else if (event.which === 40 && !autoCompletState.showSuggestions) { } else if (event.which === 40 && !autoCompletState.showSuggestions) {
if (_cmdIndex > -1) { if (_cmdIndex > -1) {
@ -359,7 +360,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
log: 'text-log', log: 'text-log',
info: 'text-log', info: 'text-log',
warn: 'text-warning', warn: 'text-warning',
error: 'text-danger' error: 'text-danger',
}[mode] // defaults }[mode] // defaults
if (mode) { if (mode) {
@ -389,7 +390,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const handleClearConsole = () => { const handleClearConsole = () => {
setClearConsole(true) setClearConsole(true)
dispatch({type: 'clearconsole', payload: []}) typeWriterIndexes.current = []
dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus() inputEl.current.focus()
} }
/* start of autoComplete */ /* start of autoComplete */
@ -408,42 +410,42 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: false, showSuggestions: false,
userInput: inputString userInput: inputString,
})) }))
} else { } else {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: true, showSuggestions: true,
userInput: inputString userInput: inputString,
})) }))
} }
const textList = inputString.split('.') const textList = inputString.split('.')
if (textList.length === 1) { if (textList.length === 1) {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
data: {_options: []} data: { _options: [] },
})) }))
const result = Objectfilter(allPrograms, autoCompletState.userInput) const result = Objectfilter(allPrograms, autoCompletState.userInput)
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
data: {_options: result} data: { _options: result },
})) }))
} else { } else {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
data: {_options: []} data: { _options: [] },
})) }))
const result = Objectfilter(allCommands, autoCompletState.userInput) const result = Objectfilter(allCommands, autoCompletState.userInput)
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
data: {_options: result} data: { _options: result },
})) }))
} }
} else { } else {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: false, showSuggestions: false,
userInput: inputString userInput: inputString,
})) }))
} }
} }
@ -453,7 +455,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
showSuggestions: false, showSuggestions: false,
userInput: result userInput: result,
})) }))
inputEl.current.focus() inputEl.current.focus()
} }
@ -466,7 +468,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: suggestionCount - 1 activeSuggestion: suggestionCount - 1,
})) }))
} else if (event.keyCode === 40) { } else if (event.keyCode === 40) {
if (autoCompletState.activeSuggestion - 1 === autoCompletState.data._options.length) { if (autoCompletState.activeSuggestion - 1 === autoCompletState.data._options.length) {
@ -474,7 +476,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: suggestionCount + 1 activeSuggestion: suggestionCount + 1,
})) }))
} }
} }
@ -488,12 +490,12 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
okFn, okFn,
cancelLabel, cancelLabel,
cancelFn, cancelFn,
hide hide,
})) }))
} }
const handleHideModal = () => { const handleHideModal = () => {
setModalState((prevState) => ({...prevState, hide: true})) setModalState((prevState) => ({ ...prevState, hide: true }))
} }
const txDetails = (event, tx) => { const txDetails = (event, tx) => {
@ -515,7 +517,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
display: display:
autoCompletState.showSuggestions && autoCompletState.userInput !== '' && autoCompletState.userInput.length > 0 && autoCompletState.data._options.length > 0 autoCompletState.showSuggestions && autoCompletState.userInput !== '' && autoCompletState.userInput.length > 0 && autoCompletState.data._options.length > 0
? 'block' ? 'block'
: 'none' : 'none',
}} }}
> >
<div> <div>
@ -545,7 +547,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
activeSuggestion: 0, activeSuggestion: 0,
showSuggestions: false showSuggestions: false,
})) }))
} }
@ -572,7 +574,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const classNameBlock = 'remix_ui_terminal_block px-4 py-1 text-break' const classNameBlock = 'remix_ui_terminal_block px-4 py-1 text-break'
return ( return (
<div style={{flexGrow: 1}} className="remix_ui_terminal_panel" ref={panelRef}> <div style={{ flexGrow: 1 }} className="remix_ui_terminal_panel" ref={panelRef}>
<div className="remix_ui_terminal_bar d-flex"> <div className="remix_ui_terminal_bar d-flex">
<div className="remix_ui_terminal_menu d-flex w-100 align-items-center position-relative border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu"> <div className="remix_ui_terminal_menu d-flex w-100 align-items-center position-relative border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu">
<CustomTooltip placement="top" tooltipId="terminalToggle" tooltipClasses="text-nowrap" tooltipText={isOpen ? 'Hide Terminal' : 'Show Terminal'}> <CustomTooltip placement="top" tooltipId="terminalToggle" tooltipClasses="text-nowrap" tooltipText={isOpen ? 'Hide Terminal' : 'Show Terminal'}>
@ -590,12 +592,17 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText="Pending Transactions"> <CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText="Pending Transactions">
<div className="mx-2">0</div> <div className="mx-2">0</div>
</CustomTooltip> </CustomTooltip>
<div className="pt-1 h-80 mx-3 align-items-center remix_ui_terminal_listenOnNetwork custom-control custom-checkbox"> <div className="h-80 mx-3 align-items-center remix_ui_terminal_listenOnNetwork custom-control custom-checkbox">
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({id: 'terminal.listenTitle'})}> <CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({ id: 'terminal.listenTitle' })}>
<input className="custom-control-input" id="listenNetworkCheck" onChange={listenOnNetwork} type="checkbox" /> <input className="custom-control-input" id="listenNetworkCheck" onChange={listenOnNetwork} type="checkbox" />
</CustomTooltip> </CustomTooltip>
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({id: 'terminal.listenTitle'})}> <CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({ id: 'terminal.listenTitle' })}>
<label className="pt-1 form-check-label custom-control-label text-nowrap" htmlFor="listenNetworkCheck" data-id="listenNetworkCheckInput"> <label
className="form-check-label custom-control-label text-nowrap"
style={{ paddingTop: '0.125rem' }}
htmlFor="listenNetworkCheck"
data-id="listenNetworkCheckInput"
>
<FormattedMessage id="terminal.listen" /> <FormattedMessage id="terminal.listen" />
</label> </label>
</CustomTooltip> </CustomTooltip>
@ -607,7 +614,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
type="text" type="text"
className="remix_ui_terminal_filter border form-control" className="remix_ui_terminal_filter border form-control"
id="searchInput" id="searchInput"
placeholder={intl.formatMessage({id: 'terminal.search'})} placeholder={intl.formatMessage({ id: 'terminal.search' })}
data-id="terminalInputSearch" data-id="terminalInputSearch"
/> />
</div> </div>
@ -718,21 +725,36 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
) )
} else { } else {
return ( // typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
<div className={classNameBlock} data-id="block" key={i}> if (x.typewriter && !typeWriterIndexes.current.includes(index)) {
<span className={x.style}>{msg ? msg.toString() : null}</span> typeWriterIndexes.current.push(index)
</div> return (
) <div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
typewrite(element, msg ? msg.toString() : null)
}} className={x.style}></span></div>
)
} else {
return (
<div className={classNameBlock} data-id="block" key={i}><span className={x.style}>{msg ? msg.toString() : null}</span></div>
)
}
} }
}) })
} else { } else {
if (typeof x.message !== 'function') { // typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
if (x.typewriter && !typeWriterIndexes.current.includes(index)) {
typeWriterIndexes.current.push(index)
return ( return (
<div className={classNameBlock} data-id="block" key={index}> <div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
{' '} typewrite(element, x.message)
<span className={x.style}> {x.message}</span> }} className={x.style}></span></div>
</div>
) )
} else {
if (typeof x.message !== 'function') {
return (
<div className={classNameBlock} data-id="block" key={index}> <span className={x.style}> {x.message}</span></div>
)
}
} }
} }
})} })}
@ -773,7 +795,18 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
} }
function isHtml(value) { const typewrite = (elementsRef, message) => {
(() => {
let count = 0
const id = setInterval(() => {
count++
elementsRef.innerText = message.substr(0, count)
if (message === count) clearInterval(id)
}, 5)
})()
}
function isHtml (value) {
if (!value.indexOf) return false if (!value.indexOf) return false
return value.indexOf('<div') !== -1 || value.indexOf('<span') !== -1 || value.indexOf('<p') !== -1 || value.indexOf('<label') !== -1 || value.indexOf('<b') !== -1 return value.indexOf('<div') !== -1 || value.indexOf('<span') !== -1 || value.indexOf('<p') !== -1 || value.indexOf('<label') !== -1 || value.indexOf('<b') !== -1
} }

@ -15,6 +15,9 @@ export const NEW_CALL = 'newCall'
export const HTML = 'html' export const HTML = 'html'
export const LOG = 'log' export const LOG = 'log'
export const TYPEWRITERLOG = 'typewriterlog'
export const TYPEWRITERWARNING = 'typewriterwarning'
export const TYPEWRITERSUCCESS = 'typewritersuccess'
export const INFO = 'info' export const INFO = 'info'
export const WARN = 'warn' export const WARN = 'warn'
export const ERROR = 'error' export const ERROR = 'error'

@ -178,6 +178,7 @@
"latest-version": "^5.1.0", "latest-version": "^5.1.0",
"merge": "^2.1.1", "merge": "^2.1.1",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"openai": "^3.3.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"prettier-plugin-solidity": "^1.0.0-beta.24", "prettier-plugin-solidity": "^1.0.0-beta.24",

@ -7812,6 +7812,13 @@ axios@^0.21.1:
dependencies: dependencies:
follow-redirects "^1.14.0" follow-redirects "^1.14.0"
axios@^0.26.0:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
axios@^1.4.0: axios@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
@ -21152,6 +21159,14 @@ open@^8.0.9, open@^8.4.0:
is-docker "^2.1.1" is-docker "^2.1.1"
is-wsl "^2.2.0" is-wsl "^2.2.0"
openai@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532"
integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==
dependencies:
axios "^0.26.0"
form-data "^4.0.0"
opener@^1.5.1, opener@^1.5.2: opener@^1.5.1, opener@^1.5.2:
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"

Loading…
Cancel
Save