Merge pull request #2949 from ethereum/custom-tooltips-debugger

Custom Tooltips for Debugger
pull/3040/head^2
Joseph Izang 2 years ago committed by GitHub
commit 6f90bb099d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  2. 26
      libs/remix-ui/debugger-ui/src/lib/button-navigator/button-navigator.css
  3. 115
      libs/remix-ui/debugger-ui/src/lib/button-navigator/button-navigator.tsx
  4. 20
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  5. 32
      libs/remix-ui/debugger-ui/src/lib/tx-browser/tx-browser.tsx
  6. 13
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx

@ -191,14 +191,14 @@ module.exports = {
.clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' }) .clickFunction('f - transact (not payable)', { types: 'uint256[] ', values: '[]' })
.debugTransaction(0) .debugTransaction(0)
.pause(2000) .pause(2000)
.click('*[data-id="debuggerTransactionStartButton"]') // stop debugging .click('*[id="debuggerTransactionStartButtonContainer"]') // stop debugging
.click('*[data-id="debugGeneratedSourcesLabel"]') // select debug with generated sources .click('*[data-id="debugGeneratedSourcesLabel"]') // select debug with generated sources
.click('*[data-id="debuggerTransactionStartButton"]') // start debugging .click('*[id="debuggerTransactionStartButtonContainer"]') // start debugging
.pause(2000) .pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32)') !== -1, 'current displayed content is not a generated source') browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32)') !== -1, 'current displayed content is not a generated source')
}) })
.click('*[data-id="debuggerTransactionStartButton"]') .click('*[id="debuggerTransactionStartButtonContainer"]')
}, },
// depends on Should debug using generated sources // depends on Should debug using generated sources
'Should call the debugger api: getTrace #group4': function (browser: NightwatchBrowser) { 'Should call the debugger api: getTrace #group4': function (browser: NightwatchBrowser) {

@ -6,16 +6,42 @@
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
color: #fff;
} }
.stepButton { .stepButton {
} }
.stepButtonDisabled {
background-color: #005573;
border-color: #005573;
}
.stepButton:hover {
color: #fff;
background-color: #005573;
border-color: #005573;
}
.jumpButtons { .jumpButtons {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
color: #fff;
} }
.jumpButton { .jumpButton {
} }
.jumpButtonDisabled {
background-color: #005573;
border-color: #005573;
}
.jumButton:hover {
color: #fff;
background-color: #005573;
border-color: #005573;
}
.navigator { .navigator {
} }
.navigator:hover { .navigator:hover {

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, { useState, useEffect } from 'react' // eslint-disable-line
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import './button-navigator.css' import './button-navigator.css'
export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward, stepOverForward, jumpOut, jumpPreviousBreakpoint, jumpNextBreakpoint, jumpToException, revertedReason, stepState, jumpOutDisabled }) => { export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward, stepOverForward, jumpOut, jumpPreviousBreakpoint, jumpNextBreakpoint, jumpToException, revertedReason, stepState, jumpOutDisabled }) => {
@ -50,22 +51,120 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
}) })
} }
const stepBtnStyle = 'd-flex align-items-center justify-content-center btn btn-primary btn-sm stepButton h-75 m-0 p-1'
const disableStepBtnStyle = 'stepButtonDisabled'
const jumpBtnStyle = 'd-flex align-items-center justify-content-center btn btn-primary btn-sm jumpButton h-75 m-0 p-1'
const disableJumpBtnStyle = 'jumpButtonDisabled'
return ( return (
<div className="buttons"> <div className="buttons">
<div className="stepButtons btn-group py-1"> <div className="stepButtons btn-group py-1">
<button id='overback' className='btn btn-primary btn-sm navigator stepButton fas fa-reply' title='Step over back' onClick={() => { stepOverBack && stepOverBack() }} disabled={state.overBackDisabled} ></button> <OverlayTrigger
<button id='intoback' data-id="buttonNavigatorIntoBack" className='btn btn-primary btn-sm navigator stepButton fas fa-level-up-alt' title='Step back' onClick={() => { stepIntoBack && stepIntoBack() }} disabled={state.intoBackDisabled}></button> placement="top-start"
<button id='intoforward' data-id="buttonNavigatorIntoForward" className='btn btn-primary btn-sm navigator stepButton fas fa-level-down-alt' title='Step into' onClick={() => { stepIntoForward && stepIntoForward() }} disabled={state.intoForwardDisabled}></button> overlay={
<button id='overforward' className='btn btn-primary btn-sm navigator stepButton fas fa-share' title='Step over forward' onClick={() => { stepOverForward && stepOverForward() }} disabled={state.overForwardDisabled}></button> <Tooltip className="text-nowrap" id="overbackTooltip">
<span>Step over back</span>
</Tooltip>
}
>
<div className={state.overBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverBack && stepOverBack() }}>
<button id='overback' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverBack && stepOverBack() }} disabled={state.overBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}>
<span className="fas fa-reply"></span>
</button>
</div>
</OverlayTrigger>
<OverlayTrigger
placement="top-start"
overlay={
<Tooltip className="text-nowrap" id="intobackTooltip">
<span>Step back</span>
</Tooltip>
}
>
<div className={state.intoBackDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoBack && stepIntoBack() }} data-id="buttonNavigatorIntoBack" id="buttonNavigatorIntoBackContainer">
<button id='intoback' data-id="buttonNavigatorIntoBack" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoBack && stepIntoBack() }} disabled={state.intoBackDisabled} style={{ pointerEvents: 'none', color: 'white' }}>
<span className="fas fa-level-up-alt"></span>
</button>
</div>
</OverlayTrigger>
<OverlayTrigger
placement="top-start"
overlay={
<Tooltip className="text-nowrap" id="intoforwardTooltip">
<span>Step into</span>
</Tooltip>
}
>
<div className={state.intoForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepIntoForward && stepIntoForward() }} data-id="buttonNavigatorIntoForward" id="buttonNavigatorIntoFowardContainer">
<button id='intoforward' data-id="buttonNavigatorIntoForward" className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepIntoForward && stepIntoForward() }} disabled={state.intoForwardDisabled}
style={{ pointerEvents: 'none', color: 'white' }}
>
<span className="fas fa-level-down-alt"></span>
</button>
</div>
</OverlayTrigger>
<OverlayTrigger
placement="top-end"
overlay={
<Tooltip className="text-nowrap" id="overforwardTooltip">
<span>Step over forward</span>
</Tooltip>
}
>
<div className={state.overForwardDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { stepOverForward && stepOverForward() }} data-id="buttonNavigatorOverForward" id="buttonNavigatorOverForwardContainer">
<button id='overforward' className='btn btn-link btn-sm stepButton m-0 p-0' onClick={() => { stepOverForward && stepOverForward() }} disabled={state.overForwardDisabled} style={{ pointerEvents: 'none', color: 'white' }}>
<span className="fas fa-share"></span>
</button>
</div>
</OverlayTrigger>
</div> </div>
<div className="jumpButtons btn-group py-1"> <div className="jumpButtons btn-group py-1">
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-step-backward' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" title='Jump to the previous breakpoint' onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} disabled={state.jumpPreviousBreakpointDisabled}></button> <OverlayTrigger
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-eject' id='jumpout' title='Jump out' onClick={() => { jumpOut && jumpOut() }} disabled={state.jumpOutDisabled}></button> placement="top-start"
<button className='btn btn-primary btn-sm navigator jumpButton fas fa-step-forward' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" title='Jump to the next breakpoint' onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} disabled={state.jumpNextBreakpointDisabled}></button> overlay={
<Tooltip id="jumppreviousbreakpointTooltip" className="text-nowrap">
<span>{'Jump to the previous breakpoint'}</span>
</Tooltip>
}
>
<div className={state.jumpPreviousBreakpointDisabled ? `${stepBtnStyle} ${disableJumpBtnStyle}`: `${stepBtnStyle}`} id="buttonNavigatorJumpPreviousBreakpointContainer" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} data-id="buttonNavigatorJumpPreviousBreakpoint">
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" onClick={() => { jumpPreviousBreakpoint && jumpPreviousBreakpoint() }} disabled={state.jumpPreviousBreakpointDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }}>
<span className="fas fa-step-backward"></span>
</button>
</div>
</OverlayTrigger>
<OverlayTrigger
placement="top-end"
overlay={
<Tooltip id="jumpoutTooltip" className="text-nowrap">
<span>{'Jump out'}</span>
</Tooltip>
}
>
<div className={state.jumpOutDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpOut && jumpOut() }} data-id="buttonNavigatorJumpOut" id="buttonNavigatorJumpOutContainer">
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpout' onClick={() => { jumpOut && jumpOut() }} disabled={state.jumpOutDisabled} style={{ pointerEvents: 'none', backgroundColor: 'inherit', color: 'white' }} data-id="buttonNavigatorJumpOut">
<span className="fas fa-eject"></span>
</button>
</div>
</OverlayTrigger>
<OverlayTrigger
placement="top-end"
overlay={
<Tooltip id="jumpnextbreakpointTooltip" className="text-nowrap">
<span>{'Jump to the next breakpoint'}</span>
</Tooltip>
}
>
<div className={state.jumpNextBreakpointDisabled ? `${stepBtnStyle} ${disableStepBtnStyle}`: `${stepBtnStyle}`} onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} data-id="buttonNavigatorJumpNextBreakpoint" id="buttonNavigatorJumpNextBreakpointContainer">
<button className='btn btn-link btn-sm jumpButton m-0 p-0' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" onClick={() => { jumpNextBreakpoint && jumpNextBreakpoint() }} disabled={state.jumpNextBreakpointDisabled} style={{ pointerEvents: 'none', color: 'white' }}>
<span className="fas fa-step-forward"></span>
</button>
</div>
</OverlayTrigger>
</div> </div>
<div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}> <div id='reverted' style={{ display: revertedReason === '' ? 'none' : 'block' }}>
<span className='text-warning'>This call has reverted, state changes made during the call will be reverted.</span> <span className='text-warning'>This call has reverted, state changes made during the call will be reverted.</span>
<span className='text-warning' id='outofgas' style={{ display: revertedReason === 'outofgas' ? 'inline' : 'none' }}>This call will run out of gas.</span> <span className='text-warning' id='outofgas' style={{ display: revertedReason === 'outofgas' ? 'inline' : 'none' }}>This call will run out of gas.</span>
<span className='text-warning' id='parenthasthrown' style={{ display: revertedReason === 'parenthasthrown' ? 'inline' : 'none' }}>The parent call will throw an exception</span> <span className='text-warning' id='parenthasthrown' style={{ display: revertedReason === 'parenthasthrown' ? 'inline' : 'none' }}>The parent call will throw an exception</span>
<div className='text-warning'>Click <u data-id="debugGoToRevert" className="cursorPointerRemixDebugger" role="button" onClick={() => { jumpToException && jumpToException() }}>here</u> to jump where the call reverted.</div> <div className='text-warning'>Click <u data-id="debugGoToRevert" className="cursorPointerRemixDebugger" role="button" onClick={() => { jumpToException && jumpToException() }}>here</u> to jump where the call reverted.</div>

@ -9,6 +9,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import { isValidHash } from '@remix-ui/helper' import { isValidHash } from '@remix-ui/helper'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
import './debugger-ui.css' import './debugger-ui.css'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
const _paq = (window as any)._paq = (window as any)._paq || [] const _paq = (window as any)._paq = (window as any)._paq || []
export const DebuggerUI = (props: DebuggerUIProps) => { export const DebuggerUI = (props: DebuggerUIProps) => {
@ -121,7 +122,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}) })
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address) => { debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address) => {
if (!lineColumnPos) { if (!lineColumnPos) {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState(prevState => {
return { ...prevState, sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.' } return { ...prevState, sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.' }
@ -351,12 +352,21 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="px-2" ref={debuggerTopRef}> <div className="px-2" ref={debuggerTopRef}>
<div> <div>
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox"> <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => { <OverlayTrigger overlay={
<Tooltip id="debuggerGenSourceCheckbox">
<span>{"Debug with generated sources"}</span>
</Tooltip>
} placement="top-start"
>
<span className="p-0 m-0">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => { setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } } return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
}) })
}} type="checkbox" title="Debug with generated sources" /> }} type="checkbox" />
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (Solidity {'>='} v0.7.2)</label> <label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (Solidity {'>='} v0.7.2)</label>
</span>
</OverlayTrigger>
</div> </div>
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox"> { state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugWithLocalNodeInput" onChange={({ target: { checked } }) => { <input className="custom-control-input" id="debugWithLocalNodeInput" onChange={({ target: { checked } }) => {
@ -371,11 +381,11 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
</div> </div>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } /> <TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } />
{ state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> } { state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> }
{ !state.debugging && { !state.debugging &&
<div> <div>
<i className="fas fa-info-triangle" aria-hidden="true"></i> <i className="fas fa-info-triangle" aria-hidden="true"></i>
<span> <span>
When Debugging with a transaction hash, When Debugging with a transaction hash,
if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings. if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings.
For supported networks, please see: <a href="https://sourcify.dev" target="__blank" >https://sourcify.dev</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a> For supported networks, please see: <a href="https://sourcify.dev" target="__blank" >https://sourcify.dev</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a>
</span> </span>

@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from 'react' //eslint-disable-line import React, { useState, useEffect, useRef } from 'react' //eslint-disable-line
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import './tx-browser.css' import './tx-browser.css'
export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, transactionNumber, debugging }) => { export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, transactionNumber, debugging }) => {
@ -64,16 +65,29 @@ export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, t
/> />
</div> </div>
<div className='d-flex justify-content-center w-100 btn-group py-1'> <div className='d-flex justify-content-center w-100 btn-group py-1'>
<button <OverlayTrigger
className='btn btn-primary btn-sm txbutton' placement={'bottom'}
id='load' overlay={
title={debugging ? 'Stop debugging' : 'Start debugging'} <Tooltip className={'text-nowrap'} id={'debuggingButtontooltip'}>
onClick={handleSubmit} <span>
data-id='debuggerTransactionStartButton' {debugging ? 'Stop debugging' : 'Start debugging'}
disabled={!state.txNumber } </span>
</Tooltip>
}
> >
{ debugging ? 'Stop' : 'Start' } debugging <div id="debuggerTransactionStartButtonContainer" data-id="debuggerTransactionStartButton" onClick={handleSubmit} className="btn btn-primary btn-sm btn-block text-decoration-none">
</button> <button
className='btn btn-link btn-sm btn-block h-75 p-0 m-0 text-decoration-none'
id='load'
onClick={handleSubmit}
data-id='debuggerTransactionStartButton'
disabled={!state.txNumber }
style={{ pointerEvents: 'none', color: 'white' }}
>
<span>{ debugging ? 'Stop' : 'Start' } debugging</span>
</button>
</div>
</OverlayTrigger>
</div> </div>
</div> </div>
<span id='error' /> <span id='error' />

@ -31,7 +31,18 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
<h6 className="mb-3" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6> <h6 className="mb-3" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="d-flex flex-row"> <div className="d-flex flex-row">
<div className="d-flex flex-row"> <div className="d-flex flex-row">
{plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (<i aria-hidden="true" className="text-success mt-1 px-1 fas fa-check" title="Maintained by Remix"></i>)} {plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="maintainedByTooltip" className="text-nowrap">
<span>{"Maintained by Remix"}</span>
</Tooltip>
}
>
<i aria-hidden="true" className="text-success mt-1 px-1 fas fa-check"></i>
</OverlayTrigger>
)}
</div> </div>
<OverlayTrigger overlay={ <OverlayTrigger overlay={
<Tooltip className="text-nowrap" id="pluginInfoTooltip"> <Tooltip className="text-nowrap" id="pluginInfoTooltip">

Loading…
Cancel
Save