Ver Fonte

Add app support to terminal, Refactor names

Weiyi Lou há 10 anos atrás
pai
commit
4721603d1f
1 ficheiros alterados com 121 adições e 50 exclusões
  1. 121 50
      tags/terminal.tag

+ 121 - 50
tags/terminal.tag

@@ -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>