Commit 2f9b7329 authored by PaddyMc's avatar PaddyMc Committed by Rob Stupay

Issue#1462: Terminal command autocomplete support

parent 3d59babe
const allPrograms = [
{'ethers': 'The ethers.js library is a compact and complete JavaScript library for Ethereum.'},
{'remix': 'Ethereum IDE and tools for the web.'},
{'web3': 'The web3.js library is a collection of modules which contain specific functionality for the ethereum ecosystem.'},
{'swarmgw': 'This library can be used to upload/download files to Swarm via https://swarm-gateways.net/.'}
]
const allCommands = [
{'remix.debug(hash)': 'Start debugging a transaction.'},
{'remix.debugHelp()': 'Display help message for debugging'},
{'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.'},
{'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 or ipfs.'},
{'remix.setproviderurl(url)': 'Change the current provider to Web3 provider and set the url endpoint.'},
{'swarmgw.get(url, cb)': 'Download files from Swarm via https://swarm-gateways.net/'},
{'swarmgw.put(content, cb)': 'Upload files to Swarm via https://swarm-gateways.net/'},
{'ethers.Contract': 'This API provides a graceful connection to a contract deployed on the blockchain, simplifying calling and querying its functions and handling all the binary protocol and conversion as necessarily.'},
{'ethers.HDNode': 'A Hierarchical Deterministic Wallet represents a large tree of private keys which can reliably be reproduced from an initial seed.'},
{'ethers.Interface': 'The Interface Object is a meta-class that accepts a Solidity (or compatible) Application Binary Interface (ABI) and populates functions to deal with encoding and decoding the parameters to pass in and results returned.'},
{'ethers.providers': 'A Provider abstracts a connection to the Ethereum blockchain, for issuing queries and sending state changing transactions.'},
{'ethers.SigningKey': 'The SigningKey interface provides an abstraction around the secp256k1 elliptic curve cryptography library.'},
{'ethers.utils': 'The utility functions exposed in both the ethers umbrella package and the ethers-utils.'},
{'ethers.utils.AbiCoder': 'Create a new ABI Coder object'},
{'ethers.utils.RLP': 'This encoding method is used internally for several aspects of Ethereum, such as encoding transactions and determining contract addresses.'},
{'ethers.Wallet': 'A wallet manages a private/public key pair which is used to cryptographically sign transactions and prove ownership on the Ethereum network.'},
{'ethers.version': 'Contains the version of the ethers container object.'},
{'web3.bzz': 'Bzz module for interacting with the swarm network.'},
{'web3.eth': 'Eth module for interacting with the Ethereum network.'},
{'web3.eth.accounts': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.'},
{'web3.eth.abi': 'The web3.eth.abi functions let you de- and encode parameters to ABI (Application Binary Interface) for function calls to the EVM (Ethereum Virtual Machine).'},
{'web3.eth.ens': 'The web3.eth.ens functions let you interacting with ENS.'},
{'web3.eth.Iban': 'The web3.eth.Iban function lets convert Ethereum addresses from and to IBAN and BBAN.'},
{'web3.eth.net': 'Net module for interacting with network properties.'},
{'web3.eth.personal': 'Personal module for interacting with the Ethereum accounts.'},
{'web3.eth.subscribe': 'The web3.eth.subscribe function lets you subscribe to specific events in the blockchain.'},
{'web3.givenProvider': 'When using web3.js in an Ethereum compatible browser, it will set with the current native provider by that browser. Will return the given provider by the (browser) environment, otherwise null.'},
{'web3.modules': 'Contains the version of the web3 container object.'},
{'web3.providers': 'Contains the current available providers.'},
{'web3.shh': 'Shh module for interacting with the whisper protocol'},
{'web3.utils': 'This package provides utility functions for Ethereum dapps and other web3.js packages.'},
{'web3.version': 'Contains the version of the web3 container object.'},
{'web3.eth.clearSubscriptions();': 'Resets subscriptions.'},
{'web3.eth.Contract(jsonInterface[, address][, options])': 'The web3.eth.Contract object makes it easy to interact with smart contracts on the ethereum blockchain.'},
{'web3.eth.accounts.create([entropy]);': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.'}
]
module.exports = {
allPrograms,
allCommands
}
......@@ -13,6 +13,8 @@ var swarmgw = require('swarmgw')()
var CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var executionContext = require('../../execution-context')
var Dropdown = require('../ui/dropdown')
var AutoCompletePopup = require('../ui/auto-complete-popup')
var Commands = require('../constants/commands')
var csjs = require('csjs-inject')
var styleGuide = require('../ui/styles-guide/theme-chooser')
......@@ -62,6 +64,17 @@ class Terminal {
self.updateJournal({ type: 'select', value: label })
}
})
self._components.autoCompletePopup = new AutoCompletePopup()
self._components.autoCompletePopup.event.register('handleSelect', function (input) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
self._view.input.innerText = input
self._view.input.focus()
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
})
self._components.autoCompletePopup.event.register('updateList', function () {
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
})
self._commands = {}
self.commands = {}
self._JOURNAL = []
......@@ -149,10 +162,12 @@ class Terminal {
</div>
</div>
`
self._view.autoCompletePopup = self._components.autoCompletePopup.render()
self._view.el = yo`
<div class=${css.panel}>
${self._view.bar}
${self._view.term}
${self._view.autoCompletePopup}
</div>
`
setInterval(() => {
......@@ -391,12 +406,14 @@ class Terminal {
return self._view.el
function change (event) {
handleAutoComplete(event)
if (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n'
putCursor2End(self._view.input)
self.scroll2bottom()
removeAutoComplete()
} else { // <enter>
self._cmdIndex = -1
self._cmdTemp = ''
......@@ -407,6 +424,7 @@ class Terminal {
self._cmdHistory.unshift(script)
self.commands.script(script)
}
removeAutoComplete()
}
} else if (event.which === 38) { // <arrowUp>
var len = self._cmdHistory.length
......@@ -417,6 +435,7 @@ class Terminal {
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
putCursor2End(self._view.input)
self.scroll2bottom()
removeAutoComplete()
} else if (event.which === 40) { // <arrowDown>
if (self._cmdIndex > -1) {
self._cmdIndex--
......@@ -424,6 +443,7 @@ class Terminal {
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
putCursor2End(self._view.input)
self.scroll2bottom()
removeAutoComplete()
} else {
self._cmdTemp = self._view.input.innerText
}
......@@ -455,6 +475,40 @@ class Terminal {
editable.focus()
}
function handleAutoComplete (event) {
if (event.which === 9) {
event.preventDefault()
if (self._view.input.innerText.length >= 2) {
self._components.autoCompletePopup.data._options = []
Commands.allPrograms.forEach(item => {
if (Object.keys(item)[0].substring(0, Object.keys(item)[0].length - 1).includes(self._view.input.innerText.trim())) {
self._components.autoCompletePopup.data._options.push(item)
} else if (self._view.input.innerText.trim().includes(Object.keys(item)[0]) || (Object.keys(item)[0] === self._view.input.innerText.trim())) {
Commands.allCommands.forEach(item => {
if (Object.keys(item)[0].includes(self._view.input.innerText.trim())) {
self._components.autoCompletePopup.data._options.push(item)
}
})
}
})
}
if (self._components.autoCompletePopup.data._options.length === 1) {
self._view.input.innerText = Object.keys(self._components.autoCompletePopup.data._options[0])[0]
self._components.autoCompletePopup.data._options = []
putCursor2End(self._view.input)
}
}
if (event.which === 27 || event.which === 8 || event.which === 46) {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
}
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
function removeAutoComplete () {
self._components.autoCompletePopup.data._options = []
self._components.autoCompletePopup._startingElement = 0
yo.update(self._view.autoCompletePopup, self._components.autoCompletePopup.render())
}
}
updateJournal (filterEvent) {
var self = this
......
var yo = require('yo-yo')
var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
// -------------- styling ----------------------
var css = require('./styles/auto-complete-popup-styles')
/* USAGE:
var autoCompletePopup = new AutoCompletePopup({
options: []
})
autoCompletePopup.event.register('handleSelect', function (input) { })
autoCompletePopup.event.register('updateList', function () { })
*/
class AutoCompletePopup {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.data = {
_options: opts.options || []
}
self._view = {}
self._startingElement = 0
self._elementsToShow = 3
}
render () {
var self = this
self._view.autoComplete = yo`
<div class="${css.popup}">
<div class="${css.popupcontent}">
${self.data._options.map((item, index) => {
return yo`
<div class="${css.listHandlerHide}">
<hr/>
<a value=${index}>
<div onclick=${handleSelect}>
${Object.keys(item)[0]}
</div>
</a>
<div>
${Object.values(item)[0]}
</div>
<hr/>
</div>
`
})}
</div>
<button class="${css.listHandlerHide}" value=false onclick=${handleListIteration}>▲</button>
<button class="${css.listHandlerHide}" value=true onclick=${handleListIteration}>▼</button>
<div class="${css.listHandlerHide}">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}</div>
</div>
`
handleNagivationButtons()
handleListSize()
return self._view.autoComplete
function handleSelect (event) {
self.event.trigger('handleSelect', [event.srcElement.innerText])
}
function handleNagivationButtons () {
if (self.data._options.length > self._elementsToShow) {
self._view.autoComplete.children[1].className = css.listHandlerButtonShow
self._view.autoComplete.children[2].className = css.listHandlerButtonShow
self._view.autoComplete.children[3].className = css.pageNumberAlignment
}
}
function handleListSize () {
if (self.data._options.length >= self._startingElement) {
for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) {
if (self._view.autoComplete.children[0].children[i]) {
self._view.autoComplete.children[0].children[i].className = css.listHandlerShow
}
}
}
}
function handleListIteration (event) {
if (event.srcElement.value === 'true') {
if ((self._startingElement + self._elementsToShow) < self.data._options.length) {
self._startingElement += self._elementsToShow
}
} else {
if (self._startingElement > 0) {
self._startingElement -= self._elementsToShow
}
}
self.event.trigger('updateList')
}
}
}
module.exports = AutoCompletePopup
var csjs = require('csjs-inject')
var styleGuide = require('../styles-guide/theme-chooser')
var styles = styleGuide.chooser()
var css = csjs`
.popup {
text-align : center;
z-index : 1;
position : absolute;
width : 100%;
justify-content : center;
bottom : 0;
margin-bottom : 32px;
border-radius : 6px;
padding : 8px 0;
opacity : 0.8;
}
.popupcontent {
display : block;
position : absolute;
background-color : #f1f1f1;
width : 100%;
box-shadow : 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index : 1;
bottom : 100%;
}
.popupcontent a {
color : ${styles.terminal.text_Primary};
font-family : monospace;
font-size : 10px;
display : block;
}
.popupcontent div {
font-family : monospace;
font-size : 10px;
}
.popupcontent a:hover {
background-color : #ddd;
}
.listHandlerShow {
display : block;
}
.listHandlerHide {
display : none;
}
.listHandlerButtonShow {
display : inline;
float : center;
opacity : 0.8;
}
.pageNumberAlignment {
display : inline;
position : absolute;
padding-right : 10px;
font-family : monospace;
font-size : 10px;
margin-left : 30%;
}
`
module.exports = css
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment