From 24e25bd63a51bb60ab87e6b777dc265c5de77230 Mon Sep 17 00:00:00 2001 From: "davidzagi93@gmail.com" Date: Wed, 11 Aug 2021 06:15:40 +0100 Subject: [PATCH] feat: clear-console, terminal-search, fix-input terminal input to buttom --- apps/remix-ide/src/app/panels/terminal.js | 5 + .../terminal/src/lib/remix-ui-terminal.tsx | 450 +++++++++++++++--- .../remix-ui/terminal/src/lib/utils/helper.ts | 176 +++++++ 3 files changed, 568 insertions(+), 63 deletions(-) create mode 100644 libs/remix-ui/terminal/src/lib/utils/helper.ts diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 638b97f5ed..26e63a3e6a 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -15,6 +15,11 @@ var globalRegistry = require('../../global/registry') var SourceHighlighter = require('../../app/editor/sourceHighlighter') var GistHandler = require('../../lib/gist-handler') +import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line +var globalRegistry = require('../../global/registry') +var SourceHighlighter = require('../../app/editor/sourceHighlighter') +var GistHandler = require('../../lib/gist-handler') + var KONSOLES = [] function register (api) { KONSOLES.push(api) } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index e1234bd6fc..bbb62b8f25 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -16,7 +16,6 @@ import vm from 'vm' import javascriptserialize from 'javascript-serialize' import jsbeautify from 'js-beautify' import helper from '../../../../../apps/remix-ide/src/lib/helper' - const remixLib = require('@remix-project/remix-lib') var typeConversion = remixLib.execution.typeConversion @@ -180,6 +179,53 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { if (cb) cb() } + const domTerminalFeatures = () => { + return { + remix: props.cmdInterpreter + } + } + + function exeCurrent (cb) { + return execute(undefined, cb) + } + + function execute (file, cb) { + function _execute (content, cb) { + if (!content) { + // toolTip('no content to execute') + if (cb) cb() + return + } + + newstate.commands.script(content) + } + + if (typeof file === 'undefined') { + var content = props._deps.editor.currentContent() + _execute(content, cb) + return + } + + var provider = props._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) + // TODO: pop up + if (cb) cb() + return + } + + _execute(content, cb) + }) + } + const _shell = async (script, scopedCommands, done) => { // default shell if (script.indexOf('remix:') === 0) { return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.') @@ -408,26 +454,10 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { /* end of mouse event */ useEffect(() => { - // document.addEventListener('mousemove', changeBg) - // function changeBg () { - // document.getElementById('dragId').style.backgroundColor = 'skyblue' - // } - // document.addEventListener('mouseup', changeBg2) - // function changeBg2 () { - // document.getElementById('dragId').style.backgroundColor = '' - // } document.addEventListener('mousemove', onMouseMove) document.addEventListener('mouseup', onMouseUp) return () => { - // document.addEventListener('mousemove', changeBg) - // function changeBg () { - // document.getElementById('dragId').style.backgroundColor = 'skyblue' - // } - // document.addEventListener('mouseup', changeBg2) - // function changeBg2 () { - // document.getElementById('dragId').style.backgroundColor = '' - // } document.removeEventListener('mousemove', onMouseMove) document.removeEventListener('mouseup', onMouseUp) } @@ -945,69 +975,363 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { /* end of block content that gets rendered from script Runner */ + const handleClearConsole = () => { + dispatch({ type: 'clearconsole', payload: [] }) + inputEl.current.focus() + } /* start of autoComplete */ - const handleSelect = (text) => { - props.thisState.event.trigger('handleSelect', [text]) + + const listenOnNetwork = (event: any) => { + const isListening = event.target.checked + setIsListeningOnNetwork(isListening) + listenOnNetworkAction(props, isListening) } const onChange = (event: any) => { event.preventDefault() const inputString = event.target.value - console.log(event) - console.log({ inputString }) - setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString })) - const textList = inputString.split(' ') - const autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0] - allPrograms.forEach(item => { - const program = getKeyOf(item) - console.log({ program }) - if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) { - setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } })) - } else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) { - allCommands.forEach(item => { - console.log({ item }) - const command = getKeyOf(item) - if (command.includes(autoCompleteInput.trim())) { - setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } })) - } - }) + if (matched(allPrograms, inputString) || inputString.includes('.')) { + setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString })) + const textList = inputString.split('.') + if (textList.length === 1) { + setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } })) + const result = Objectfilter(allPrograms, autoCompletState.userInput) + setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } })) + } else { + setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } })) + const result = Objectfilter(allCommands, autoCompletState.userInput) + setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } })) } - }) - autoCompletState.extraCommands.forEach(item => { - const command = getKeyOf(item) - if (command.includes(autoCompleteInput.trim())) { - setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } })) + } else { + setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: inputString })) + } + } + + const handleSelect = (event) => { + const suggestionCount = autoCompletState.activeSuggestion + if (event.keyCode === 38) { + if (autoCompletState.activeSuggestion === 0) { + return } - }) - if (autoCompletState.data._options.length === 1 && event.which === 9) { - // if only one option and tab is pressed, we resolve it - event.preventDefault() - textList.pop() - textList.push(getKeyOf(autoCompletState.data._options[0])) - handleSelect(`${textList}`.replace(/,/g, ' ')) + setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount - 1 })) + } else if (event.keyCode === 40) { + if (autoCompletState.activeSuggestion - 1 === autoCompletState.data._options.length) { + return + } + setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount + 1 })) + } + // props.thisState.event.trigger('handleSelect', [text]) + } + + const checkTxStatus = (tx, type) => { + if (tx.status === '0x1' || tx.status === true) { + return () + } + if (type === 'call' || type === 'unknownCall' || type === 'unknown') { + return (call) + } else if (tx.status === '0x0' || tx.status === false) { + return () + } else { + return () } } + const context = (opts, blockchain) => { + const data = opts.tx || '' + const from = opts.from ? helper.shortenHexData(opts.from) : '' + let to = opts.to + if (data.to) to = to + ' ' + helper.shortenHexData(data.to) + const val = data.value + let hash = data.hash ? helper.shortenHexData(data.hash) : '' + const input = data.input ? helper.shortenHexData(data.input) : '' + const logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0 + const block = data.receipt ? data.receipt.blockNumber : data.blockNumber || '' + const i = data ? data.transactionIndex : data.transactionIndex + const value = val ? typeConversion.toInt(val) : 0 + + if (blockchain.getProvider() === 'vm') { + return ( +
+ + [{vm}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
data: {input}
+
logs: {logs}
+
hash: {hash}
+
+
) + } else if (blockchain.getProvider() !== 'vm' && data.resolvedData) { + return ( +
+ + [block:${block} txIndex:${i}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
data: {input}
+
logs: {logs}
+
hash: {hash}
+
+
) + } else { + to = helper.shortenHexData(to) + hash = helper.shortenHexData(data.blockHash) + return ( +
+ + [block:${block} txIndex:${i}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
+
) + } + } + + const txDetails = (event, tx, obj) => { + if (showTableDetails === null) { + setShowTableDetails(true) + } else { + setShowTableDetails(null) + } + // if (showTableDetails.length === 0) { + // setShowTableDetails([{ hash: tx.hash, show: true }]) + // } + // const id = showTableDetails.filter(x => x.hash !== tx.hash) + // if ((showTableDetails.length !== 0) && (id[0] === tx.hash)) { + // setShowTableDetails(currentState => ([...currentState, { hash: tx.hash, show: false }])) + // } + // console.log((showTableDetails.length !== 0) && (id[0] === tx.hash)) + // console.log({ showTableDetails }, ' clicked button') + } + + const showTable = (opts) => { + let msg = '' + let toHash + const data = opts.data // opts.data = data.tx + if (data.to) { + toHash = opts.to + ' ' + data.to + } else { + toHash = opts.to + } + let callWarning = '' + if (opts.isCall) { + callWarning = '(Cost only applies when called by a contract)' + } + if (!opts.isCall) { + 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' + } + } + + let stringified = ' - ' + if (opts.logs && opts.logs.decoded) { + stringified = typeConversion.stringify(opts.logs.decoded) + } + const val = opts.val != null ? typeConversion.toInt(opts.val) : 0 + return ( + + + + + + + + + + { + opts.contractAddress && ( + + + + + ) + } + { + opts.from && ( + + + + + ) + } + { + opts.to && ( + + + + + ) + } + { + opts.gas && ( + + + + + ) + } + { + opts.transactionCost && ( + + + + + ) + } + { + opts.executionCost && ( + + + + + ) + } + {opts.hash && ( + + + + + )} + {opts.input && ( + + + + + )} + {opts['decoded input'] && ( + + + + + )} + {opts['decoded output'] && ( + + + + + )} + {opts.logs && ( + + + + + )} + {opts.val && ( + + + + + )} +
status {opts.status}{msg}
transaction hash {opts.hash} + +
contract address {opts.contractAddress} + +
from {opts.from} + +
to {toHash} + +
gas {opts.gas} gas + +
transaction cost {opts.transactionCost} gas {callWarning} + +
execution cost {opts.executionCost} gas {callWarning} + +
hash {opts.hash} + +
input {helper.shortenHexData(opts.input)} + +
decode input {opts['decoded input']} + +
decode output {opts['decoded output']} + +
logs + {JSON.stringify(stringified, null, '\t')} + + +
val {val} wei + +
+ ) + } + + const debug = (event, tx) => { + event.stopPropagation() + if (tx.isCall && tx.envMode !== 'vm') { + console.log('start debugging') + return ( {} } + message="Cannot debug this call. Debugging calls is only possible in JavaScript VM mode." + />) + } else { + props.event.trigger('debuggingRequested', [tx.hash]) + console.log('trigger ', { tx: props.event.trigger }) + } + } + + const renderKnownTransactions = (tx, receipt, index) => { + const from = tx.from + const to = tx.to + const obj = { from, to } + const showDetails = showTableDetails === tx.from + const txType = 'unknown' + (tx.isCall ? 'Call' : 'Tx') + return ( + +
txDetails(event, tx, obj)}> + {/* onClick={e => txDetails(e, tx, data, obj)} */} + {checkTxStatus(receipt || tx, txType)} + {context({ from, to, tx }, props.blockchain)} +
+
debug(event, tx)}>Debug
+
+ +
+ {showTableDetails ? showTable({ + hash: tx.hash, + status: receipt !== null ? receipt.status : null, + isCall: tx.isCall, + contractAddress: tx.contractAddress, + data: tx, + from, + to, + gas: tx.gas, + input: tx.input, + 'decoded input': tx.resolvedData && tx.resolvedData.params ? JSON.stringify(typeConversion.stringify(tx.resoparams), null, '\t') : ' - ', + 'decoded output': tx.resolvedData && tx.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(tx.resolvedData.decodedReturnValue), null, '\t') : ' - ', + logs: tx.logs, + val: tx.value, + transactionCost: tx.transactionCost, + executionCost: tx.executionCost + }) : null} +
+ ) + } + const handleAutoComplete = () => ( -
+
- ${autoCompletState.data._options.map((item, index) => { + {autoCompletState.data._options.map((item, index) => { return ( -
auto complete here
- //
- //
{ handleSelect(event.srcElement.innerText) }}> - // {getKeyOf(item)} - //
- //
- // {getValueOf(item)} - //
- //
+
+
+ {getKeyOf(item)} +
+
+ {getValueOf(item)} +
+
) })}
- {/*
-
Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(data._options.length / self._elementsToShow)}
-
*/}
) /* end of autoComplete */ diff --git a/libs/remix-ui/terminal/src/lib/utils/helper.ts b/libs/remix-ui/terminal/src/lib/utils/helper.ts new file mode 100644 index 0000000000..56b50a4377 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/utils/helper.ts @@ -0,0 +1,176 @@ +// var async = require('async') +// const ethJSUtil = require('ethereumjs-util') + +// export const shortenAddress = (address, etherBalance) => { +// var len = address.length +// return address.slice(0, 5) + '...' + address.slice(len - 5, len) + (etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '') +// } + +// export const addressToString = (address) => { +// if (!address) return null +// if (typeof address !== 'string') { +// address = address.toString('hex') +// } +// if (address.indexOf('0x') === -1) { +// address = '0x' + address +// } +// return ethJSUtil.toChecksumAddress(address) +// } + +// export const shortenHexData = (data) => { +// if (!data) return '' +// if (data.length < 5) return data +// var len = data.length +// return data.slice(0, 5) + '...' + data.slice(len - 5, len) +// } + +// export const createNonClashingNameWithPrefix = (name, fileProvider, prefix, cb) => { +// if (!name) name = 'Undefined' +// var counter = '' +// var ext = 'sol' +// var reg = /(.*)\.([^.]+)/g +// var split = reg.exec(name) +// if (split) { +// name = split[1] +// ext = split[2] +// } +// var exist = true +// async.whilst( +// () => { return exist }, +// (callback) => { +// fileProvider.exists(name + counter + prefix + '.' + ext).then(currentExist => { +// exist = currentExist +// if (exist) counter = (counter | 0) + 1 +// callback() +// }).catch(error => { +// if (error) console.log(error) +// }) +// }, +// (error) => { cb(error, name + counter + prefix + '.' + ext) } +// ) +// } + +// export const createNonClashingName = (name, fileProvider, cb) => { +// this.createNonClashingNameWithPrefix(name, fileProvider, '', cb) +// }, +// export const createNonClashingNameAsync = async (name, fileManager, prefix = '') => { +// if (!name) name = 'Undefined' +// let counter = '' +// let ext = 'sol' +// const reg = /(.*)\.([^.]+)/g +// const split = reg.exec(name) +// if (split) { +// name = split[1] +// ext = split[2] +// } +// let exist = true + +// do { +// const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext) + +// if (isDuplicate) counter = (counter | 0) + 1 +// else exist = false +// } while (exist) + +// return name + counter + prefix + '.' + ext +// } + +// export const createNonClashingDirNameAsync = async (name, fileManager) => { +// if (!name) name = 'Undefined' +// let counter = '' +// let exist = true + +// do { +// const isDuplicate = await fileManager.exists(name + counter) + +// if (isDuplicate) counter = (counter | 0) + 1 +// else exist = false +// } while (exist) + +// return name + counter +// } + +// export const checkSpecialChars = (name) => { +// return name.match(/[:*?"<>\\'|]/) != null +// } + +// export const checkSlash = (name) => { +// return name.match(/\//) != null +// } + +// export const isHexadecimal = (value) => { +// return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0) +// } + +// export const is0XPrefixed = (value) => { +// return value.substr(0, 2) === '0x' +// } + +// export const isNumeric = (value) => { +// return /^\+?(0|[1-9]\d*)$/.test(value) +// } + +// export const isValidHash = (hash) => { // 0x prefixed, hexadecimal, 64digit +// const hexValue = hash.slice(2, hash.length) +// return this.is0XPrefixed(hash) && /^[0-9a-fA-F]{64}$/.test(hexValue) +// } + +// export const removeTrailingSlashes = (text) { +// // Remove single or consecutive trailing slashes +// return text.replace(/\/+$/g, '') +// }, +// removeMultipleSlashes (text) { +// // Replace consecutive slashes with '/' +// return text.replace(/\/+/g, '/') +// }, +// find: find, +// getPathIcon (path) { +// return path.endsWith('.txt') +// ? 'far fa-file-alt' : path.endsWith('.md') +// ? 'far fa-file-alt' : path.endsWith('.sol') +// ? 'fak fa-solidity-mono' : path.endsWith('.js') +// ? 'fab fa-js' : path.endsWith('.json') +// ? 'fas fa-brackets-curly' : path.endsWith('.vy') +// ? 'fak fa-vyper-mono' : path.endsWith('.lex') +// ? 'fak fa-lexon' : path.endsWith('.contract') +// ? 'fab fa-ethereum' : 'far fa-file' +// }, +// joinPath (...paths) { +// paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash) +// if (paths.length === 1) return paths[0] +// return paths.join('/') +// }, +// extractNameFromKey (key) { +// const keyPath = key.split('/') + +// return keyPath[keyPath.length - 1] +// } + +// const findDeep = (object, fn, found = { break: false, value: undefined }) => { +// if (typeof object !== 'object' || object === null) return +// for (var i in object) { +// if (found.break) break +// var el = object[i] +// if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText +// if (fn(el, i, object)) { +// found.value = el +// found.break = true +// break +// } else { +// findDeep(el, fn, found) +// } +// } +// return found.value +// } + +// const find = (args, query) => { +// query = query.trim() +// var isMatch = !!findDeep(args, function check (value, key) { +// if (value === undefined || value === null) return false +// if (typeof value === 'function') return false +// if (typeof value === 'object') return false +// var contains = String(value).indexOf(query.trim()) !== -1 +// return contains +// }) +// return isMatch +// }