diff --git a/lib/client/console/Cakefile b/lib/client/console/Cakefile
new file mode 100644
index 00000000..af53e80d
--- /dev/null
+++ b/lib/client/console/Cakefile
@@ -0,0 +1,16 @@
+{spawn, exec} = require 'child_process'
+
+task 'watch', 'Build and watch the CoffeeScript source files', ->
+ coffee = spawn 'coffee', ['-cw', '-o', 'lib', 'src']
+ test = spawn 'coffee', ['-cw', 'test']
+ log = (d)-> console.log d.toString()
+ coffee.stdout.on 'data', log
+ test.stdout.on 'data', log
+
+task 'build', 'Build minified file with uglify', ->
+ console.log 'building...'
+ exec 'uglifyjs -o jqconsole.min.js lib/jqconsole.js', (err, res)->
+ if err
+ console.error 'failed with', err
+ else
+ console.log 'build complete'
diff --git a/lib/client/console/ChangeLog b/lib/client/console/ChangeLog
new file mode 100644
index 00000000..85ae8052
--- /dev/null
+++ b/lib/client/console/ChangeLog
@@ -0,0 +1,68 @@
+2011.08.28, Version 2.4.2
+
+* Fix for issue #13 .
+* Add optional parameter "escape" to the Write method, see README for more
+info.
+
+20011.10.9, Version 2.5
+
+* Added mobile support.
+* Follow the cursor's position when the window is scrolled away.
+* Add async_multiline option in prompt, see README for more info.
+* Add Dump method that would dump the console's text content.
+* Add GetState method that gets the current state of the console.
+* Publicize MoveToStart and MoveToEnd methods.
+
+2011.11.1, Version 2.5.1
+
+* Added Disable/Enable functionality and methods.
+* Added IsDisabled method.
+
+2011.11.3, Version 2.5.2
+
+* Added multibyte character input support, issue #19.
+
+2011.11.4, Version 2.6
+
+* Fix safari paste. Issue #15.
+* Created constants and minifier friendliness. Issue #14.
+
+2011.11.19, Version 2.6.1
+
+* Fix issues #20, #21
+* Implement feature #22
+* Add built-in css for ease of use.
+
+2011.12.6, Version 2.6.2
+* Fix issue #23.
+
+2011.12.16 Version 2.6.3
+* Fix issue #24.
+
+2011.12.28 Version 2.7
+* Implement ANSI graphics, Issue #12
+* Fix issue #25
+
+2012.3.7 Version 2.7.1
+* Fix issue #26
+* Complete issue #12 by adding stacking graphics support.
+
+2012.10.28 Version 2.7.2
+* Add set / get History methods.
+* Add Append method.
+
+2012.11.10 Version 2.7.3
+* Allow empty string in prompt.
+
+2012.12.13 Version 2.7.4
+* Fix issue #36
+* Fix issue #35
+
+2013.1.21 Version 2.7.5
+* Add SetPromptLabel method.
+
+2013.1.22 Version 2.7.6
+* Continue label argument in SetPromptLabel method.
+
+2013.1.26 Version 2.7.7
+* Support for middle click paste on linux. #47.
diff --git a/lib/client/console/README.md b/lib/client/console/README.md
new file mode 100644
index 00000000..7eada46b
--- /dev/null
+++ b/lib/client/console/README.md
@@ -0,0 +1,723 @@
+#jq-console
+
+A jQuery terminal plugin written in CoffeeScript.
+
+This project was spawned because of our need for a simple web terminal plugin
+for the repl.it project. It tries to simulate a low level terminal by providing (almost)
+raw input/output streams as well as input and output states.
+
+Version 2.0 adds baked-in support for rich multi-line prompting and operation
+queueing.
+
+
+##Tested Browsers
+
+The plugin has been tested on the following browsers:
+
+* IE 9+
+* Chrome 10+
+* Firefox 4+
+* Opera 11+
+* iOS 4+
+
+
+##Getting Started
+
+###Echo example
+
+```css
+ /* The console container element */
+ #console {
+ position: absolute;
+ width: 400px;
+ height: 500px;
+ background-color:black;
+ }
+ /* The inner console element. */
+ .jqconsole {
+ padding: 10px;
+ }
+ /* The cursor. */
+ .jqconsole-cursor {
+ background-color: gray;
+ }
+ /* The cursor color when the console looses focus. */
+ .jqconsole-blurred .jqconsole-cursor {
+ background-color: #666;
+ }
+ /* The current prompt text color */
+ .jqconsole-prompt {
+ color: #0d0;
+ }
+ /* The command history */
+ .jqconsole-old-prompt {
+ color: #0b0;
+ font-weight: normal;
+ }
+ /* The text color when in input mode. */
+ .jqconsole-input {
+ color: #dd0;
+ }
+ /* Previously entered input. */
+ .jqconsole-old-input {
+ color: #bb0;
+ font-weight: normal;
+ }
+ /* The text color of the output. */
+ .jqconsole-output {
+ color: white;
+ }
+```
+
+```html
+
+
+
+
+```
+
+
+###Instantiating
+
+```javascript
+ $(div).jqconsole(welcomeString, promptLabel, continueLabel);
+```
+
+* `div` is the div element or selector. Note that this element must be
+ explicity sized and positioned `absolute` or `relative`.
+* `welcomeString` is the string to be shown when the terminal is first rendered.
+* `promptLabel` is the label to be shown before the input when using Prompt().
+* `continueLabel` is the label to be shown before the continued lines of the
+ input when using Prompt().
+
+##Configuration
+
+There isn't much initial configuration needed, because the user must supply
+options and callbacks with each state change. There are a few config methods
+provided to create custom shortcuts and change indentation width:
+
+###jqconsole.RegisterShortcut
+Registers a callback for a keyboard shortcut.
+Takes two arguments:
+
+ * __(int|string)__ *keyCode*: The code of the key pressing which (when Ctrl is
+ held) will trigger this shortcut. If a string is provided, the ASCII code
+ of the first character is taken.
+
+ * __function__ *callback*: A function called when the shortcut is pressed;
+ "this" will point to the JQConsole object.
+
+
+ Example:
+
+ // Ctrl+R: resets the console.
+ jqconsole.RegisterShortcut('R', function() {
+ this.Reset();
+ });
+
+###jqconsole.SetIndentWidth
+Sets the number of spaces inserted when indenting and removed when unindenting.
+Takes one argument:
+
+ * __int__ *width*: The code of the key pressing which (when Ctrl is held) will
+ trigger this shortcut.
+
+
+ Example:
+
+ // Sets the indent width to 4 spaces.
+ jqconsole.SetIndentWidth(4);
+
+###jqconsole.RegisterMatching
+Registers an opening and closing characters to match and wraps each of the
+opening and closing characters with a span with the specified class.
+Takes one parameters:
+
+ * __char__ *open*: The opening character of a "block".
+ * __char__ *close*: The closing character of a "block".
+ * __string__ *class*: The css class that is applied to the matched characters.
+
+
+ Example:
+
+ jqconsole.RegisterMatching('{', '}', 'brackets');
+
+##Usage
+
+Unlike most terminal plugins, jq-console gives you complete low-level control
+over the execution; you have to call the appropriate methods to start input
+or output:
+
+###jqconsole.Input:
+Asks user for input. If another input or prompt operation is currently underway,
+the new input operation is enqueued and will be called when the current
+operation and all previously enqueued operations finish. Takes one argument:
+
+ * __function__ *input_callback*: A function called with the user's input when
+ the user presses Enter and the input operation is complete.
+
+
+ Example:
+
+ // Echo the input.
+ jqconsole.Input(function(input) {
+ jqconsole.Write(input);
+ });
+
+
+###jqconsole.Prompt
+Asks user for input. If another input or prompt operation is currently underway
+the new prompt operation is enqueued and will be called when the current
+peration and all previously enqueued operations finish. Takes three arguments:
+
+ * __bool__ *history_enabled*: Whether this input should use history. If true,
+ the user can select the input from history, and their input will also be
+ added as a new history item.
+
+ * __function__ *result_callback*: A function called with the user's input when
+ the user presses Enter and the prompt operation is complete.
+
+ * __function__ *multiline_callback*: If specified, this function is called when
+ the user presses Enter to check whether the input should continue to the
+ next line. The function must return one of the following values:
+
+ * `false`: the input operation is completed.
+
+ * `0`: the input continues to the next line with the current indent.
+
+ * `N` (int): the input continues to the next line, and the current
+ indent is adjusted by `N`, e.g. `-2` to unindent two levels.
+
+
+ * __bool__ *async_multiline*: Whether the multiline callback function should
+ be treated as an asynchronous operation and be passed a continuation
+ function that should be called with one of the return values mentioned
+ above: `false`/`0`/`N`.
+
+
+ Example:
+
+ jqconsole.Prompt(true, function(input) {
+ // Alert the user with the command.
+ alert(input);
+ }, function (input) {
+ // Continue if the last character is a backslash.
+ return /\\$/.test(input);
+ });
+
+###jqconsole.AbortPrompt
+Aborts the current prompt operation and returns to output mode or the next
+queued input/prompt operation. Takes no arguments.
+
+ Example:
+
+ jqconsole.Prompt(true, function(input) {
+ alert(input);
+ });
+ // Give the user 2 seconds to enter the command.
+ setTimeout(function() {
+ jqconsole.AbortPrompt();
+ }, 2000);
+
+###jqconsole.Write
+Writes the given text to the console in a ``, with an
+optional class. If a prompt is currently being shown, the text is inserted
+before it. Takes two arguments:
+
+ * __string__ *text*: The text to write.
+
+ * __string__ *cls*: The class to give the span containing the text. Optional.
+
+ * __bool__ *escape*: Whether the text to write should be html escaped.
+ Optional, defaults to true.
+
+
+ Examples:
+
+ jqconsole.Write(output, 'my-output-class')
+ jqconsole.Write(err.message, 'my-error-class')
+
+###jqconsole.Append
+Append the given node to the DOM. If a prompt is currently being shown, the
+text is inserted before it. Takes a single argument:
+
+ * __(string|Element)__ *node*: The DOM Element or html string to append to
+ the console just before the prompt.
+
+ Example:
+
+ // Add a div with the text 'hello' on a red background using jquery
+ jqconsole.Append($('hello
').css('background-color', 'red'));
+
+ // We can also use document.createElement
+ node = document.createElement("div");
+ content = document.createTextNode("hello");
+ node.appendChild(content);
+ jqconsole.Append(node);
+
+
+###jqconsole.SetPromptText
+Sets the text currently in the input prompt. Takes one parameter:
+
+ * __string__ *text*: The text to put in the prompt.
+
+ Examples:
+
+ jqconsole.SetPromptText('ls')
+ jqconsole.SetPromptText('print [i ** 2 for i in range(10)]')
+
+
+###jqconsole.SetPromptLabel
+Replaces the main prompt label. Takes two parameters:
+
+ * __string__ *main_label*: String to replace the main prompt label.
+ * __string__ *continuation_label*: String to replace the continuation prompt label. Optional.
+
+ Examples:
+
+ jqconsole.SetPromptLabel('$')
+ jqconsole.SetPromptLabel(' $','..')
+
+
+###jqconsole.ClearPromptText
+Clears all the text currently in the input prompt. Takes one parameter:
+
+ * __bool__ *clear_label*: If specified and true, also clears the main prompt
+ label (e.g. ">>>").
+
+
+ Example:
+
+ jqconsole.ClearPromptText()
+
+
+###jqconsole.GetPromptText
+Returns the contents of the prompt. Takes one parameter:
+
+ * __bool__ *full*: If specified and true, also includes the prompt labels
+ (e.g. ">>>").
+
+
+ Examples:
+
+ var currentCommand = jqconsole.GetPromptText()
+ var logEntry = jqconsole.GetPromptText(true)
+
+
+###jqconsole.Reset
+Resets the console to its initial state, cancelling all current and pending
+operations. Takes no parameters.
+
+ Example:
+
+ jqconsole.Reset()
+
+
+###jqconsole.GetColumn
+Returns the 0-based number of the column on which the cursor currently is.
+Takes no parameters.
+
+ Example:
+
+ // Show the current line and column in a status area.
+ $('#status').text(jqconsole.GetLine() + ', ' + jqconsole.GetColumn())
+
+
+###jqconsole.GetLine
+Returns the 0-based number of the line on which the cursor currently is.
+Takes no parameters.
+
+ Example:
+
+ // Show the current line and column in a status area.
+ $('#status').text(jqconsole.GetLine() + ', ' + jqconsole.GetColumn())
+
+###jqconsole.Focus
+Forces the focus onto the console so events can be captured.
+Takes no parameters.
+
+ Example:
+
+ // Redirect focus to the console whenever the user clicks anywhere.
+ $(window).click(function() {
+ jqconsole.Focus();
+ })
+
+
+###jqconsole.GetIndentWidth
+Returns the number of spaces inserted when indenting. Takes no parameters.
+
+ Example:
+
+ jqconsole.SetIndentWidth(4);
+ console.assert(jqconsole.GetIndentWidth() == 4);
+
+
+###jqconsole.UnRegisterMatching
+Deletes a certain matching settings set by `jqconsole.RegisterMatching`.
+Takes two paramaters:
+
+ * __char__ *open*: The opening character of a "block".
+ * __char__ *close*: The closing character of a "block".
+
+
+ Example:
+
+ jqconsole.UnRegisterMatching('{', '}');
+
+
+###jqconsole.Dump
+Returns the text content of the console.
+
+###jqconsole.GetState
+Returns the current state of the console. Could be one of the following:
+
+ * Input: `"input"`
+ * Output: `"output"`
+ * Prompt: `"prompt"`
+
+
+ Example:
+
+ jqconsole.GetState(); //output
+
+
+###jqconsole.MoveToStart
+Moves the cursor to the start of the current line.
+Takes one parameter:
+
+ * __bool__ *all_lines*: If true moves the cursor to the beginning of the first
+ line in the current prompt. Defaults to false.
+
+
+ Example:
+
+ // Move to line start Ctrl+A.
+ jqconsole.RegisterShortcut('A', function() {
+ jqconsole.MoveToStart();
+ handler();
+ });
+
+
+###jqconsole.MoveToEnd
+Moves the cursor to the end of the current line.
+Takes one parameter:
+
+ * __bool__ *all_lines*: If true moves the cursor to the end of the first
+ line in the current prompt. Defaults to false.
+
+ Example:
+
+ // Move to line end Ctrl+E.
+ jqconsole.RegisterShortcut('E', function() {
+ jqconsole.MoveToEnd();
+ handler();
+ });
+
+###jqconsole.Disable
+Disables input and focus on the console.
+
+
+###jqconsole.Enable
+Enables input and focus on the console.
+
+
+###jqconsole.IsDisabled
+Returns true if the console is disabled.
+
+
+###jqconsole.GetHistory
+Returns the contents of the history buffer.
+
+
+###jqconsole.SetHistory
+Set the history buffer to the given array.
+
+Takes one parameter:
+
+ * __array__ *history*: The history buffer to use.
+
+ Example:
+
+ jqconsole.SetHistory(['a = 3', 'a + 3']);
+
+
+###jqconsole.ResetHistory
+Resets the console history.
+
+
+###jqconsole.ResetMatchings
+Resets the character matching configuration.
+
+
+###jqconsole.ResetShortcuts
+Resets the shortcut configuration.
+
+
+##Default Key Config
+
+The console responds to the followind keys and key combinations by default:
+
+* `Delete`: Delete the following character.
+* `Ctrl+Delete`: Delete the following word.
+* `Backspace`: Delete the preceding character.
+* `Ctrl+Backspace`: Delete the preceding word.
+* `Ctrl+Left`: Move one word to the left.
+* `Ctrl+Right`: Move one word to the right.
+* `Home`: Move to the beginning of the current line.
+* `Ctrl+Home`: Move to the beginnig of the first line.
+* `End`: Move to the end of the current line.
+* `Ctrl+End`: Move to the end of the last line.
+* `Shift+Up`, `Ctrl+Up`: Move cursor to the line above the current one.
+* `Shift+Down`, `Ctrl+Down`: Move cursor to the line below the current one.
+* `Tab`: Indent.
+* `Shift+Tab`: Unindent.
+* `Up`: Previous history item.
+* `Down`: Next history item.
+* `Enter`: Finish input/prompt operation. See Input() and Prompt() for details.
+* `Shift+Enter`: New line.
+* `Page Up`: Scroll console one page up.
+* `Page Down`: Scroll console one page down.
+
+##ANSI escape code SGR support
+
+jq-console implements a large subset of the ANSI escape code graphics.
+Using the `.Write` method you could add style to the console using
+the following syntax:
+
+`ASCII 27 (decimal) or 0x1b (hex)` `[` `SGR code` `m`
+
+Example:
+
+ jqconsole.Write('\033[31mRed Text');
+
+Note that the third parameter `escape` must be true which defaults to it.
+
+You'll need to include the `ansi.css` file for default effects or create your
+own using the css classes from the table below.
+
+###SGR
+[Reference](http://en.wikipedia.org/wiki/ANSI_escape_code#graphics).
+
+
+ | Code |
+ Effect |
+ Class |
+
+
+ | 0 |
+ Reset / Normal |
+ |
+
+
+ | 1 |
+ Bold |
+ `jqconsole-ansi-bold` |
+
+
+ | 2 |
+ Faint |
+ `jqconsole-ansi-lighter` |
+
+
+ | 3 |
+ Italic |
+ `jqconsole-ansi-italic` |
+
+
+ | 4 |
+ Line below text |
+ `jqconsole-ansi-underline` |
+
+
+ | 5 |
+ Blink: 1s delay |
+ `jqconsole-ansi-blink` |
+
+
+ | 6 |
+ Blink: 0.5s delay |
+ `jqconsole-ansi-blink-rapid` |
+
+
+ | 8 |
+ Hide text |
+ `jqconsole-ansi-hidden` |
+
+
+ | 9 |
+ Line through text |
+ `jqconsole-ansi-line-through` |
+
+
+ | 10 |
+ Remove all fonts |
+ |
+
+
+ | 11-19 |
+ Add custom font |
+ `jqconsole-ansi-fonts-{N}` where N is code - 10 |
+
+
+ | 20 |
+ Add Fraktur font (not implemented in ansi.css) |
+ `jqconsole-ansi-fraktur` |
+
+
+ | 21 |
+ Remove Bold and Faint effects |
+ |
+
+
+ | 22 |
+ Same as 21 |
+ |
+
+
+ | 23 |
+ Remove italic and fraktur effects |
+ |
+
+
+ | 24 |
+ Remove underline effect |
+ |
+
+
+ | 25 |
+ Remove blinking effect(s). |
+ |
+
+
+ | 28 |
+ Reveal text |
+ |
+
+
+ | 29 |
+ Remove line-through effect |
+ |
+
+
+ | 30-37 |
+ Set foreground color to color from the color table below |
+ jqconsole-ansi-color-{COLOR} where {COLOR} is the color name |
+
+
+ | 39 |
+ Restore default foreground color |
+ |
+
+
+ | 40-47 |
+ Set background color to color from the color table below |
+ `jqconsole-ansi-background-color-{COLOR}` where {COLOR} is the color name |
+
+
+ | 49 |
+ Restore default background color |
+ |
+
+
+ | 51 |
+ Adds a frame around the text |
+ `jqconsole-ansi-framed` |
+
+
+ | 53 |
+ Line above text |
+ jqconsole-ansi-overline |
+
+
+ | 54 |
+ Remove frame effect |
+ |
+
+
+ | 55 |
+ Remove over-line effect |
+ |
+
+
+
+###Colors
+[Reference](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors).
+
+
+ | Code offset |
+ Color |
+
+
+ | 0 |
+ Black |
+
+
+ | 1 |
+ Red |
+
+
+ | 2 |
+ Green |
+
+
+ | 3 |
+ Yellow |
+
+
+ | 4 |
+ Blue |
+
+
+ | 5 |
+ Magenta |
+
+
+ | 6 |
+ Cyan |
+
+
+ | 7 |
+ White |
+
+
+
+##CSS Classes
+
+Several CSS classes are provided to help stylize the console:
+
+* `jqconsole`: The main console container.
+* `jqconsole, jqconsole-blurred`: The main console container, when not in focus.
+* `jqconsole-cursor`: The cursor.
+* `jqconsole-header`: The welcome message at the top of the console.
+* `jqconsole-input`: The prompt area during input. May have multiple lines.
+* `jqconsole-old-input`: Previously-entered inputs.
+* `jqconsole-prompt`: The prompt area during prompting. May have multiple lines.
+* `jqconsole-old-prompt`: Previously-entered prompts.
+* `jqconsole-composition`: The div encapsulating the composition of multi-byte
+ characters.
+
+
+Of course, custom classes may be specified when using `jqconsole.Write()` for
+further customization.
+
+
+##Contributors
+
+[Max Shawabkeh](http://max99x.com/)
+[Amjad Masad](http://twitter.com/amjad_masad)
diff --git a/lib/client/console/jqconsole.coffee b/lib/client/console/jqconsole.coffee
new file mode 100644
index 00000000..0230629c
--- /dev/null
+++ b/lib/client/console/jqconsole.coffee
@@ -0,0 +1,1310 @@
+###
+Copyrights 2011, the repl.it project.
+Licensed under the MIT license
+###
+
+# Shorthand for jQuery.
+$ = jQuery
+
+# The states in which the console can be.
+STATE_INPUT = 0
+STATE_OUTPUT = 1
+STATE_PROMPT = 2
+
+# Key code values.
+KEY_ENTER = 13
+KEY_TAB = 9
+KEY_DELETE = 46
+KEY_BACKSPACE = 8
+KEY_LEFT = 37
+KEY_RIGHT = 39
+KEY_UP = 38
+KEY_DOWN = 40
+KEY_HOME = 36
+KEY_END = 35
+KEY_PAGE_UP = 33
+KEY_PAGE_DOWN = 34
+
+# CSS classes
+CLASS_PREFIX = 'jqconsole-'
+CLASS_CURSOR = "#{CLASS_PREFIX}cursor"
+CLASS_HEADER = "#{CLASS_PREFIX}header"
+CLASS_PROMPT = "#{CLASS_PREFIX}prompt"
+CLASS_OLD_PROMPT = "#{CLASS_PREFIX}old-prompt"
+CLASS_INPUT = "#{CLASS_PREFIX}input"
+CLASS_BLURRED = "#{CLASS_PREFIX}blurred"
+
+# Frequently used string literals
+E_KEYPRESS = 'keypress'
+EMPTY_SPAN = ''
+EMPTY_DIV = ''
+EMPTY_SELECTOR = ':empty'
+NEWLINE = '\n'
+
+# Default prompt text for main and continuation prompts.
+DEFAULT_PROMPT_LABEL = '>>> '
+DEFAULT_PROMPT_CONINUE_LABEL = '... '
+
+# The default number of spaces inserted when indenting.
+DEFAULT_INDENT_WIDTH = 2
+
+CLASS_ANSI = "#{CLASS_PREFIX}ansi-"
+ESCAPE_CHAR = '\x1B'
+ESCAPE_SYNTAX = /\[(\d*)(?:;(\d*))*m/
+
+class Ansi
+ COLORS: ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
+
+ constructor: ->
+ @klasses = [];
+
+ _append: (klass) =>
+ klass = "#{CLASS_ANSI}#{klass}"
+ if @klasses.indexOf(klass) is -1
+ @klasses.push klass
+
+ _remove: (klasses...) =>
+ for klass in klasses
+ if klass in ['fonts', 'color', 'background-color']
+ @klasses = (cls for cls in @klasses when cls.indexOf(klass) isnt CLASS_ANSI.length)
+ else
+ klass = "#{CLASS_ANSI}#{klass}"
+ @klasses = (cls for cls in @klasses when cls isnt klass)
+
+ _color: (i) => @COLORS[i]
+
+ _style: (code) =>
+ code = 0 if code == ''
+ code = parseInt code
+
+ return if isNaN code
+
+ switch code
+ when 0 then @klasses = []
+ when 1 then @_append 'bold'
+ when 2 then @_append 'lighter'
+ when 3 then @_append 'italic'
+ when 4 then @_append 'underline'
+ when 5 then @_append 'blink'
+ when 6 then @_append 'blink-rapid'
+ when 8 then @_append 'hidden'
+ when 9 then @_append 'line-through'
+ when 10 then @_remove 'fonts'
+ when 11,12,13,14,15,16,17,18,19
+ @_remove 'fonts'
+ @_append "fonts-#{code - 10}"
+ when 20 then @_append 'fraktur'
+ when 21 then @_remove 'bold', 'lighter'
+ when 22 then @_remove 'bold', 'lighter'
+ when 23 then @_remove 'italic', 'fraktur'
+ when 24 then @_remove 'underline'
+ when 25 then @_remove 'blink', 'blink-rapid'
+ when 28 then @_remove 'hidden'
+ when 29 then @_remove 'line-through'
+ when 30,31,32,33,34,35,36,37
+ @_remove 'color'
+ @_append 'color-' + @_color code - 30
+ when 39 then @_remove 'color'
+ when 40,41,42,43,44,45,46,47
+ @_remove 'background-color'
+ @_append 'background-color-' + @_color code - 40
+ when 49 then @_remove 'background-color'
+ when 51 then @_append 'framed'
+ when 53 then @_append 'overline'
+ when 54 then @_remove 'framed'
+ when 55 then @_remove 'overline'
+
+ getClasses: => @klasses.join ' '
+
+ _openSpan: (text) => "#{text}"
+ _closeSpan: (text) => "#{text}"
+
+ stylize: (text) =>
+ text = @_openSpan text
+
+ i = 0
+ while (i = text.indexOf(ESCAPE_CHAR ,i)) and i isnt -1
+ if d = text[i...].match ESCAPE_SYNTAX
+ @_style code for code in d[1...]
+ text = @_closeSpan(text[0...i]) + @_openSpan text[i + 1 + d[0].length...]
+ else i++
+
+ return @_closeSpan text
+
+# Helper functions
+spanHtml = (klass, content) -> "#{content or ''}"
+
+class JQConsole
+ # Creates a console.
+ # @arg container: The DOM element into which the console is inserted.
+ # @arg header: Text to print at the top of the console on reset. Optional.
+ # Defaults to an empty string.
+ # @arg prompt_label: The label to show before the command prompt. Optional.
+ # Defaults to DEFAULT_PROMPT_LABEL.
+ # @arg prompt_continue: The label to show before continuation lines of the
+ # command prompt. Optional. Defaults to DEFAULT_PROMPT_CONINUE_LABEL.
+ constructor: (@container, header, prompt_label, prompt_continue_label) ->
+ # Mobile devices supported sniff.
+ @isMobile = !!navigator.userAgent.match /iPhone|iPad|iPod|Android/i
+ @isIos = !!navigator.userAgent.match /iPhone|iPad|iPod/i
+ @isAndroid = !!navigator.userAgent.match /Android/i
+
+ @$window = $(window)
+
+ # The header written when the console is reset.
+ @header = header or ''
+
+ # The prompt label used by Prompt().
+ @prompt_label_main = if typeof prompt_label == 'string'
+ prompt_label
+ else
+ DEFAULT_PROMPT_LABEL
+ @prompt_label_continue = (prompt_continue_label or
+ DEFAULT_PROMPT_CONINUE_LABEL)
+
+ # How many spaces are inserted when a tab character is pressed.
+ @indent_width = DEFAULT_INDENT_WIDTH
+
+ # By default, the console is in the output state.
+ @state = STATE_OUTPUT
+
+ # A queue of input/prompt operations waiting to be called. The items are
+ # bound functions ready to be called.
+ @input_queue = []
+
+ # The function to call when input is accepted. Valid only in
+ # input/prompt mode.
+ @input_callback = null
+ # The function to call to determine whether the input should continue to the
+ # next line.
+ @multiline_callback = null
+
+ # A table of all "recorded" inputs given so far.
+ @history = []
+ # The index of the currently selected history item. If this is past the end
+ # of @history, then the user has not selected a history item.
+ @history_index = 0
+ # The command which the user was typing before browsing history. Keeping
+ # track of this allows us to restore the user's command if they browse the
+ # history then decide to go back to what they were typing.
+ @history_new = ''
+ # Whether the current input operation is using history.
+ @history_active = false
+
+ # A table of custom shortcuts, mapping character codes to callbacks.
+ @shortcuts = {}
+
+ # The main console area. Everything else happens inside this.
+ @$console = $('').appendTo @container
+ @$console.css
+ position: 'absolute'
+ top: 0
+ bottom: 0
+ right: 0
+ left: 0
+ margin: 0
+ overflow: 'auto'
+
+ # Whether the console currently has focus.
+ @$console_focused = true
+
+ # On screen somehow invisible textbox for input.
+ # Copied from codemirror2, this works for both mobile and desktop browsers.
+ @$input_container = $(EMPTY_DIV).appendTo @container
+ @$input_container.css
+ position: 'relative'
+ width: 1
+ height: 0
+ overflow: 'hidden'
+ @$input_source = $('')
+ @$input_source.attr('wrap', 'off').css
+ position: 'absolute'
+ width: 2
+ @$input_source.appendTo @$input_container
+
+ @$composition = $(EMPTY_DIV)
+ @$composition.addClass "#{CLASS_PREFIX}composition"
+ @$composition.css
+ display: 'inline'
+ position: 'relative'
+
+ # Hash containing all matching settings
+ # openings/closings[char] = matching_config
+ # where char is the opening/closing character.
+ # clss is an array of classes for fast unhighlighting
+ # for matching_config see Match method
+ @matchings =
+ openings: {}
+ closings: {}
+ clss: []
+
+ @ansi = new Ansi()
+
+ # Prepare console for interaction.
+ @_InitPrompt()
+ @_SetupEvents()
+ @Write @header, CLASS_HEADER
+
+ # Save this instance to be accessed if lost.
+ $(@container).data 'jqconsole', this
+
+ #### Reset methods
+
+ # Resets the history into intitial state.
+ ResetHistory: ->
+ @SetHistory []
+
+ # Resets the shortcut configuration.
+ ResetShortcuts: ->
+ @shortcuts = {}
+
+ # Resets the matching configuration.
+ ResetMatchings: ->
+ @matchings =
+ openings: {}
+ closings: {}
+ clss: []
+
+ # Resets the console to its initial state.
+ Reset: ->
+ if @state != STATE_OUTPUT then @ClearPromptText true
+ @state = STATE_OUTPUT
+ @input_queue = []
+ @input_callback = null
+ @multiline_callback = null
+ @ResetHistory()
+ @ResetShortcuts()
+ @ResetMatchings()
+ @$prompt.detach()
+ @$input_container.detach()
+ @$console.html ''
+ @$prompt.appendTo @$console
+ @$input_container.appendTo @container
+ @Write @header, CLASS_HEADER
+ return undefined
+
+ #### History Methods
+
+ # Get the current history
+ GetHistory: ->
+ @history
+
+ # Set the history
+ SetHistory: (history) ->
+ @history = history.slice()
+ @history_index = @history.length
+
+ ###------------------------ Shortcut Methods -----------------------------###
+
+ # Checks the type/value of key codes passed in for registering/unregistering
+ # shortcuts and handles accordingly.
+ _CheckKeyCode: (key_code) ->
+ if isNaN key_code
+ key_code = key_code.charCodeAt 0
+ else
+ key_code = parseInt key_code, 10
+
+ if not (0 < key_code < 256) or isNaN key_code
+ throw new Error 'Key code must be a number between 0 and 256 exclusive.'
+
+ return key_code
+
+ # A helper function responsible for calling the register/unregister callback
+ # twice passing in both the upper and lower case letters.
+ _LetterCaseHelper: (key_code, callback)->
+ callback key_code
+ if 65 <= key_code <= 90 then callback key_code + 32
+ if 97 <= key_code <= 122 then callback key_code - 32
+
+ # Registers a Ctrl+Key shortcut.
+ # @arg key_code: The code of the key pressing which (when Ctrl is held) will
+ # trigger this shortcut. If a string is provided, the character code of
+ # the first character is taken.
+ # @arg callback: A function called when the shortcut is pressed; "this" will
+ # point to the JQConsole object.
+ RegisterShortcut: (key_code, callback) ->
+ key_code = @_CheckKeyCode key_code
+ if typeof callback != 'function'
+ throw new Error 'Callback must be a function, not ' + callback + '.'
+
+ addShortcut = (key) =>
+ if key not of @shortcuts then @shortcuts[key] = []
+ @shortcuts[key].push callback
+
+ @_LetterCaseHelper key_code, addShortcut
+ return undefined
+
+ # Removes a Ctrl+Key shortcut from shortcut registry.
+ # @arg key_code: The code of the key pressing which (when Ctrl is held) will
+ # trigger this shortcut. If a string is provided, the character code of
+ # the first character is taken.
+ # @arg handler: The handler that was used when registering the shortcut,
+ # if not supplied then all shortcut handlers corrosponding to the key
+ # would be removed.
+ UnRegisterShortcut: (key_code, handler) ->
+ key_code = @_CheckKeyCode key_code
+
+ removeShortcut = (key)=>
+ if key of @shortcuts
+ if handler
+ @shortcuts[key].splice @shortcuts[key].indexOf(handler), 1
+ else
+ delete @shortcuts[key]
+
+ @_LetterCaseHelper key_code, removeShortcut
+ return undefined
+
+ ###---------------------- END Shortcut Methods ---------------------------###
+
+ # Returns the 0-based number of the column on which the cursor currently is.
+ GetColumn: ->
+ @$prompt_cursor.text ''
+ lines = @$console.text().split NEWLINE
+ @$prompt_cursor.html ' '
+ return lines[lines.length - 1].length
+
+ # Returns the 0-based number of the line on which the cursor currently is.
+ GetLine: ->
+ return @$console.text().split(NEWLINE).length - 1
+
+ # Clears the contents of the prompt.
+ # @arg clear_label: If true, also clears the main prompt label (e.g. ">>>").
+ ClearPromptText: (clear_label) ->
+ if @state == STATE_OUTPUT
+ throw new Error 'ClearPromptText() is not allowed in output state.'
+ @$prompt_before.html ''
+ @$prompt_after.html ''
+ @$prompt_label.text if clear_label then '' else @_SelectPromptLabel false
+ @$prompt_left.text ''
+ @$prompt_right.text ''
+ return undefined
+
+ # Returns the contents of the prompt.
+ # @arg full: If true, also includes the prompt labels (e.g. ">>>").
+ GetPromptText: (full) ->
+ if @state == STATE_OUTPUT
+ throw new Error 'GetPromptText() is not allowed in output state.'
+
+ if full
+ @$prompt_cursor.text ''
+ text = @$prompt.text()
+ @$prompt_cursor.html ' '
+ return text
+ else
+ getPromptLines = (node) ->
+ buffer = []
+ node.children().each -> buffer.push $(@).children().last().text()
+ return buffer.join NEWLINE
+
+ before = getPromptLines @$prompt_before
+ if before then before += NEWLINE
+
+ current = @$prompt_left.text() + @$prompt_right.text()
+
+ after = getPromptLines @$prompt_after
+ if after then after = NEWLINE + after
+
+ return before + current + after
+
+ # Sets the contents of the prompt.
+ # @arg text: The text to put in the prompt. May contain multiple lines.
+ SetPromptText: (text) ->
+ if @state == STATE_OUTPUT
+ throw new Error 'SetPromptText() is not allowed in output state.'
+ @ClearPromptText false
+ @_AppendPromptText text
+ @_ScrollToEnd()
+ return undefined
+
+ # Replaces the main prompt label.
+ # @arg main_label: The new main label for the next prompt.
+ # @arg continue_label: The new continuation label for the next prompt. Optional.
+ SetPromptLabel: (main_label, continue_label) ->
+ @prompt_label_main = main_label
+ if continue_label?
+ @prompt_label_continue = continue_label
+ return undefined
+
+ # Writes the given text to the console in a , with an optional class.
+ # @arg text: The text to write.
+ # @arg cls: The class to give the span containing the text. Optional.
+ Write: (text, cls, escape=true) ->
+ if escape
+ text = @ansi.stylize $(EMPTY_SPAN).text(text).html()
+
+ span = $(EMPTY_SPAN).html text
+ if cls? then span.addClass cls
+ @Append span
+
+ # Adds a dom node, where any text would have been inserted
+ # @arg node: The node to insert.
+ Append: (node) ->
+ $node = $(node).insertBefore @$prompt
+ @_ScrollToEnd()
+ # Force reclaculation of the cursor's position.
+ @$prompt_cursor.detach().insertAfter @$prompt_left
+ return $node
+
+ # Starts an input operation. If another input or prompt operation is currently
+ # underway, the new input operation is enqueued and will be called when the
+ # current operation and all previously enqueued operations finish.
+ # @arg input_callback: A function called with the user's input when the
+ # user presses Enter and the input operation is complete.
+ Input: (input_callback) ->
+ if @state == STATE_PROMPT
+ # Input operation has a higher priority, Abort and defer current prompt
+ # by putting it on top of the queue.
+ current_input_callback = @input_callback
+ current_multiline_callback = @multiline_callback
+ current_history_active = @history_active
+ current_async_multiline = @async_multiline
+ @AbortPrompt()
+ @input_queue.unshift => @Prompt current_history_active,
+ current_input_callback,
+ current_multiline_callback,
+ current_async_multiline
+ else if @state != STATE_OUTPUT
+ @input_queue.push => @Input input_callback
+ return
+ @history_active = false
+ @input_callback = input_callback
+ @multiline_callback = null
+ @state = STATE_INPUT
+ @$prompt.attr 'class', CLASS_INPUT
+ @$prompt_label.text @_SelectPromptLabel false
+ @Focus()
+ @_ScrollToEnd()
+ return undefined
+
+ # Starts a command prompt operation. If another input or prompt operation is
+ # currently underway, the new prompt operation is enqueued and will be called
+ # when the current operation and all previously enqueued operations finish.
+ # @arg history_enabled: Whether this input should use history. If true, the
+ # user can select the input from history, and their input will also be
+ # added as a new history item.
+ # @arg result_callback: A function called with the user's input when the
+ # user presses Enter and the prompt operation is complete.
+ # @arg multiline_callback: If specified, this function is called when the
+ # user presses Enter to check whether the input should continue to the
+ # next line. The function must return one of the following values:
+ # false: the input operation is completed.
+ # 0: the input continues to the next line with the current indent.
+ # N (int): the input continues to the next line, and the current indent
+ # is adjusted by N, e.g. -2 to unindent two levels.
+ Prompt: (history_enabled, result_callback, multiline_callback, async_multiline) ->
+ if @state != STATE_OUTPUT
+ @input_queue.push =>
+ @Prompt history_enabled, result_callback, multiline_callback, async_multiline
+ return
+ @history_active = history_enabled
+ @input_callback = result_callback
+ @multiline_callback = multiline_callback
+ @async_multiline = async_multiline
+ @state = STATE_PROMPT
+ @$prompt.attr 'class', CLASS_PROMPT + ' ' + @ansi.getClasses()
+ @$prompt_label.text @_SelectPromptLabel false
+ @Focus()
+ @_ScrollToEnd()
+ return undefined
+
+ # Aborts the current prompt operation and returns to output mode or the next
+ # queued input/prompt operation.
+ AbortPrompt: ->
+ if @state != STATE_PROMPT
+ throw new Error 'Cannot abort prompt when not in prompt state.'
+ @Write @GetPromptText(true) + NEWLINE, CLASS_OLD_PROMPT
+ @ClearPromptText true
+ @state = STATE_OUTPUT
+ @input_callback = @multiline_callback = null
+ @_CheckInputQueue()
+ return undefined
+
+ # Sets focus on the console's hidden input box so input can be read.
+ Focus: ->
+ @$input_source.focus() if not @IsDisabled()
+ return undefined
+
+ # Sets the number of spaces inserted when indenting.
+ SetIndentWidth: (width) ->
+ @indent_width = width
+
+ # Returns the number of spaces inserted when indenting.
+ GetIndentWidth: ->
+ return @indent_width
+
+ # Registers character matching settings for a single matching
+ # @arg open: the openning character
+ # @arg close: the closing character
+ # @arg cls: the html class to add to the matched characters
+ RegisterMatching: (open, close, cls) ->
+ match_config =
+ opening_char: open
+ closing_char: close
+ cls: cls
+
+ @matchings.clss.push(cls)
+ @matchings.openings[open] = match_config
+ @matchings.closings[close] = match_config
+
+ # Unregisters a character matching. cls is optional.
+ UnRegisterMatching: (open, close) ->
+ cls = @matchings.openings[open].cls
+ delete @matchings.openings[open]
+ delete @matchings.closings[close]
+ @matchings.clss.splice @matchings.clss.indexOf(cls), 1
+
+ # Dumps the content of the console before the current prompt.
+ Dump: ->
+ $elems = @$console.find(".#{CLASS_HEADER}").nextUntil(".#{CLASS_PROMPT}")
+
+ return (
+ for elem in $elems
+ if $(elem).is ".#{CLASS_OLD_PROMPT}"
+ $(elem).text().replace /^\s+/, '>>> '
+ else
+ $(elem).text()
+ ).join ' '
+
+ # Gets the current prompt state.
+ GetState: ->
+ return if @state is STATE_INPUT
+ 'input'
+ else if @state is STATE_OUTPUT
+ 'output'
+ else
+ 'prompt'
+
+ # Disables focus and input on the console.
+ Disable: ->
+ @$input_source.attr 'disabled', on
+ @$input_source.blur();
+
+ # Enables focus and input on the console.
+ Enable: ->
+ @$input_source.attr 'disabled', off
+
+ # Returns true if the console is disabled.
+ IsDisabled: ->
+ return Boolean @$input_source.attr 'disabled'
+
+ # Moves the cursor to the start of the current prompt line.
+ # @arg all_lines: If true, moves to the beginning of the first prompt line,
+ # instead of the beginning of the current.
+ MoveToStart: (all_lines) ->
+ @_MoveTo all_lines, true
+ return undefined
+
+ # Moves the cursor to the end of the current prompt line.
+ MoveToEnd: (all_lines) ->
+ @_MoveTo all_lines, false
+ return undefined
+
+ ###------------------------ Private Methods -------------------------------###
+
+ _CheckInputQueue: ->
+ if @input_queue.length
+ @input_queue.shift()()
+
+ # Creates the movable prompt span. When the console is in input mode, this is
+ # shown and allows user input. The structure of the spans are as follows:
+ # $prompt
+ # $prompt_before
+ # line1
+ # prompt_label
+ # prompt_content
+ # ...
+ # lineN
+ # prompt_label
+ # prompt_content
+ # $prompt_current
+ # $prompt_label
+ # $prompt_left
+ # $prompt_cursor
+ # $prompt_right
+ # $prompt_after
+ # line1
+ # prompt_label
+ # prompt_content
+ # ...
+ # lineN
+ # prompt_label
+ # prompt_content
+ _InitPrompt: ->
+ # The main prompt container.
+ @$prompt = $(spanHtml(CLASS_INPUT)).appendTo @$console
+ # The main divisions of the prompt - the lines before the current line, the
+ # current line, and the lines after it.
+ @$prompt_before = $(EMPTY_SPAN).appendTo @$prompt
+ @$prompt_current = $(EMPTY_SPAN).appendTo @$prompt
+ @$prompt_after = $(EMPTY_SPAN).appendTo @$prompt
+
+ # The subdivisions of the current prompt line - the static prompt label
+ # (e.g. ">>> "), and the editable text to the left and right of the cursor.
+ @$prompt_label = $(EMPTY_SPAN).appendTo @$prompt_current
+ @$prompt_left = $(EMPTY_SPAN).appendTo @$prompt_current
+ @$prompt_right = $(EMPTY_SPAN).appendTo @$prompt_current
+
+ # Needed for the CSS z-index on the cursor to work.
+ @$prompt_right.css position: 'relative'
+
+ # The cursor. A span containing a space that shades its following character.
+ # If the font of the prompt is not monospace, the content should be set to
+ # the first character of @$prompt_right to get the appropriate width.
+ @$prompt_cursor = $(spanHtml(CLASS_CURSOR, ' '))
+ @$prompt_cursor.insertBefore @$prompt_right
+ @$prompt_cursor.css
+ color: 'transparent'
+ display: 'inline'
+ zIndex: 0
+ @$prompt_cursor.css('position', 'absolute') if not @isMobile
+
+ # Binds all the required input and focus events.
+ _SetupEvents: ->
+
+ # Click to focus.
+ if @isMobile
+ @$console.click (e) =>
+ e.preventDefault()
+ @Focus()
+ else
+ @$console.mouseup (e) =>
+ # Focus immediatly when it's the middle click to support
+ # paste on linux desktop.
+ if e.which == 2
+ @Focus()
+ else
+ fn = =>
+ if not window.getSelection().toString()
+ e.preventDefault()
+ @Focus()
+ # Force selection update.
+ setTimeout fn, 0
+
+ # Mark the console with a style when it loses focus.
+ @$input_source.focus =>
+ @_ScrollToEnd()
+ @$console_focused = true
+ @$console.removeClass CLASS_BLURRED
+ removeClass = =>
+ if @$console_focused then @$console.removeClass CLASS_BLURRED
+ setTimeout removeClass, 100
+ hideTextInput = =>
+ if @isIos and @$console_focused then @$input_source.hide()
+ setTimeout hideTextInput, 500
+
+ @$input_source.blur =>
+ @$console_focused = false
+ if @isIos then @$input_source.show()
+ addClass = =>
+ if not @$console_focused then @$console.addClass CLASS_BLURRED
+ setTimeout addClass, 100
+
+ # Intercept pasting.
+ paste_event = if $.browser.opera then 'input' else 'paste'
+ @$input_source.bind paste_event, =>
+ handlePaste = =>
+ # Opera fires input on composition end.
+ return if @in_composition
+ @_AppendPromptText @$input_source.val()
+ @$input_source.val ''
+ @Focus()
+ # Wait until the browser has handled the paste event before scraping.
+ setTimeout handlePaste, 0
+
+ # Actual key-by-key handling.
+ @$input_source.keypress @_HandleChar
+ @$input_source.keydown @_HandleKey
+ @$input_source.keydown @_CheckComposition
+
+ # Firefox don't fire any key event for composition characters, so we listen
+ # for the unstandard composition-events.
+ if $.browser.mozilla?
+ @$input_source.bind 'compositionstart', @_StartComposition
+ @$input_source.bind 'compositionend', @_EndCommposition
+ @$input_source.bind 'text', @_UpdateComposition
+
+ # There is no way to detect compositionstart in opera so we poll for it.
+ if $.browser.opera?
+ cb = =>
+ return if @in_composition
+ # if there was characters that actually escaped to the input source
+ # then its most probably a multibyte char.
+ if @$input_source.val().length
+ @_StartComposition()
+ setInterval cb, 200
+
+ # Handles a character key press.
+ # @arg event: The jQuery keyboard Event object to handle.
+ _HandleChar: (event) =>
+ # We let the browser take over during output mode.
+ # Skip everything when a modifier key other than shift is held.
+ # Allow alt key to pass through for unicode & multibyte characters.
+ if @state == STATE_OUTPUT or event.metaKey or event.ctrlKey
+ return true
+
+ # IE & Chrome capture non-control characters and Enter.
+ # Mozilla and Opera capture everything.
+
+ # This is the most reliable cross-browser; charCode/keyCode break on Opera.
+ char_code = event.which
+
+ # Skip Enter on IE and Chrome and Tab & backspace on Opera.
+ # These are handled in _HandleKey().
+ if char_code in [8, 9, 13] then return false
+
+ # Pass control characters which are captured on Mozilla/Safari.
+ if $.browser.mozilla
+ if event.keyCode or event.altKey
+ return true
+ # Pass control characters which are captured on Opera.
+ if $.browser.opera
+ if event.altKey
+ return true
+
+ @$prompt_left.text @$prompt_left.text() + String.fromCharCode char_code
+ @_ScrollToEnd()
+ return false
+
+ # Handles a key up event and dispatches specific handlers.
+ # @arg event: The jQuery keyboard Event object to handle.
+ _HandleKey: (event) =>
+ # We let the browser take over during output mode.
+ if @state == STATE_OUTPUT then return true
+
+ key = event.keyCode or event.which
+
+ # Check for char matching next time the callstack unwinds.
+ setTimeout $.proxy(@_CheckMatchings, this), 0
+
+ # Don't care about alt-modifier.
+ if event.altKey
+ return true
+ # Handle shortcuts.
+ else if event.ctrlKey or event.metaKey
+ return @_HandleCtrlShortcut key
+ else if event.shiftKey
+ # Shift-modifier shortcut.
+ switch key
+ when KEY_ENTER then @_HandleEnter true
+ when KEY_TAB then @_Unindent()
+ when KEY_UP then @_MoveUp()
+ when KEY_DOWN then @_MoveDown()
+ when KEY_PAGE_UP then @_ScrollUp()
+ when KEY_PAGE_DOWN then @_ScrollDown()
+ # Allow other Shift shortcuts to pass through to the browser.
+ else return true
+ return false
+ else
+ # Not a modifier shortcut.
+ switch key
+ when KEY_ENTER then @_HandleEnter false
+ when KEY_TAB then @_Indent()
+ when KEY_DELETE then @_Delete false
+ when KEY_BACKSPACE then @_Backspace false
+ when KEY_LEFT then @_MoveLeft false
+ when KEY_RIGHT then @_MoveRight false
+ when KEY_UP then @_HistoryPrevious()
+ when KEY_DOWN then @_HistoryNext()
+ when KEY_HOME then @MoveToStart false
+ when KEY_END then @MoveToEnd false
+ when KEY_PAGE_UP then @_ScrollUp()
+ when KEY_PAGE_DOWN then @_ScrollDown()
+ # Let any other key continue its way to keypress.
+ else return true
+ return false
+
+ # Handles a Ctrl+Key shortcut.
+ # @arg key: The keyCode of the pressed key.
+ _HandleCtrlShortcut: (key) ->
+ switch key
+ when KEY_DELETE then @_Delete true
+ when KEY_BACKSPACE then @_Backspace true
+ when KEY_LEFT then @_MoveLeft true
+ when KEY_RIGHT then @_MoveRight true
+ when KEY_UP then @_MoveUp()
+ when KEY_DOWN then @_MoveDown()
+ when KEY_END then @MoveToEnd true
+ when KEY_HOME then @MoveToStart true
+ else
+ if key of @shortcuts
+ # Execute custom shortcuts.
+ handler.call(this) for handler in @shortcuts[key]
+ return false
+ else
+ # Allow unhandled Ctrl shortcuts.
+ return true
+ # Block handled shortcuts.
+ return false
+
+ # Handles the user pressing the Enter key.
+ # @arg shift: Whether the shift key is held.
+ _HandleEnter: (shift) ->
+ if shift
+ @_InsertNewLine true
+ else
+ text = @GetPromptText()
+ continuation = (indent) =>
+ if indent isnt false
+ @MoveToEnd true
+ @_InsertNewLine true
+ for _ in [0...Math.abs indent]
+ if indent > 0 then @_Indent() else @_Unindent()
+ else
+ # Done with input.
+ cls_suffix = if @state == STATE_INPUT then 'input' else 'prompt'
+ @Write @GetPromptText(true) + NEWLINE, "#{CLASS_PREFIX}old-" + cls_suffix
+ @ClearPromptText true
+ if @history_active
+ if not @history.length or @history[@history.length - 1] != text
+ @history.push text
+ @history_index = @history.length
+ @state = STATE_OUTPUT
+ callback = @input_callback
+ @input_callback = null
+ if callback then callback text
+ @_CheckInputQueue()
+
+ if @multiline_callback
+ if @async_multiline
+ @multiline_callback text, continuation
+ else
+ continuation @multiline_callback text
+ else
+ continuation false
+
+
+ # Returns the appropriate variables for usage in methods that depends on the
+ # direction of the interaction with the console.
+ _GetDirectionals: (back) ->
+ $prompt_which = if back then @$prompt_left else @$prompt_right
+ $prompt_opposite = if back then @$prompt_right else @$prompt_left
+ $prompt_relative = if back then @$prompt_before else @$prompt_after
+ $prompt_rel_opposite = if back then @$prompt_after else @$prompt_before
+ MoveToLimit = if back
+ $.proxy @MoveToStart, @
+ else
+ $.proxy @MoveToEnd, @
+ MoveDirection = if back
+ $.proxy @_MoveLeft, @
+ else
+ $.proxy @_MoveRight, @
+ which_end = if back then 'last' else 'first'
+ where_append = if back then 'prependTo' else 'appendTo'
+ return {
+ $prompt_which
+ $prompt_opposite
+ $prompt_relative
+ $prompt_rel_opposite
+ MoveToLimit
+ MoveDirection
+ which_end
+ where_append
+ }
+
+ # Moves the cursor vertically in the current prompt,
+ # in the same column. (Used by _MoveUp, _MoveDown)
+ _VerticalMove: (up) ->
+ {
+ $prompt_which
+ $prompt_opposite
+ $prompt_relative
+ MoveToLimit
+ MoveDirection
+ } = @_GetDirectionals(up)
+
+ if $prompt_relative.is EMPTY_SELECTOR then return
+ pos = @$prompt_left.text().length
+ MoveToLimit()
+ MoveDirection()
+ text = $prompt_which.text()
+ $prompt_opposite.text if up then text[pos..] else text[...pos]
+ $prompt_which.text if up then text[...pos] else text[pos..]
+
+
+ # Moves the cursor to the line above the current one, in the same column.
+ _MoveUp: ->
+ @_VerticalMove true
+
+ # Moves the cursor to the line below the current one, in the same column.
+ _MoveDown: ->
+ @_VerticalMove()
+
+ # Moves the cursor horizontally in the current prompt.
+ # Used by _MoveLeft, _MoveRight
+ _HorizontalMove: (whole_word, back) ->
+ {
+ $prompt_which
+ $prompt_opposite
+ $prompt_relative
+ $prompt_rel_opposite
+ which_end
+ where_append
+ } = @_GetDirectionals(back)
+ regexp = if back then /\w*\W*$/ else /^\w*\W*/
+
+ text = $prompt_which.text()
+ if text
+ if whole_word
+ word = text.match regexp
+ if not word then return
+ word = word[0]
+ tmp = $prompt_opposite.text()
+ $prompt_opposite.text if back then word + tmp else tmp + word
+ len = word.length
+ $prompt_which.text if back then text[...-len] else text[len..]
+ else
+ tmp = $prompt_opposite.text()
+ $prompt_opposite.text if back then text[-1...] + tmp else tmp + text[0]
+ $prompt_which.text if back then text[...-1] else text[1...]
+ else if not $prompt_relative.is EMPTY_SELECTOR
+ $which_line = $(EMPTY_SPAN)[where_append] $prompt_rel_opposite
+ $which_line.append $(EMPTY_SPAN).text @$prompt_label.text()
+ $which_line.append $(EMPTY_SPAN).text $prompt_opposite.text()
+
+ $opposite_line = $prompt_relative.children()[which_end]().detach()
+ @$prompt_label.text $opposite_line.children().first().text()
+ $prompt_which.text $opposite_line.children().last().text()
+ $prompt_opposite.text ''
+
+ # Moves the cursor to the left.
+ # @arg whole_word: Whether to move by a whole word rather than a character.
+ _MoveLeft: (whole_word) ->
+ @_HorizontalMove whole_word, true
+
+ # Moves the cursor to the right.
+ # @arg whole_word: Whether to move by a whole word rather than a character.
+ _MoveRight: (whole_word) ->
+ @_HorizontalMove whole_word
+
+ # Moves the cursor either to the start or end of the current prompt line(s).
+ _MoveTo: (all_lines, back) ->
+ {
+ $prompt_which
+ $prompt_opposite
+ $prompt_relative
+ MoveToLimit
+ MoveDirection
+ } = @_GetDirectionals(back)
+
+ if all_lines
+ # Warning! FF 3.6 hangs on is(EMPTY_SELECTOR)
+ until $prompt_relative.is(EMPTY_SELECTOR) and $prompt_which.text() == ''
+ MoveToLimit false
+ MoveDirection false
+ else
+ $prompt_opposite.text @$prompt_left.text() + @$prompt_right.text()
+ $prompt_which.text ''
+
+ # Deletes the character or word following the cursor.
+ # @arg whole_word: Whether to delete a whole word rather than a character.
+ _Delete: (whole_word) ->
+ text = @$prompt_right.text()
+ if text
+ if whole_word
+ word = text.match /^\w*\W*/
+ if not word then return
+ word = word[0]
+ @$prompt_right.text text[word.length...]
+ else
+ @$prompt_right.text text[1...]
+ else if not @$prompt_after.is EMPTY_SELECTOR
+ $lower_line = @$prompt_after.children().first().detach()
+ @$prompt_right.text $lower_line.children().last().text()
+
+ # Deletes the character or word preceding the cursor.
+ # @arg whole_word: Whether to delete a whole word rather than a character.
+ _Backspace: (whole_word) ->
+ setTimeout $.proxy(@_ScrollToEnd, @), 0
+ text = @$prompt_left.text()
+ if text
+ if whole_word
+ word = text.match /\w*\W*$/
+ if not word then return
+ word = word[0]
+ @$prompt_left.text text[...-word.length]
+ else
+ @$prompt_left.text text[...-1]
+ else if not @$prompt_before.is EMPTY_SELECTOR
+ $upper_line = @$prompt_before.children().last().detach()
+ @$prompt_label.text $upper_line.children().first().text()
+ @$prompt_left.text $upper_line.children().last().text()
+
+ # Indents the current line.
+ _Indent: ->
+ @$prompt_left.prepend (' ' for _ in [1..@indent_width]).join ''
+
+ # Unindents the current line.
+ _Unindent: ->
+ line_text = @$prompt_left.text() + @$prompt_right.text()
+ for _ in [1..@indent_width]
+ if not /^ /.test(line_text) then break
+ if @$prompt_left.text()
+ @$prompt_left.text @$prompt_left.text()[1..]
+ else
+ @$prompt_right.text @$prompt_right.text()[1..]
+ line_text = line_text[1..]
+
+ # Inserts a new line at the cursor position.
+ # @arg indent: If specified and true, the inserted line is indented to the
+ # same column as the last line.
+ _InsertNewLine: (indent = false) ->
+ old_prompt = @_SelectPromptLabel not @$prompt_before.is EMPTY_SELECTOR
+ $old_line = $(EMPTY_SPAN).appendTo @$prompt_before
+ $old_line.append $(EMPTY_SPAN).text old_prompt
+ $old_line.append $(EMPTY_SPAN).text @$prompt_left.text()
+
+ @$prompt_label.text @_SelectPromptLabel true
+ if indent and match = @$prompt_left.text().match /^\s+/
+ @$prompt_left.text match[0]
+ else
+ @$prompt_left.text ''
+ @_ScrollToEnd()
+
+ # Appends the given text to the prompt.
+ # @arg text: The text to append. Can contain multiple lines.
+ _AppendPromptText: (text) ->
+ lines = text.split NEWLINE
+ @$prompt_left.text @$prompt_left.text() + lines[0]
+ for line in lines[1..]
+ @_InsertNewLine()
+ @$prompt_left.text line
+
+ # Scrolls the console area up one page (with animation).
+ _ScrollUp: ->
+ target = @$console[0].scrollTop - @$console.height()
+ @$console.stop().animate {scrollTop: target}, 'fast'
+
+ # Scrolls the console area down one page (with animation).
+ _ScrollDown: ->
+ target = @$console[0].scrollTop + @$console.height()
+ @$console.stop().animate {scrollTop: target}, 'fast'
+
+ # Scrolls the console area to its bottom;
+ # Scrolls the window to the cursor vertical position.
+ # Called with every input/output to the console.
+ _ScrollToEnd: ->
+ # Scroll console to the bottom.
+ @$console.scrollTop @$console[0].scrollHeight
+
+ # The cursor's top position is effected by the scroll-top of the console
+ # so we need to this asynchronously to give the browser a chance to
+ # reflow and recaluclate the cursor's possition.
+ cont = =>
+ line_height = @$prompt_cursor.height()
+ screen_top = @$window.scrollTop()
+ screen_left = @$window.scrollLeft()
+ doc_height = document.documentElement.clientHeight
+ pos = @$prompt_cursor.offset()
+ rel_pos = @$prompt_cursor.position()
+
+ # Move the input element to the cursor position.
+ @$input_container.css
+ left: rel_pos.left
+ top: rel_pos.top
+
+ optimal_pos = pos.top - (2 * line_height)
+ # Follow the cursor vertically on mobile and desktop.
+ if @isMobile and orientation?
+ # Since the keyboard takes up most of the screen, we don't care about how
+ # far the the cursor position from the screen top is. We just follow it.
+ if screen_top < pos.top or screen_top > pos.top
+ @$window.scrollTop optimal_pos
+ else
+ if screen_top + doc_height < pos.top
+ # Scroll just to a place where the cursor is in the view port.
+ @$window.scrollTop pos.top - doc_height + line_height
+ else if screen_top > optimal_pos
+ # If the window is scrolled beyond the cursor, scroll to the cursor's
+ # position and give two line to the top.
+ @$window.scrollTop pos.top
+
+ setTimeout cont, 0
+
+ # Selects the prompt label appropriate to the current mode.
+ # @arg continuation: If true, returns the continuation prompt rather than
+ # the main one.
+ _SelectPromptLabel: (continuation) ->
+ if @state == STATE_PROMPT
+ return if continuation then (' \n' + @prompt_label_continue) else @prompt_label_main
+ else
+ return if continuation then '\n ' else ' '
+
+ # Cross-browser outerHTML
+ _outerHTML: ($elem) ->
+ if document.body.outerHTML
+ return $elem.get(0).outerHTML
+ else
+ return $(EMPTY_DIV).append($elem.eq(0).clone()).html()
+
+ # Wraps a single character in an element with a having a class
+ # @arg $elem: The JqDom element in question
+ # @arg index: the index of the character to be wrapped
+ # @arg cls: the html class to be given to the wrapping
+ _Wrap: ($elem, index, cls) ->
+ text = $elem.html()
+ html = text[0...index]+
+ spanHtml(cls, text[index])+
+ text[index + 1...]
+ $elem.html html
+
+ # Walks a string of characters incrementing current_count each time a char is found
+ # and decrementing each time an opposing char is found.
+ # @arg text: the text in question
+ # @arg char: the char that would increment the counter
+ # @arg opposing_char: the char that would decrement the counter
+ # @arg back: specifies whether the walking should be done backwards.
+ _WalkCharacters: (text, char, opposing_char, current_count, back) ->
+ index = if back then text.length else 0
+ text = text.split ''
+ read_char = () ->
+ if back
+ [text..., ret] = text
+ else
+ [ret, text...] = text
+ if ret
+ index = index + if back then -1 else +1
+ ret
+
+ while ch = read_char()
+ if ch is char
+ current_count++
+ else if ch is opposing_char
+ current_count--
+ if current_count is 0
+ return {index: index, current_count: current_count}
+
+ return {index: -1, current_count: current_count}
+
+ _ProcessMatch: (config, back, before_char) =>
+ [char, opposing_char] = if back
+ [
+ config['closing_char']
+ config['opening_char']
+ ]
+ else
+ [
+ config['opening_char']
+ config['closing_char']
+ ]
+ {$prompt_which, $prompt_relative} = @_GetDirectionals(back)
+
+ current_count = 1
+ found = false
+ # check current line first
+ text = $prompt_which.html()
+ # When on the same line discard checking the first character, going backwards
+ # is not an issue since the cursor's current character is found in $prompt_right.
+ if !back then text = text[1...]
+ if before_char and back then text = text[...-1]
+ {index, current_count} = @_WalkCharacters text, char, opposing_char, current_count, back
+ if index > -1
+ @_Wrap $prompt_which, index, config.cls
+ found = true
+ else
+ $collection = $prompt_relative.children()
+ # When going backwards we have to the reverse our jQuery collection
+ # for fair matchings
+ $collection = if back then Array.prototype.reverse.call($collection) else $collection
+ $collection.each (i, elem) =>
+ $elem = $(elem).children().last()
+ text = $elem.html()
+ {index, current_count} = @_WalkCharacters text, char, opposing_char, current_count, back
+ if index > -1
+ # When checking for matchings ona different line going forward we must decrement
+ # the index since the current char is not included
+ if !back then index--
+ @_Wrap $elem, index, config.cls
+ found = true
+ return false
+
+ return found
+
+ # Unrwaps all prevoisly matched characters.
+ # Checks if the cursor's current character is one to be matched, then walks
+ # the following/preceeding characters to look for the opposing character that
+ # would satisfy the match. If found both characters would be wrapped with a
+ # span and applied the html class that was found in the match_config.
+ _CheckMatchings: (before_char) ->
+ current_char = if before_char then @$prompt_left.text()[@$prompt_left.text().length - 1...] else @$prompt_right.text()[0]
+ # on every move unwrap all matched elements
+ # TODO(amasad): cache previous matched elements since this must be costly
+ $('.' + cls, @$console).contents().unwrap() for cls in @matchings.clss
+
+ if config = @matchings.closings[current_char]
+ found = @_ProcessMatch config, true, before_char
+ else if config = @matchings.openings[current_char]
+ found = @_ProcessMatch config, false, before_char
+ else if not before_char
+ @_CheckMatchings true
+
+ if before_char
+ @_Wrap @$prompt_left, @$prompt_left.html().length - 1, config.cls if found
+ else
+ # Wrap current element when a matching was found
+ @_Wrap @$prompt_right, 0, config.cls if found
+
+
+ # Sets the prompt to the previous history item.
+ _HistoryPrevious: ->
+ if not @history_active then return
+ if @history_index <= 0 then return
+ if @history_index == @history.length
+ @history_new = @GetPromptText()
+ @SetPromptText @history[--@history_index]
+
+ # Sets the prompt to the next history item.
+ _HistoryNext: ->
+ if not @history_active then return
+ if @history_index >= @history.length then return
+ if @history_index == @history.length - 1
+ @history_index++
+ @SetPromptText @history_new
+ else
+ @SetPromptText @history[++@history_index]
+
+ # Check if this could be the start of a composition or an update to it.
+ _CheckComposition: (e) =>
+ key = e.keyCode or e.which
+ if $.browser.opera? and @in_composition
+ @_UpdateComposition()
+ if key == 229
+ if @in_composition then @_UpdateComposition() else @_StartComposition()
+
+ # Starts a multibyte character composition.
+ _StartComposition: =>
+ @$input_source.bind E_KEYPRESS, @_EndComposition
+ @in_composition = true
+ @_ShowComposition()
+ setTimeout @_UpdateComposition, 0
+
+ # Ends a multibyte character composition.
+ _EndComposition: =>
+ @$input_source.unbind E_KEYPRESS, @_EndComposition
+ @in_composition = false
+ @_HideComposition()
+ @$input_source.val ''
+
+ # Updates a multibyte character composition.
+ _UpdateComposition: (e) =>
+ cb = =>
+ return if not @in_composition
+ @$composition.text @$input_source.val()
+ setTimeout cb, 0
+
+ # Shows a multibyte character composition.
+ _ShowComposition: =>
+ @$composition.css 'height', @$prompt_cursor.height()
+ @$composition.empty()
+ @$composition.appendTo @$prompt_left
+
+ # Hides a multibyte character composition.
+ _HideComposition: =>
+ # We just detach the element because by now the text value of this element
+ # is already extracted and has been put on the left of the prompt.
+ @$composition.detach()
+
+$.fn.jqconsole = (header, prompt_main, prompt_continue) ->
+ new JQConsole this, header, prompt_main, prompt_continue
+
+$.fn.jqconsole.JQConsole = JQConsole
+$.fn.jqconsole.Ansi = Ansi
diff --git a/lib/client/console/jqconsole.jquery.json b/lib/client/console/jqconsole.jquery.json
new file mode 100644
index 00000000..a42d1945
--- /dev/null
+++ b/lib/client/console/jqconsole.jquery.json
@@ -0,0 +1,22 @@
+{
+ "name": "jqconsole",
+ "version": "2.7.7",
+ "title": "Feature complete web terminal.",
+ "author": {
+ "name": "Amjad Masad",
+ "email": "amjad.masad@gmail.com",
+ "url": "http://amasad.me"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://opensource.org/licenses/MIT"
+ }
+ ],
+ "description": "A jQuery terminal plugin with raw input/output streams as well as input and output states and support for rich multi-line prompting and operation queueing.",
+ "keywords": ["terminal", "console", "emulator", "REPL", "repl.it"],
+ "demo": "http://repl.it/",
+ "dependencies": {
+ "jquery": ">=1.5"
+ }
+}
\ No newline at end of file