|
|
@@ -30,71 +30,105 @@
|
|
|
* ## Making A Shell Class
|
|
|
*
|
|
|
* The shell class must be defined before the tag is mounted. Shells can keep
|
|
|
- * prompt and welcome message settings, and must define a `process()` function.
|
|
|
+ * prompt and welcome message settings, and must define a `run()` function.
|
|
|
* Here is the structure of a minimal shell that does nothing:
|
|
|
*
|
|
|
* // Contents of myshell.js
|
|
|
* function() myshell() {
|
|
|
* this.prompt = ''
|
|
|
* this.welcome = ''
|
|
|
- * this.process = function(input) {
|
|
|
+ * this.run = function(input, terminal) {
|
|
|
* return ''
|
|
|
* }
|
|
|
* }
|
|
|
*
|
|
|
- * // shell class used by Terminal Riot Tag in index.html
|
|
|
+ * // Shell class used by Terminal Riot Tag in index.html
|
|
|
* <terminal shell='myshell'></terminal>
|
|
|
*
|
|
|
- * ## Special Shell Return Values
|
|
|
- *
|
|
|
- * The terminal tag recognises some predefined shell return values and upon
|
|
|
- * receiving them, can perform actions other than printing output:
|
|
|
- *
|
|
|
- * - `'clear'`. Terminal will clear its output history.
|
|
|
+ * The `run()` function can take 2 parameters. The first will contain user input
|
|
|
+ * from the command line. The second will contain the terminal object itself -
|
|
|
+ * this will allow the shell access to all of the terminal's functions, mainly
|
|
|
+ * through its 2 child tags, `terminal.tags.display` and
|
|
|
+ * `terminal.tags.commandline`. Refer those tags below for capabilities.
|
|
|
+ *
|
|
|
+ * A shell can, at its most basic operation, return a string of text or HTML,
|
|
|
+ * and this will be appended to the display.
|
|
|
+ *
|
|
|
+ * ## Apps
|
|
|
+ *
|
|
|
+ * A shell can also return an object that will temporarily process all input,
|
|
|
+ * until the object returns control back to the shell. This can be thought of as
|
|
|
+ * the shell returning an "app" object.
|
|
|
+ *
|
|
|
+ * An "app" object should implement a `run()` function, just like the shell
|
|
|
+ * class. It should also have exit conditions under which it will return control
|
|
|
+ * to the shell by calling `terminal.returnToShell()`.
|
|
|
+ *
|
|
|
+ * {
|
|
|
+ * 'run': function(input, terminal) {
|
|
|
+ * terminal.tags.commandline.hidePrompt()
|
|
|
+ * terminal.tags.display.add('Hello! inputs will now come to this app.')
|
|
|
+ * if (input == 'exit') {
|
|
|
+ * terminal.tags.display.add('Bye! Returning control to the shell.')
|
|
|
+ * terminal.tags.commandline.showPrompt()
|
|
|
+ * terminal.returnToShell()
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }
|
|
|
*
|
|
|
+ * An "app" is an object, so can keep state information for a current run with
|
|
|
+ * `this`. For more persistent saving of information, it is also possible to
|
|
|
+ * save data to `terminal` or to `terminal.shell`.
|
|
|
*/
|
|
|
<terminal>
|
|
|
- <history welcome={ welcome } />
|
|
|
+ <display welcome={ welcome } />
|
|
|
<commandline prompt={ prompt } />
|
|
|
|
|
|
- // Take defaults from shell, otherwise take from terminal tag options.
|
|
|
- var shell = { 'process': function() { return ''; } }
|
|
|
+ // Create a new shell with the class name given to the terminal tag.
|
|
|
+ var shell = { 'run': function() { return ''; } }
|
|
|
this.shell = window[opts.shell] ? new window[opts.shell] : shell
|
|
|
- this.welcome = this.shell.welcome ? this.shell.welcome : opts.welcome
|
|
|
- this.prompt = this.shell.prompt ? this.shell.prompt : opts.prompt
|
|
|
+ this.active = this.shell
|
|
|
+ this.welcome = this.shell.welcome || opts.welcome
|
|
|
+ this.prompt = this.shell.prompt || opts.prompt
|
|
|
|
|
|
/**
|
|
|
* How to process a command:
|
|
|
+ *
|
|
|
* - Make the input safe by transforming html entities.
|
|
|
- * - Keep the last command in history as we go to the next line.
|
|
|
- * - Based on shell response, perform special actions or append the output.
|
|
|
- * - Set the prompt as required.
|
|
|
- * - Make sure all tags update their display content.
|
|
|
+ * - Keep the last command line on display before other output.
|
|
|
+ * - Based on shell response, append output, or give control to an app object.
|
|
|
+ * - Empty the command line of input.
|
|
|
+ * - Update the display.
|
|
|
*/
|
|
|
process(prompt, input) {
|
|
|
input = he.encode(input)
|
|
|
- var output = prompt + input + '\n'
|
|
|
- var response = this.shell.process(input)
|
|
|
- switch(response) {
|
|
|
- case 'clear':
|
|
|
- this.clear()
|
|
|
- break
|
|
|
- default:
|
|
|
- this.tags.history.add(output + response)
|
|
|
+ this.tags.display.add(prompt + input + '\n')
|
|
|
+ var response = this.active.run(input, this)
|
|
|
+ if (typeof response === 'string') {
|
|
|
+ this.tags.display.add(response)
|
|
|
+ }
|
|
|
+ if (typeof response === 'object' && typeof response.run === 'function') {
|
|
|
+ this.active = response
|
|
|
+ response.run(input, this)
|
|
|
}
|
|
|
- this.tags.commandline.setPrompt(this.shell.prompt)
|
|
|
this.tags.commandline.command.value = ''
|
|
|
this.update()
|
|
|
}
|
|
|
|
|
|
- clear() {
|
|
|
- this.tags.history.hist = []
|
|
|
+ /**
|
|
|
+ * Make the shell the active process.
|
|
|
+ *
|
|
|
+ * For app objects to return control to the shell when finished.
|
|
|
+ */
|
|
|
+ returnToShell() {
|
|
|
+ this.active = this.shell
|
|
|
}
|
|
|
</terminal>
|
|
|
|
|
|
<commandline>
|
|
|
<form autocomplete='off' onsubmit={ process }>
|
|
|
- <raw name='lhs' content={ prompt } /><input type='text' name='command' />
|
|
|
+ <raw name='lhs' content={ prompt } show={ visible } />
|
|
|
+ <input type='text' name='command' />
|
|
|
</form>
|
|
|
|
|
|
<style>
|
|
|
@@ -106,12 +140,20 @@
|
|
|
}
|
|
|
</style>
|
|
|
|
|
|
+ this.prompt = opts.prompt || '$ '
|
|
|
+ this.visible = true
|
|
|
+
|
|
|
this.on('mount', function() {
|
|
|
document.getElementsByName('command')[0].focus()
|
|
|
})
|
|
|
|
|
|
+ process() {
|
|
|
+ var prompt = this.visible ? this.prompt : '';
|
|
|
+ this.parent.process(prompt, this.command.value)
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * Set the prompt to any truthy value.
|
|
|
+ * Set the prompt value.
|
|
|
*
|
|
|
* Note about `lhs.write()` check:
|
|
|
* Terminal tag setup fires `setPrompt()` before `lhs.write()` exists.
|
|
|
@@ -119,31 +161,60 @@
|
|
|
* After that, `lhs.write()` is called to make all subsequent prompt changes.
|
|
|
*/
|
|
|
setPrompt(value) {
|
|
|
- if (value) {
|
|
|
- if (typeof this.tags.lhs.write != 'undefined') {
|
|
|
- this.tags.lhs.write(value)
|
|
|
- }
|
|
|
- this.prompt = value
|
|
|
+ if (typeof this.tags.lhs.write !== 'undefined') {
|
|
|
+ this.tags.lhs.write(value)
|
|
|
}
|
|
|
+ this.prompt = value
|
|
|
}
|
|
|
|
|
|
- process() {
|
|
|
- this.parent.process(this.prompt, this.command.value)
|
|
|
+ hidePrompt() {
|
|
|
+ this.visible = false
|
|
|
}
|
|
|
|
|
|
- this.prompt = '$ '
|
|
|
- this.setPrompt(opts.prompt)
|
|
|
+ showPrompt() {
|
|
|
+ this.visible = true
|
|
|
+ }
|
|
|
</commandline>
|
|
|
|
|
|
-<history>
|
|
|
- <div each={ hist }>
|
|
|
+<display>
|
|
|
+ <div each={ output }>
|
|
|
<raw content={ content } />
|
|
|
</div>
|
|
|
|
|
|
- add(output) {
|
|
|
- if (output) {
|
|
|
- output = this.preserveWhiteSpace(output)
|
|
|
- this.hist.push({ 'content': output })
|
|
|
+ add(text) {
|
|
|
+ if (text) {
|
|
|
+ text = this.preserveWhiteSpace(text)
|
|
|
+ this.output.push({ 'content': text })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ set(text) {
|
|
|
+ if (text) {
|
|
|
+ this.clear()
|
|
|
+ this.add(text)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Empty the display.
|
|
|
+ *
|
|
|
+ * An extra update is needed for subsequent changes to `output` (e.g. `add()`,
|
|
|
+ * `restore()`) to display properly.
|
|
|
+ */
|
|
|
+ clear() {
|
|
|
+ this.output.length = 0
|
|
|
+ this.update()
|
|
|
+ }
|
|
|
+
|
|
|
+ hide() {
|
|
|
+ this.saved = this.output.splice(0, this.output.length)
|
|
|
+ this.clear()
|
|
|
+ }
|
|
|
+
|
|
|
+ restore() {
|
|
|
+ if (this.saved.length > 0) {
|
|
|
+ this.clear()
|
|
|
+ this.output = this.saved.splice(0, this.saved.length)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -165,16 +236,16 @@
|
|
|
return text
|
|
|
}
|
|
|
|
|
|
- this.hist = []
|
|
|
+ this.output = []
|
|
|
this.add(opts.welcome)
|
|
|
-</history>
|
|
|
+</display>
|
|
|
|
|
|
<raw>
|
|
|
<span></span>
|
|
|
|
|
|
// Initialise contents from tag options, but expose `write()` for updating.
|
|
|
- write(value) {
|
|
|
- this.root.innerHTML = value
|
|
|
+ write(text) {
|
|
|
+ this.root.innerHTML = text
|
|
|
}
|
|
|
this.write(opts.content)
|
|
|
</raw>
|