Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b182724
feat: add AI copilot toggle and explain button in bottom bar
hsy822 Jul 7, 2025
f9b44be
feat: dynamic compile/run script dropdown menus
hsy822 Jul 8, 2025
aa6fd36
chore(tabs): analyze CompileDropdown plugin call requirements
hsy822 Jul 9, 2025
c76e0ff
chore: push in-progress changes for CompileDropdown storage flow
hsy822 Jul 10, 2025
b215b03
WIP: add publish to storage logic
hsy822 Jul 14, 2025
55dd7e8
feat: implemented run script dropdown
hsy822 Jul 14, 2025
3ae9da3
update css for bottom-bar
hsy822 Jul 15, 2025
5bc7c10
update compile widget css
hsy822 Jul 16, 2025
43f6dfb
update CSS for detailed layout and visual polish
hsy822 Jul 17, 2025
fdc6352
simple E2E test for compile widget
yann300 Jul 17, 2025
89d975e
feat(e2e): Add & enhance copilot toggle and compile widget tests
hsy822 Jul 21, 2025
9a59329
linting and fixing E2E
yann300 Jul 21, 2025
3a6ccc3
fix linting & E2E
yann300 Jul 21, 2025
1045130
refactor: Centralize SolidityScan logic into a shared helper
hsy822 Jul 22, 2025
65a5de9
fix(tabs): Update compile status based on 'compilationFinished' event
hsy822 Jul 22, 2025
68dd754
fix(tabs): Ensure dropdown actions await compilation
hsy822 Jul 22, 2025
2370256
fix displaying terminal when minimized
yann300 Jul 22, 2025
7324a07
fix linting
yann300 Jul 22, 2025
3e1c80a
Refactor: Consolidate file fetching & clean up comments
hsy822 Jul 22, 2025
5d592db
Merge remote-tracking branch 'upstream/feature/new-code-editor-ux' in…
hsy822 Jul 22, 2025
8641724
Merge branch 'master' into feature/new-code-editor-ux
hsy822 Jul 22, 2025
9730bc1
fix tests
yann300 Jul 22, 2025
69f1b2b
Merge branch 'master' into feature/new-code-editor-ux
yann300 Jul 23, 2025
6f123d1
fix getting active tab
Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions apps/remix-ide-e2e/src/tests/bottom-bar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict'

import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'

module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return []
},

'Should toggle AI Copilot status in the bottom bar (Simplified)': function (browser: NightwatchBrowser) {
const statusTextSelector = '[data-id="remixui_status_bottom_bar"] span.small';
const toggleInputSelector = '[data-id="copilot_toggle"]';

browser
.waitForElementVisible('[data-id="remixui_status_bottom_bar"]', 5000)
.waitForElementContainsText(statusTextSelector, 'RemixAI Copilot', 1000)
.perform((done) => {
browser.getText(statusTextSelector, (result) => {
const currentStatusText = result.value as string
const isCurrentlyDisabled = currentStatusText.includes('(disabled)')
const expectedStatusAfterToggle = isCurrentlyDisabled ? '(enabled)' : '(disabled)'
browser.click(toggleInputSelector)
.waitForElementContainsText(statusTextSelector, expectedStatusAfterToggle, 10000)
done()
});
});
}
}
10 changes: 5 additions & 5 deletions apps/remix-ide-e2e/src/tests/circom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module.exports = {
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Everything went okay')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js"]')
.openFile('circuits/.bin/simple_js')
Expand Down Expand Up @@ -204,7 +204,7 @@ module.exports = {
.waitForElementVisible('[data-path="Hash Checker - 1/scripts/groth16/groth16_trusted_setup.ts"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(10000)
.journalLastChildIncludes('newZkey')
.pause(25000)
Expand All @@ -218,7 +218,7 @@ module.exports = {
.waitForElementVisible('[data-path="Hash Checker - 1/scripts/groth16/groth16_zkproof.ts"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(2000)
.journalLastChildIncludes('Compiling circuits/calculate_hash.circom')
.pause(5000)
Expand All @@ -238,7 +238,7 @@ module.exports = {
.waitForElementVisible('[data-path="Hash Checker - 1/scripts/plonk/plonk_trusted_setup.ts"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(7000)
.journalLastChildIncludes('plonk setup')
.pause(10000)
Expand All @@ -252,7 +252,7 @@ module.exports = {
.waitForElementVisible('[data-path="Hash Checker - 1/scripts/plonk/plonk_zkproof.ts"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(2000)
.journalLastChildIncludes('Compiling circuits/calculate_hash.circom')
.pause(5000)
Expand Down
61 changes: 61 additions & 0 deletions apps/remix-ide-e2e/src/tests/compile_run_widget.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict'

import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'

module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return []
},

'Compile using the widget #group1': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-action"]')
.waitForElementVisible('[data-id="compile_group"] i.fa-check', 10000)
.verifyContracts(['Ballot'])
},

'Run script using the widget #group2': function (browser: NightwatchBrowser) {
browser
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="compile-action"]')
.waitForElementVisible('[data-id="compile_group"] i.fa-check', 10000)
},

'Should activate Solidity Static Analysis and show "Solidity Analyzers" title from compile dropdown #group3': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-dropdown-trigger"]')
.waitForElementVisible('[data-id="compile-dropdown-panel"]', 5000)
.click('[data-id="compile-run-analysis-menu-item"]')
.waitForElementVisible('[data-id="compile-run-analysis-menu-item-panel"]', 5000)
.click('[data-id="run-remix-analysis-submenu-item"]')
.waitForElementVisible('#icon-panel div[plugin="solidityStaticAnalysis"]', 10000)
.waitForElementVisible('[data-id="sidePanelSwapitTitle"]', 5000)
.assert.textContains('[data-id="sidePanelSwapitTitle"]', 'SOLIDITY ANALYZERS', 'Solidity Analyzers title should be visible.')
.waitForElementVisible('#side-panel', 5000)
.verifyContracts(['Ballot'])
},

'Should run Solidity Scan and display results in terminal #group4': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-dropdown-trigger"]')
.waitForElementVisible('[data-id="compile-dropdown-panel"]', 5000)
.click('[data-id="compile-run-analysis-menu-item"]')
.waitForElementVisible('[data-id="compile-run-analysis-menu-item-panel"]', 5000)
.click('[data-id="run-solidity-scan-submenu-item"]')
.waitForElementVisible('[data-id="SolidityScanPermissionHandlerModalDialogModalTitle-react"]', 10000)
.waitForElementVisible('[data-id="SolidityScanPermissionHandler-modal-footer-ok-react"]', 5000)
.click('[data-id="SolidityScanPermissionHandler-modal-footer-ok-react"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Scan Summary:', 30000)
.verifyContracts(['Ballot'])
}


}
2 changes: 1 addition & 1 deletion apps/remix-ide-e2e/src/tests/etherscan_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const tests = {
.frameParent()
.clickLaunchIcon('filePanel')
.addFile('receiptStatusScript.ts', { content: receiptStatusScript })
.click('*[data-id="play-editor"]') // run the script
.click('*[data-id="compile-action"]') // run the script
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Already Verified', 60000)
}
}
Expand Down
6 changes: 3 additions & 3 deletions apps/remix-ide-e2e/src/tests/file_decorator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {
.openFile('contracts/3_Ballot.sol')
.addFile('scripts/decorators.ts', { content: testScriptSet })
.pause(2000)
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(4000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
Expand All @@ -37,15 +37,15 @@ module.exports = {
.useCss()
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot })
.pause(2000)
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
},
'clear all decorators': function (browser: NightwatchBrowser) {
browser
.addFile('scripts/clearall.ts', { content: testScriptClear })
.pause(2000)
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000)
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000)
Expand Down
4 changes: 2 additions & 2 deletions apps/remix-ide-e2e/src/tests/noir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
'Should compile a simple circuit using editor play button #group1': '' + function (browser: NightwatchBrowser) {
browser
.openFile('src/main.nr')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.clickLaunchIcon('noir-compiler')
.waitForElementPresent('[data-id="view-noir-compilation-result"]')
.click('[data-id="view-noir-compilation-result"]')
Expand All @@ -37,7 +37,7 @@ module.exports = {
browser
.clickLaunchIcon('filePanel')
.openFile('tests/multiplier.test.ts')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', ' CHECK PROOF ', 60000)
}
}
4 changes: 2 additions & 2 deletions apps/remix-ide-e2e/src/tests/providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ module.exports = {
.addFile('testScript.ts', { content: testScript })
.clearConsole()
.pause(10000)
.waitForElementVisible('*[data-id="play-editor"]')
.click('*[data-id="play-editor"]')
.waitForElementVisible('*[data-id="compile-action"]')
.click('*[data-id="compile-action"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-danger' and contains(., 'exceed maximum block range')]"
Expand Down
2 changes: 1 addition & 1 deletion apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="play-editor"]')
.click('[data-id="compile-action"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'address:')
.openFile('.states/vm-london/state.json')
.waitForElementPresent('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
Expand Down
99 changes: 99 additions & 0 deletions apps/remix-ide/src/app/components/bottom-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useState, useEffect } from 'react'
import { Plugin } from '@remixproject/engine'
import './styles/bottom-bar.css'

interface BottomBarProps {
plugin: Plugin
}

export const BottomBar = ({ plugin }: BottomBarProps) => {
const [explaining, setExplaining] = useState(false)
const [aiSwitch, setAiSwitch] = useState(true)
const [currentExt, setCurrentExt] = useState('')

useEffect(() => {
const getAI = async () => {
try {
const initState = await plugin.call('settings', 'getCopilotSetting')
setAiSwitch(initState ?? true)
} catch (err) {
console.error('Failed to get copilot setting', err)
}
}

const getCurrentExt = async () => {
try {
const path = await plugin.call('fileManager', 'getCurrentFile')
const ext = path?.split('.').pop()?.toLowerCase() || ''
setCurrentExt(ext)
} catch (err) {
console.error('Failed to get current file', err)
setCurrentExt('')
}
}

getAI()
getCurrentExt()

plugin.on('fileManager', 'currentFileChanged', getCurrentExt)

return () => {
plugin.off('fileManager', 'currentFileChanged')
}

}, [plugin])

const handleExplain = async () => {
setExplaining(true)
try {
await plugin.call('menuicons', 'select', 'remixaiassistant')
await new Promise(resolve => setTimeout(resolve, 500))
const path = await plugin.call('fileManager', 'getCurrentFile')
const content = await plugin.call('fileManager', 'readFile', path)
await plugin.call('remixAI', 'chatPipe', 'code_explaining', content)

} catch (err) {
console.error("Explain failed:", err)
}
setExplaining(false)
}

const toggleAI = async () => {
try {
await plugin.call('settings', 'updateCopilotChoice', !aiSwitch)
setAiSwitch(!aiSwitch)
} catch (err) {
console.error('Failed to toggle AI copilot', err)
}
}

const getExplainLabel = () => {
if (['sol', 'vy', 'circom'].includes(currentExt)) return 'Explain contract'
if (['js', 'ts'].includes(currentExt)) return 'Explain script'
return ''
}

return (
<div className="bottom-bar border-top border-bottom">
{getExplainLabel() && (
<button
className="btn explain-btn"
onClick={handleExplain}
disabled={explaining}
>
<img src="assets/img/remixAI_small.svg" alt="Remix AI" className="explain-icon" />
<span>{getExplainLabel()}</span>
</button>
)}
<div className="copilot-toggle">
<span className={aiSwitch ? "on" : ""}>AI copilot</span>
<label className="switch" data-id="copilot_toggle" >
<input type="checkbox" checked={aiSwitch} onChange={toggleAI}/>
<span className="slider"></span>
</label>
</div>
</div>
)
}

export default BottomBar
Loading