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. 125
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  7. 16
      libs/remix-ui/renderer/src/lib/renderer.tsx
  8. 32
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  9. 17
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  10. 119
      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',

@ -24,7 +24,7 @@ enum MarkerSeverity {
Hint = 1, Hint = 1,
Info = 2, Info = 2,
Warning = 4, Warning = 4,
Error = 8 Error = 8,
} }
type sourceAnnotation = { type sourceAnnotation = {
@ -109,6 +109,7 @@ export type EditorAPIType = {
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
@ -275,7 +277,7 @@ export const EditorUI = (props: EditorUIProps) => {
// 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,7 +449,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
return { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations) currentDecorations: model.deltaDecorations(currentDecorations, decorations),
} }
} }
@ -457,7 +459,7 @@ export const EditorUI = (props: EditorUIProps) => {
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] = []
@ -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,7 +731,7 @@ 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)
@ -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,6 +766,15 @@ 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)
@ -737,6 +786,14 @@ export const EditorUI = (props: EditorUIProps) => {
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) {
@ -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>
)} )}
</> </>

@ -285,7 +285,7 @@ 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')
@ -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()
@ -597,7 +597,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
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)
@ -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>

@ -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,

@ -8,7 +8,7 @@ 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'
@ -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)
@ -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] },
}) })
} },
}) })
}, []) }, [])
@ -213,8 +214,8 @@ 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)
@ -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,
})) }))
} }
} }
@ -298,14 +299,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
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,6 +390,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const handleClearConsole = () => { const handleClearConsole = () => {
setClearConsole(true) setClearConsole(true)
typeWriterIndexes.current = []
dispatch({ type: 'clearconsole', payload: [] }) dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus() inputEl.current.focus()
} }
@ -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,7 +490,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
okFn, okFn,
cancelLabel, cancelLabel,
cancelFn, cancelFn,
hide hide,
})) }))
} }
@ -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,
})) }))
} }
@ -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>
@ -718,23 +725,38 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
) )
} else { } else {
// 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={i}> <div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
<span className={x.style}>{msg ? msg.toString() : null}</span> typewrite(element, msg ? msg.toString() : null)
</div> }} 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 {
// 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 (
<div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
typewrite(element, x.message)
}} className={x.style}></span></div>
)
} else { } else {
if (typeof x.message !== 'function') { if (typeof x.message !== 'function') {
return ( return (
<div className={classNameBlock} data-id="block" key={index}> <div className={classNameBlock} data-id="block" key={index}> <span className={x.style}> {x.message}</span></div>
{' '}
<span className={x.style}> {x.message}</span>
</div>
) )
} }
} }
}
})} })}
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</div> </div>
@ -773,6 +795,17 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
} }
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) { 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