remove uneeded code

pull/1897/head
yann300 3 years ago
parent 5b6c1444b3
commit 629bbaa821
  1. 194
      apps/remix-ide/src/app/editor/contextView.js
  2. 2
      apps/remix-ide/src/app/panels/terminal.js
  3. 5
      apps/remix-ide/src/app/tabs/analysis-tab.js
  4. 3
      apps/remix-ide/src/app/tabs/test-tab.js
  5. 70
      apps/remix-ide/src/app/ui/card.js
  6. 83
      apps/remix-ide/src/app/ui/contextMenu.js
  7. 39
      apps/remix-ide/src/app/ui/copy-to-clipboard.js
  8. 131
      apps/remix-ide/src/app/ui/draggableContent.js
  9. 128
      apps/remix-ide/src/app/ui/dropdown.js
  10. 153
      apps/remix-ide/src/app/ui/renderer.js
  11. 52
      apps/remix-ide/src/app/ui/styles/renderer-styles.js
  12. 590
      apps/remix-ide/src/app/ui/txLogger.js
  13. 132
      apps/remix-ide/src/lib/cmdInterpreterAPI.js

@ -1,194 +0,0 @@
'use strict'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import Registry from '../state/registry'
const yo = require('yo-yo')
const css = require('./styles/contextView-styles')
/*
Display information about the current focused code:
- if it's a reference, display information about the declaration
- jump to the declaration
- number of references
- rename declaration/references
*/
class ContextView {
constructor (opts) {
this._components = {}
this._components.registry = Registry.getInstance()
this.contextualListener = opts.contextualListener
this.editor = opts.editor
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api
}
this._view = null
this._nodes = null
this._current = null
this.sourceMappingDecoder = sourceMappingDecoder
this.previousElement = null
this.contextualListener.event.register('contextChanged', nodes => {
this.show()
this._nodes = nodes
this.update()
})
this.contextualListener.event.register('stopHighlighting', () => {
})
}
render () {
const view = yo`
<div class="${css.contextview} ${css.contextviewcontainer} bg-light text-dark border-0">
<div class=${css.container}>
${this._renderTarget()}
</div>
</div>`
if (!this._view) {
this._view = view
}
return view
}
hide () {
if (this._view) {
this._view.style.display = 'none'
}
}
show () {
if (this._view) {
this._view.style.display = 'block'
}
}
update () {
if (this._view) {
yo.update(this._view, this.render())
}
}
_renderTarget () {
let last
const previous = this._current
if (this._nodes && this._nodes.length) {
last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) {
this._current = last
} else {
const target = this.contextualListener.declarationOf(last)
if (target) {
this._current = target
} else {
this._current = null
}
}
}
if (!this._current || !previous || previous.id !== this._current.id || (this.previousElement && !this.previousElement.children.length)) {
this.previousElement = this._render(this._current, last)
}
return this.previousElement
}
_jumpToInternal (position) {
const jumpToLine = (lineColumn) => {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
if (filename !== this._deps.config.get('currentFile')) {
const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) {
provider.exists(filename).then(exist => {
this._deps.fileManager.open(filename)
jumpToLine(lineColumn)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {
jumpToLine(lineColumn)
}
}
}
_render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>`
let references = this.contextualListener.referencesOf(node)
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
references = `${references ? references.length : '0'} reference(s)`
let ref = 0
const nodes = this.contextualListener.getActiveHighlights()
for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k
break
}
}
// JUMP BETWEEN REFERENCES
const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0
this._jumpToInternal(nodes[ref].position)
}
const jumpTo = () => {
if (node && node.src) {
const position = this.sourceMappingDecoder.decode(node.src)
if (position) {
this._jumpToInternal(position)
}
}
}
const showGasEstimation = () => {
if (node.nodeType === 'FunctionDefinition') {
const result = this.contextualListener.gasEstimation(node)
const executionCost = ' Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return yo`
<div class=${css.gasEstimation}>
<i class="fas fa-gas-pump ${css.gasStationIcon}" title='Gas estimation'></i>
<span>${estimatedGas}</span>
</div>
`
}
}
return yo`
<div class=${css.line}>${showGasEstimation()}
<div title=${type} class=${css.type}>${type}</div>
<div title=${node.name} class=${css.name}>${node.name}</div>
<i class="fas fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i>
<span class=${css.referencesnb}>${references}</span>
<i data-action='previous' class="fas fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i>
<i data-action='next' class="fas fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
</div>
`
}
}
function isDefinition (node) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
module.exports = ContextView

@ -8,7 +8,6 @@ import Registry from '../state/registry'
const vm = require('vm')
const EventManager = require('../../lib/events')
const CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
const AutoCompletePopup = require('../ui/auto-complete-popup')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
@ -65,7 +64,6 @@ class Terminal extends Plugin {
}
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {}
this._components.cmdInterpreter = new CommandInterpreterAPI(this, this.blockchain)
this._components.autoCompletePopup = new AutoCompletePopup(this._opts)
this._commands = {}
this.commands = {}

@ -5,7 +5,6 @@ import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
var Renderer = require('../ui/renderer')
var EventManager = require('../../lib/events')
@ -30,9 +29,7 @@ class AnalysisTab extends ViewPlugin {
this.registry = Registry.getInstance()
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {
renderer: new Renderer(this)
}
this._components = {}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(

@ -8,8 +8,6 @@ import { ViewPlugin } from '@remixproject/engine-web'
import helper from '../../lib/helper'
import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity'
// var tooltip = require('../ui/tooltip')
var Renderer = require('../ui/renderer')
var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests')
const profile = {
@ -31,7 +29,6 @@ module.exports = class TestTab extends ViewPlugin {
this.fileManager = fileManager
this.filePanel = filePanel
this.appManager = appManager
this.renderer = new Renderer(this)
this.testRunner = new UnitTestRunner()
this.testTabLogic = new TestTabLogic(this.fileManager, helper)
this.offsetToLineColumnConverter = offsetToLineColumnConverter

@ -1,70 +0,0 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var EventManager = require('../../lib/events')
module.exports = class Card {
constructor (api, events, opts) {
const self = this
self._api = api
self._events = events
self._opts = opts
self._view = {}
self.event = new EventManager()
}
render () {
const self = this
if (self._view.el) return self._view.el
self._view.cardBody = yo`<div></div>`
self._view.arrow = yo`<i class="${css.arrow} fas fa-angle-down" onclick="${() => trigger(this)}"></i>`
self._view.expandCollapseButton = yo`
<div>${self._view.arrow}</div>`
self._view.statusBar = yo`<div>${self._opts.collapsedView}</div>`
self._view.cardHeader = yo`
<div class="d-flex justify-content-between align-items-center" onclick=${() => trigger(self._view.arrow)}>
<div class="pr-1 d-flex flex-row">
<div>${self._opts.title}</div>
${self._view.statusBar}
</div>
<div>${self._view.expandCollapseButton}</div>
</div>`
function trigger (el) {
var body = self._view.cardBody
var status = self._view.statusBar
if (el.classList) {
el.classList.toggle('fa-angle-up')
var arrow = el.classList.toggle('fa-angle-down') ? 'up' : 'down'
self.event.trigger('expandCollapseCard', [arrow, body, status])
}
}
// HTML
self._view.el = yo`
<div class="${css.cardContainer} list-group-item border-0">
${self._view.cardHeader}
${self._view.cardBody}
</div>`
return self._view.el
}
}
const css = csjs`
.cardContainer {
padding : 0 24px 16px;
margin : 0;
background : none;
}
.arrow {
font-weight : bold;
cursor : pointer;
font-size : 14px;
}
.arrow:hover {
}
`

@ -1,83 +0,0 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
var csjs = require('csjs-inject')
var css = csjs`
.container
{
display: none;
position: fixed;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.liitem
{
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
.liitem:hover
{
background-color: var(--secondary);
}
#menuitems
{
list-style: none;
margin: 0px;
}
`
module.exports = (event, items, linkItems) => {
event.preventDefault()
function hide (event, force) {
if (container && container.parentElement && (force || (event.target !== container))) {
container.parentElement.removeChild(container)
}
window.removeEventListener('click', hide)
}
const menu = Object.keys(items).map((item, index) => {
const current = yo`<li id="menuitem${item.toLowerCase()}" class=${css.liitem}>${item}</li>`
current.onclick = () => { hide(null, true); items[item]() }
return current
})
let menuForLinks = yo``
if (linkItems) {
menuForLinks = Object.keys(linkItems).map((item, index) => {
const current = yo`<li id="menuitem${item.toLowerCase()}" class=${css.liitem}><a href=${linkItems[item]} target="_blank">${item}</a></li>`
current.onclick = () => { hide(null, true) }
return current
})
}
const container = yo`
<div id="menuItemsContainer" class="p-1 ${css.container} bg-light shadow border">
<ul id='menuitems'>${menu} ${menuForLinks}</ul>
</div>
`
container.style.left = event.pageX + 'px'
container.style.top = event.pageY + 'px'
container.style.display = 'block'
document.querySelector('body').appendChild(container)
const menuItemsContainer = document.getElementById('menuItemsContainer')
const boundary = menuItemsContainer.getBoundingClientRect()
if (boundary.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
menuItemsContainer.style.position = 'absolute'
menuItemsContainer.style.bottom = '10px'
menuItemsContainer.style.top = null
}
setTimeout(() => {
window.addEventListener('click', hide)
}, 500)
return { hide }
}

@ -1,39 +0,0 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
const copy = require('copy-to-clipboard')
var addTooltip = require('./tooltip')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var css = csjs`
.copyIcon {
margin-left: 5px;
cursor: pointer;
}
`
module.exports = function copyToClipboard (getContent, tip = 'Copy value to clipboard', icon = 'fa-copy') {
var copyIcon = yo`<i title="${tip}" class="${css.copyIcon} far ${icon} p-2" data-id="copyToClipboardCopyIcon" aria-hidden="true"></i>`
copyIcon.onclick = (event) => {
event.stopPropagation()
var copiableContent
try {
copiableContent = getContent()
} catch (e) {
addTooltip(e.message)
return
}
if (copiableContent) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
try {
if (typeof copiableContent !== 'string') {
copiableContent = JSON.stringify(copiableContent, null, '\t')
}
} catch (e) {}
copy(copiableContent)
addTooltip('Copied value to clipboard.')
} else {
addTooltip('Cannot copy empty content!')
}
}
return copyIcon
}

@ -1,131 +0,0 @@
'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var css = csjs`
.containerDraggableModal {
position: absolute;
z-index: 1000;
text-align: center;
width: 500px;
height: 500px;
overflow-y: hidden;
}
.headerDraggableModal {
cursor: move;
z-index: 10;
text-overflow: ellipsis;
overflow-x: hidden;
}
.modalActions {
float: right;
}
.modalAction {
padding-right: 1px;
padding-left: 1px;
cursor: pointer;
}
`
module.exports =
class DraggableContent {
constructor (closeCb) {
this.closeCb = closeCb
this.isMaximised = false
}
render (title, url, content) {
this.content = content
var el = yo`
<div class=${css.containerDraggableModal}>
<div>
<div class="${css.headerDraggableModal} title" title=${title}><span title="${title}" >${title}</span><span title="${url}" > - ${url}</span>
<div class=${css.modalActions}>
<i onclick=${() => { this.minimize() }} class="fas fa-window-minimize ${css.modalAction}"></i>
<i onclick=${() => { this.maximise() }} class="fas fa-window-maximize ${css.modalAction}"></i>
<i onclick=${() => { this.close() }} class="fas fa-window-close-o ${css.modalAction}"></i>
</div>
</div>
</div>
${content}
</div> `
dragElement(el)
el.style.top = '20%'
el.style.left = '50%'
this.el = el
return el
}
setTitle (title) {
this.el.querySelector('.title span').innerHTML = title
}
minimize () {
this.isMaximised = false
this.content.style.display = 'none'
this.el.style.height = 'inherit'
this.el.style.width = '150px'
this.el.querySelector('.title').style.width = '146px'
}
maximise () {
this.content.style.display = 'block'
var body = document.querySelector('body')
this.el.style.height = this.isMaximised ? '500px' : body.offsetHeight + 'px'
this.el.style.width = this.isMaximised ? '500px' : body.offsetWidth + 'px'
this.isMaximised = !this.isMaximised
this.el.style.top = this.isMaximised ? '0%' : '20%'
this.el.style.left = this.isMaximised ? '0%' : '50%'
this.el.querySelector('.title').style.width = 'inherit'
}
close () {
if (this.closeCb) this.closeCb()
if (this.el.parentElement) {
this.el.parentElement.removeChild(this.el)
}
}
}
function dragElement (elmnt) {
var pos1 = 0
var pos2 = 0
var pos3 = 0
var pos4 = 0
elmnt.querySelector('.title').onmousedown = dragMouseDown
function dragMouseDown (e) {
e = e || window.event
if (e.button !== 0) return
e.preventDefault()
// get the mouse cursor position at startup:
pos3 = e.clientX
pos4 = e.clientY
document.onmouseup = closeDragElement
// call a function whenever the cursor moves:
document.onmousemove = elementDrag
}
function elementDrag (e) {
e = e || window.event
e.preventDefault()
// calculate the new cursor position:
pos1 = pos3 - e.clientX
pos2 = pos4 - e.clientY
pos3 = e.clientX
pos4 = e.clientY
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + 'px'
elmnt.style.left = (elmnt.offsetLeft - pos1) + 'px'
}
function closeDragElement () {
/* stop moving when mouse button is released: */
document.onmouseup = null
document.onmousemove = null
}
}

@ -1,128 +0,0 @@
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
// -------------- styling ----------------------
var css = require('./styles/dropdown-styles')
/* USAGE:
var dropdown = new Dropdown({
options: [
'knownTransaction',
'unknownTransaction',
'log',
'info',
'error'
],
defaults: ['knownTransaction']
})
dropdown.event.register('deselect', function (label) { })
dropdown.event.register('select', function (label) { })
*/
class Dropdown {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.data = {
_options: opts.options || [],
_dependencies: opts.dependencies || [],
selected: opts.defaults || [],
_elements: []
}
self._view = {}
self._api = opts.api
self._events = opts.events
}
render () {
var self = this
if (self._view.el) return self._view.el
self._view.selected = yo`
<div class=${css.selectbox}>
<span class=${css.selected}> [${self.data.selected.length}] ${self.data.selected.join(', ')}</span>
<i class="${css.icon} fas fa-caret-down"></i>
</div>
`
self._view.el = yo`
<div name="dropdown" class="${css.dropdown} form-control form-control-sm" onclick=${show}>
${self._view.selected}
<div class="${css.options} bg-light" style="display: none;}">
${self.data._options.map(label => {
const index = self.data._elements.length
var input = yo`
<input
data-idx=${index}
onchange=${emit}
type="${index === 2 ? 'checkbox' : 'radio'}"
id="${label}"
/>
`
if (self.data.selected.indexOf(label) !== -1) {
input.checked = true
self.event.trigger('select', [label])
}
self.data._elements.push(input)
return yo`
<div class=${css.option}>
${input}
<label class="text-dark" for="${label}">${label}</label>
</div>
`
})}
</div>
</div>
`
return self._view.el
function emit (event) {
var input = event.currentTarget
var label = input.nextSibling.innerText
if (input.checked) {
self.data.selected.push(label)
if (event.type === 'change') {
self.event.trigger('select', [label])
updateDependencies(label)
}
} else {
var idx = self.data.selected.indexOf(label)
self.data.selected.splice(idx, 1)
if (event.type === 'change') {
self.event.trigger('deselect', [label])
updateDependencies(label)
}
}
self._view.selected.children[0].innerText = `[${self.data.selected.length}] ${self.data.selected.join(', ')}`
}
function updateDependencies (changed) {
if (self.data._dependencies[changed]) {
for (var dep in self.data._dependencies[changed]) {
var label = self.data._dependencies[changed][dep]
var el = self.data._elements[self.data._options.indexOf(label)]
el.checked = !el.checked
emit({ currentTarget: el, type: 'changeDependencies' })
}
}
}
function show (event) {
event.stopPropagation()
var options = event.currentTarget.children[1]
var parent = event.target.parentElement
var isOption = parent === options || parent.parentElement === options
if (isOption) return
if (options.style.display === 'none') {
options.style.display = ''
document.body.addEventListener('click', handler)
} else {
options.style.display = 'none'
document.body.removeEventListener('click', handler)
}
function handler (event) {
options.style.display = 'none'
document.body.removeEventListener('click', handler)
}
}
}
}
module.exports = Dropdown

@ -1,153 +0,0 @@
'use strict'
var $ = require('jquery')
var yo = require('yo-yo')
const { default: Registry } = require('../state/registry')
var css = require('./styles/renderer-styles')
/**
* After refactor, the renderer is only used to render error/warning
* TODO: This don't need to be an object anymore. Simplify and just export the renderError function.
*
*/
function Renderer (service) {
const self = this
self.service = service
self._components = {}
self._components.registry = Registry.getInstance()
// dependencies
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api
}
if (document && document.head) {
document.head.appendChild(css)
}
}
Renderer.prototype._error = function (file, error) {
const self = this
if (file === self._deps.config.get('currentFile')) {
self.service.call('editor', 'addAnnotation', error, file)
}
}
Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
const self = this
const editor = self._components.registry.get('editor').api
if (errFile !== self._deps.config.get('currentFile')) {
// TODO: refactor with this._components.contextView.jumpTo
var provider = self._deps.fileManager.fileProviderOf(errFile)
if (provider) {
provider.exists(errFile).then(exist => {
self._deps.fileManager.open(errFile)
editor.gotoLine(errLine, errCol)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {
editor.gotoLine(errLine, errCol)
}
}
function getPositionDetails (msg) {
const result = {}
// To handle some compiler warning without location like SPDX license warning etc
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg }
// extract line / column
let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
result.errLine = position ? parseInt(position[2]) - 1 : -1
result.errCol = position ? parseInt(position[3]) : -1
// extract file
position = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = position ? position[1] : ''
return result
}
/**
* format msg like error or warning,
*
* @param {String or DOMElement} message
* @param {DOMElement} container
* @param {Object} options {
* useSpan,
* noAnnotations,
* click:(Function),
* type:(
* warning,
* error
* ),
* errFile,
* errLine,
* errCol
* }
*/
Renderer.prototype.error = function (message, container, opt) {
if (!message) return
if (container === undefined) return
opt = opt || {}
var text
if (typeof message === 'string') {
text = message
message = yo`<span>${message}</span>`
} else if (message.innerText) {
text = message.innerText
}
// ^ e.g:
// browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
let position = getPositionDetails(text)
// For compiler version 0.8.0 and upcoming versions, errors and warning will be reported in a different way
// Above method regex will return type of error as 'errFile'
// Comparison of 'errFile' with passed error type will ensure the reporter type
if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) {
// Updated error reported includes '-->' before file details
const errorDetails = text.split('-->')
// errorDetails[1] will have file details
if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1])
}
opt.errLine = position.errLine
opt.errCol = position.errCol
opt.errFile = position.errFile.trim()
if (!opt.noAnnotations && opt.errFile) {
this._error(opt.errFile, {
row: opt.errLine,
column: opt.errCol,
text: text,
type: opt.type
})
}
var $pre = $(opt.useSpan ? yo`<span></span>` : yo`<pre></pre>`).html(message)
const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
var $error = $(yo`<div class="sol ${opt.type} ${classList}" data-id="${opt.errFile}"><div class="close" data-id="renderer"><i class="fas fa-times"></i></div></div>`).prepend($pre)
$(container).append($error)
$error.click((ev) => {
if (opt.click) {
opt.click(message)
} else if (opt.errFile !== undefined && opt.errLine !== undefined && opt.errCol !== undefined) {
this._errorClick(opt.errFile, opt.errLine, opt.errCol)
}
})
$error.find('.close').click(function (ev) {
ev.preventDefault()
$error.remove()
return false
})
}
module.exports = Renderer

@ -1,52 +0,0 @@
var yo = require('yo-yo')
var css = yo`<style>
.sol.success,
.sol.error,
.sol.warning {
white-space: pre-line;
word-wrap: break-word;
cursor: pointer;
position: relative;
margin: 0.5em 0 1em 0;
border-radius: 5px;
line-height: 20px;
padding: 8px 15px;
}
.sol.success pre,
.sol.error pre,
.sol.warning pre {
white-space: pre-line;
overflow-y: hidden;
background-color: transparent;
margin: 0;
font-size: 12px;
border: 0 none;
padding: 0;
border-radius: 0;
}
.sol.success .close,
.sol.error .close,
.sol.warning .close {
white-space: pre-line;
font-weight: bold;
position: absolute;
color: hsl(0, 0%, 0%); /* black in style-guide.js */
top: 0;
right: 0;
padding: 0.5em;
}
.sol.error {
}
.sol.warning {
}
.sol.success {
/* background-color: // styles.rightPanel.message_Success_BackgroundColor; */
}</style>`
module.exports = css

@ -1,590 +0,0 @@
'use strict'
var yo = require('yo-yo')
var copyToClipboard = require('./copy-to-clipboard')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var remixLib = require('@remix-project/remix-lib')
var EventManager = require('../../lib/events')
var helper = require('../../lib/helper')
var modalDialog = require('./modal-dialog-custom')
const { default: Registry } = require('../state/registry')
var typeConversion = remixLib.execution.typeConversion
var css = csjs`
.log {
display: flex;
cursor: pointer;
align-items: center;
cursor: pointer;
}
.log:hover {
opacity: 0.8;
}
.arrow {
color: var(--text-info);
font-size: 20px;
cursor: pointer;
display: flex;
margin-left: 10px;
}
.arrow:hover {
color: var(--secondary);
}
.txLog {
}
.txStatus {
display: flex;
font-size: 20px;
margin-right: 20px;
float: left;
}
.succeeded {
color: var(--success);
}
.failed {
color: var(--danger);
}
.notavailable {
}
.call {
font-size: 7px;
border-radius: 50%;
min-width: 20px;
min-height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-info);
text-transform: uppercase;
font-weight: bold;
}
.txItem {
color: var(--text-info);
margin-right: 5px;
float: left;
}
.txItemTitle {
font-weight: bold;
}
.tx {
color: var(--text-info);
font-weight: bold;
float: left;
margin-right: 10px;
}
.txTable,
.tr,
.td {
border-collapse: collapse;
font-size: 10px;
color: var(--text-info);
border: 1px solid var(--text-info);
}
#txTable {
margin-top: 1%;
margin-bottom: 5%;
align-self: center;
width: 85%;
}
.tr, .td {
padding: 4px;
vertical-align: baseline;
}
.td:first-child {
min-width: 30%;
width: 30%;
align-items: baseline;
font-weight: bold;
}
.tableTitle {
width: 25%;
}
.buttons {
display: flex;
margin-left: auto;
}
.debug {
white-space: nowrap;
}
.debug:hover {
opacity: 0.8;
}`
/**
* This just export a function that register to `newTransaction` and forward them to the logger.
*
*/
class TxLogger {
constructor (terminal, blockchain) {
this.event = new EventManager()
this.seen = {}
function filterTx (value, query) {
if (value.length) {
return helper.find(value, query)
}
return false
}
this.eventsDecoder = Registry.getInstance().get('eventsDecoder').api
this.txListener = Registry.getInstance().get('txlistener').api
this.terminal = terminal
// dependencies
this._deps = {
compilersArtefacts: Registry.getInstance().get('compilersartefacts').api
}
this.logKnownTX = this.terminal.registerCommand('knownTransaction', (args, cmds, append) => {
var data = args[0]
var el
if (data.tx.isCall) {
el = renderCall(this, data)
} else {
el = renderKnownTransaction(this, data, blockchain)
}
this.seen[data.tx.hash] = el
append(el)
}, { activate: true, filterFn: filterTx })
this.logUnknownTX = this.terminal.registerCommand('unknownTransaction', (args, cmds, append) => {
// triggered for transaction AND call
var data = args[0]
var el = renderUnknownTransaction(this, data, blockchain)
append(el)
}, { activate: false, filterFn: filterTx })
this.logEmptyBlock = this.terminal.registerCommand('emptyBlock', (args, cmds, append) => {
var data = args[0]
var el = renderEmptyBlock(this, data)
append(el)
}, { activate: true })
this.txListener.event.register('newBlock', (block) => {
if (!block.transactions || (block.transactions && !block.transactions.length)) {
this.logEmptyBlock({ block: block })
}
})
this.txListener.event.register('newTransaction', (tx, receipt) => {
log(this, tx, receipt)
})
this.txListener.event.register('newCall', (tx) => {
log(this, tx, null)
})
this.terminal.updateJournal({ type: 'select', value: 'unknownTransaction' })
this.terminal.updateJournal({ type: 'select', value: 'knownTransaction' })
}
}
function debug (e, data, self) {
e.stopPropagation()
if (data.tx.isCall && data.tx.envMode !== 'vm') {
modalDialog.alert('Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.')
} else {
self.event.trigger('debuggingRequested', [data.tx.hash])
}
}
function log (self, tx, receipt) {
var resolvedTransaction = self.txListener.resolvedTransaction(tx.hash)
if (resolvedTransaction) {
var compiledContracts = null
if (self._deps.compilersArtefacts.__last) {
compiledContracts = self._deps.compilersArtefacts.__last.getContracts()
}
self.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, (error, logs) => {
if (!error) {
self.logKnownTX({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs })
}
})
} else {
// contract unknown - just displaying raw tx.
self.logUnknownTX({ tx: tx, receipt: receipt })
}
}
function renderKnownTransaction (self, data, blockchain) {
var from = data.tx.from
var to = data.resolvedData.contractName + '.' + data.resolvedData.fn
var obj = { from, to }
var txType = 'knownTx'
var tx = yo`
<span id="tx${data.tx.hash}" data-id="txLogger${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.receipt, txType)}
${context(self, { from, to, data }, blockchain)}
<div class=${css.buttons}>
<button
class="${css.debug} btn btn-primary btn-sm"
data-shared="txLoggerDebugButton"
data-id="txLoggerDebugButton${data.tx.hash}"
onclick=${(e) => debug(e, data, self)}
>
Debug
</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderCall (self, data) {
var to = data.resolvedData.contractName + '.' + data.resolvedData.fn
var from = data.tx.from ? data.tx.from : ' - '
var input = data.tx.input ? helper.shortenHexData(data.tx.input) : ''
var obj = { from, to }
var txType = 'call'
var tx = yo`
<span id="tx${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.tx, txType)}
<span class=${css.txLog}>
<span class=${css.tx}>[call]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
</span>
<div class=${css.buttons}>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderUnknownTransaction (self, data, blockchain) {
var from = data.tx.from
var to = data.tx.to
var obj = { from, to }
var txType = 'unknown' + (data.tx.isCall ? 'Call' : 'Tx')
var tx = yo`
<span id="tx${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.receipt || data.tx, txType)}
${context(self, { from, to, data }, blockchain)}
<div class=${css.buttons}>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderEmptyBlock (self, data) {
return yo`
<span class=${css.txLog}>
<span class='${css.tx}'><div class=${css.txItem}>[<span class=${css.txItemTitle}>block:${data.block.number} - </span> 0 transactions]</span></span>
</span>`
}
function checkTxStatus (tx, type) {
if (tx.status === '0x1' || tx.status === true) {
return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>`
}
if (type === 'call' || type === 'unknownCall') {
return yo`<i class="${css.txStatus} ${css.call}">call</i>`
} else if (tx.status === '0x0' || tx.status === false) {
return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>`
} else {
return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>`
}
}
function context (self, opts, blockchain) {
var data = opts.data || ''
var from = opts.from ? helper.shortenHexData(opts.from) : ''
var to = opts.to
if (data.tx.to) to = to + ' ' + helper.shortenHexData(data.tx.to)
var val = data.tx.value
var hash = data.tx.hash ? helper.shortenHexData(data.tx.hash) : ''
var input = data.tx.input ? helper.shortenHexData(data.tx.input) : ''
var logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0
var block = data.receipt ? data.receipt.blockNumber : data.tx.blockNumber || ''
var i = data.receipt ? data.receipt.transactionIndex : data.tx.transactionIndex
var value = val ? typeConversion.toInt(val) : 0
if (blockchain.getProvider() === 'vm') {
return yo`
<div>
<span class=${css.txLog}>
<span class=${css.tx}>[vm]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div>
</span>
</div>`
} else if (blockchain.getProvider() !== 'vm' && data.resolvedData) {
return yo`
<div>
<span class=${css.txLog}>
<span class='${css.tx}'>[block:${block} txIndex:${i}]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div>
</span>
</div>`
} else {
to = helper.shortenHexData(to)
hash = helper.shortenHexData(data.tx.blockHash)
return yo`
<div>
<span class=${css.txLog}>
<span class='${css.tx}'>[block:${block} txIndex:${i}]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
</span>
</div>`
}
}
module.exports = TxLogger
// helpers
function isDescendant (parent, child) {
var node = child.parentNode
while (node != null) {
if (node === parent) {
return true
}
node = node.parentNode
}
return false
}
function txDetails (e, tx, data, obj) {
const from = obj.from
const to = obj.to
const arrowUp = yo`<i class="${css.arrow} fas fa-angle-up"></i>`
const arrowDown = yo`<i class="${css.arrow} fas fa-angle-down"></i>`
let blockElement = e.target
while (true) { // get the parent block element
if (blockElement.className.startsWith('block')) break
else if (blockElement.parentElement) {
blockElement = blockElement.parentElement
} else break
}
const tables = blockElement.querySelectorAll(`#${tx.id} [class^="txTable"]`)
const logs = blockElement.querySelectorAll(`#${tx.id} [class^='log']`)
const arrows = blockElement.querySelectorAll(`#${tx.id} [class^='arrow']`)
let table = [...tables].filter((t) => isDescendant(tx, t))[0]
const log = [...logs].filter((t) => isDescendant(tx, t))[0]
const arrow = [...arrows].filter((t) => isDescendant(tx, t))[0]
if (table && table.parentNode) {
tx.removeChild(table)
log.removeChild(arrow)
log.appendChild(arrowDown)
} else {
log.removeChild(arrow)
log.appendChild(arrowUp)
table = createTable({
hash: data.tx.hash,
status: data.receipt ? data.receipt.status : null,
isCall: data.tx.isCall,
contractAddress: data.tx.contractAddress,
data: data.tx,
from,
to,
gas: data.tx.gas,
input: data.tx.input,
'decoded input': data.resolvedData && data.resolvedData.params ? JSON.stringify(typeConversion.stringify(data.resolvedData.params), null, '\t') : ' - ',
'decoded output': data.resolvedData && data.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(data.resolvedData.decodedReturnValue), null, '\t') : ' - ',
logs: data.logs,
val: data.tx.value,
transactionCost: data.tx.transactionCost,
executionCost: data.tx.executionCost
})
tx.appendChild(table)
}
}
function createTable (opts) {
var table = yo`<table class="${css.txTable}" id="txTable" data-id="txLoggerTable${opts.hash}"></table>`
if (!opts.isCall) {
var msg = ''
if (opts.status !== undefined && opts.status !== null) {
if (opts.status === '0x0' || opts.status === false) {
msg = ' Transaction mined but execution failed'
} else if (opts.status === '0x1' || opts.status === true) {
msg = ' Transaction mined and execution succeed'
}
} else {
msg = ' Status not available at the moment'
}
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> status </td>
<td class="${css.td}" data-id="txLoggerTableStatus${opts.hash}" data-shared="pair_${opts.hash}">${opts.status}${msg}</td>
</tr>`)
}
var transactionHash = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> transaction hash </td>
<td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash}
${copyToClipboard(() => opts.hash)}
</td>
</tr>
`
table.appendChild(transactionHash)
var contractAddress = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> contract address </td>
<td class="${css.td}" data-id="txLoggerTableContractAddress${opts.hash}" data-shared="pair_${opts.hash}">${opts.contractAddress}
${copyToClipboard(() => opts.contractAddress)}
</td>
</tr>
`
if (opts.contractAddress) table.appendChild(contractAddress)
var from = yo`
<tr class="${css.tr}">
<td class="${css.td} ${css.tableTitle}" data-shared="key_${opts.hash}"> from </td>
<td class="${css.td}" data-id="txLoggerTableFrom${opts.hash}" data-shared="pair_${opts.hash}">${opts.from}
${copyToClipboard(() => opts.from)}
</td>
</tr>
`
if (opts.from) table.appendChild(from)
var toHash
var data = opts.data // opts.data = data.tx
if (data.to) {
toHash = opts.to + ' ' + data.to
} else {
toHash = opts.to
}
var to = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> to </td>
<td class="${css.td}" data-id="txLoggerTableTo${opts.hash}" data-shared="pair_${opts.hash}">${toHash}
${copyToClipboard(() => data.to ? data.to : toHash)}
</td>
</tr>
`
if (opts.to) table.appendChild(to)
var gas = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> gas </td>
<td class="${css.td}" data-id="txLoggerTableGas${opts.hash}" data-shared="pair_${opts.hash}">${opts.gas} gas
${copyToClipboard(() => opts.gas)}
</td>
</tr>
`
if (opts.gas) table.appendChild(gas)
var callWarning = ''
if (opts.isCall) {
callWarning = '(Cost only applies when called by a contract)'
}
if (opts.transactionCost) {
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> transaction cost </td>
<td class="${css.td}" data-id="txLoggerTableTransactionCost${opts.hash}" data-shared="pair_${opts.hash}">${opts.transactionCost} gas ${callWarning}
${copyToClipboard(() => opts.transactionCost)}
</td>
</tr>`)
}
if (opts.executionCost) {
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> execution cost </td>
<td class="${css.td}" data-id="txLoggerTableExecutionHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.executionCost} gas ${callWarning}
${copyToClipboard(() => opts.executionCost)}
</td>
</tr>`)
}
var hash = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> hash </td>
<td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash}
${copyToClipboard(() => opts.hash)}
</td>
</tr>
`
if (opts.hash) table.appendChild(hash)
var input = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> input </td>
<td class="${css.td}" data-id="txLoggerTableInput${opts.hash}" data-shared="pair_${opts.hash}">${helper.shortenHexData(opts.input)}
${copyToClipboard(() => opts.input)}
</td>
</tr>
`
if (opts.input) table.appendChild(input)
if (opts['decoded input']) {
var inputDecoded = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> decoded input </td>
<td class="${css.td}" data-id="txLoggerTableDecodedInput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded input']}
${copyToClipboard(() => opts['decoded input'])}
</td>
</tr>`
table.appendChild(inputDecoded)
}
if (opts['decoded output']) {
var outputDecoded = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> decoded output </td>
<td class="${css.td}" id="decodedoutput" data-id="txLoggerTableDecodedOutput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded output']}
${copyToClipboard(() => opts['decoded output'])}
</td>
</tr>`
table.appendChild(outputDecoded)
}
var stringified = ' - '
if (opts.logs && opts.logs.decoded) {
stringified = typeConversion.stringify(opts.logs.decoded)
}
var logs = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> logs </td>
<td class="${css.td}" id="logs" data-id="txLoggerTableLogs${opts.hash}" data-shared="pair_${opts.hash}">
${JSON.stringify(stringified, null, '\t')}
${copyToClipboard(() => JSON.stringify(stringified, null, '\t'))}
${copyToClipboard(() => JSON.stringify(opts.logs.raw || '0'))}
</td>
</tr>
`
if (opts.logs) table.appendChild(logs)
var val = opts.val != null ? typeConversion.toInt(opts.val) : 0
val = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> value </td>
<td class="${css.td}" data-id="txLoggerTableValue${opts.hash}" data-shared="pair_${opts.hash}">${val} wei
${copyToClipboard(() => `${val} wei`)}
</td>
</tr>
`
if (opts.val) table.appendChild(val)
return table
}

@ -1,132 +0,0 @@
'use strict'
import { CompilerImports } from '@remix-project/core-plugin'
import Registry from '../app/state/registry'
var yo = require('yo-yo')
var async = require('async')
var EventManager = require('../lib/events')
var toolTip = require('../app/ui/tooltip')
class CmdInterpreterAPI {
constructor (terminal, blockchain) {
const self = this
self.event = new EventManager()
self.blockchain = blockchain
self._components = {}
self._components.registry = Registry.getInstance()
self._components.terminal = terminal
self._components.fileImport = new CompilerImports()
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
editor: self._components.registry.get('editor').api,
compilersArtefacts: self._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
}
self.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
}
log () { arguments[0] != null ? this._components.terminal.commands.html(arguments[0]) : this._components.terminal.commands.html(arguments[1]) }
loadgist (id, cb) {
this._components.terminal.call('gistHandler', 'load', id)
if (cb) cb()
}
loadurl (url, cb) {
const self = this
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
(err, content, cleanUrl, type, url) => {
if (err) {
toolTip(`Unable to load ${url}: ${err}`)
if (cb) cb(err)
} else {
self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
try {
content = JSON.parse(content)
async.eachOfSeries(content.sources, (value, file, callbackSource) => {
var url = value.urls[0] // @TODO retrieve all other contents ?
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) {
toolTip(`Cannot retrieve the content of ${url}: ${error}`)
return callbackSource(`Cannot retrieve the content of ${url}: ${error}`)
} else {
try {
await self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
callbackSource()
} catch (e) {
callbackSource(e.message)
}
}
})
}, (error) => {
if (cb) cb(error)
})
} catch (e) {}
if (cb) cb()
}
})
}
exeCurrent (cb) {
return this.execute(undefined, cb)
}
execute (file, cb) {
const self = this
function _execute (content, cb) {
if (!content) {
toolTip('no content to execute')
if (cb) cb()
return
}
self._components.terminal.commands.script(content)
}
if (typeof file === 'undefined') {
var content = self._deps.editor.currentContent()
_execute(content, cb)
return
}
var provider = self._deps.fileManager.fileProviderOf(file)
if (!provider) {
toolTip(`provider for path ${file} not found`)
if (cb) cb()
return
}
provider.get(file, (error, content) => {
if (error) {
toolTip(error)
if (cb) cb()
return
}
_execute(content, cb)
})
}
help (cb) {
const self = this
var help = yo`<div></div>`
for (var k in self.commandHelp) {
help.appendChild(yo`<div>${k}: ${self.commandHelp[k]}</div>`)
help.appendChild(yo`<br>`)
}
self._components.terminal.commands.html(help)
if (cb) cb()
return ''
}
}
module.exports = CmdInterpreterAPI
Loading…
Cancel
Save