feature(console) update to v2.10.0

This commit is contained in:
coderaiser 2014-02-11 06:33:00 -05:00
parent 2802fbd9b4
commit ef8b02375a
10 changed files with 368 additions and 227 deletions

View file

@ -74,3 +74,11 @@ info.
* Remove browser sniffing. Fix #37 #42 #53
* Update android support. Fix #41
* Add wrapper div for padding/styling of console. Fix #40
2014.2.8 Version 2.9.0
* Fixed `GetColumn` method to return the column number up until the cursor.
* `Dump`: Add header contents in console dump and remove extra spaces.
2014.2.8 Version 2.10.0
* Add `Clear` method to clear the console

View file

@ -15,10 +15,11 @@ queueing.
The plugin has been tested on the following browsers:
* IE 9+
* Chrome 10+
* Firefox 4+
* Opera 11+
* iOS 4+
* Chrome
* Firefox
* Opera
* iOS Safari and Chrome
* Android Chrome
##Getting Started
@ -468,6 +469,9 @@ Resets the character matching configuration.
Resets the shortcut configuration.
###jqconsole.Clear
Clears the console's content excluding the current prompt
##Default Key Config
The console responds to the followind keys and key combinations by default:

View file

@ -19,4 +19,4 @@
"dependencies": {
"jquery": ">=1.5"
}
}
}

View file

@ -10,14 +10,24 @@ module.exports = function(config) {
'test/prompt-test.coffee',
'test/shortcuts-test.coffee',
'test/history-test.coffee',
'test/matching-test.coffee',
'test/misc-test.coffee',
],
browsers: ['Chrome'],
reporters: ['dots'],
reporters: ['dots', 'coverage'],
preprocessors: {
'lib/*.js': ['coverage'],
'test/*.coffee': ['coffee']
},
coffeePreprocessor: {
// options passed to the coffee compiler
options: {
bare: false,
}
},
coverageReporter: {
type : 'html',
dir : 'coverage/'
}
});
};

View file

@ -466,9 +466,11 @@ Licensed under the MIT license
JQConsole.prototype.GetColumn = function() {
var lines;
this.$prompt_right.detach();
this.$prompt_cursor.text('');
lines = this.$console.text().split(NEWLINE);
this.$prompt_cursor.html(' ');
this.$prompt_cursor.after(this.$prompt_right);
return lines[lines.length - 1].length;
};
@ -659,7 +661,7 @@ Licensed under the MIT license
JQConsole.prototype.Dump = function() {
var $elems, elem;
$elems = this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT);
$elems = this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT).addBack();
return ((function() {
var _i, _len, _results;
_results = [];
@ -672,7 +674,7 @@ Licensed under the MIT license
}
}
return _results;
})()).join(' ');
})()).join('');
};
JQConsole.prototype.GetState = function() {
@ -708,6 +710,12 @@ Licensed under the MIT license
return void 0;
};
JQConsole.prototype.Clear = function() {
this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT).addBack().text('');
this.$prompt_cursor.detach();
return this.$prompt_after.before(this.$prompt_cursor);
};
/*------------------------ Private Methods -------------------------------*/
@ -1277,14 +1285,6 @@ Licensed under the MIT license
}
};
JQConsole.prototype._outerHTML = function($elem) {
if (document.body.outerHTML) {
return $elem.get(0).outerHTML;
} else {
return $(EMPTY_DIV).append($elem.eq(0).clone()).html();
}
};
JQConsole.prototype._Wrap = function($elem, index, cls) {
var html, text;
text = $elem.html();

View file

@ -1,6 +1,6 @@
{
"name": "jq-console",
"version": "2.8.0",
"version": "2.10.0",
"description": "Feature complete web terminal",
"main": "jqconsole.min.js",
"directories": {
@ -35,6 +35,7 @@
"karma-requirejs": "~0.2.1",
"karma-phantomjs-launcher": "~0.1.1",
"karma": "~0.10.9",
"karma-mocha": "~0.1.1"
"karma-mocha": "~0.1.1",
"karma-coverage": "~0.1.5"
}
}

View file

@ -48,21 +48,21 @@ DEFAULT_PROMPT_CONINUE_LABEL = '... '
# The default number of spaces inserted when indenting.
DEFAULT_INDENT_WIDTH = 2
CLASS_ANSI = "#{CLASS_PREFIX}ansi-"
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']
@ -70,15 +70,15 @@ class Ansi
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'
@ -113,27 +113,27 @@ class Ansi
when 53 then @_append 'overline'
when 54 then @_remove 'framed'
when 55 then @_remove 'overline'
getClasses: => @klasses.join ' '
_openSpan: (text) => "<span class=\"#{@getClasses()}\">#{text}"
_closeSpan: (text) => "#{text}</span>"
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
return @_closeSpan text
# Helper functions
spanHtml = (klass, content) -> "<span class=\"#{klass}\">#{content or ''}</span>"
class JQConsole
# Creates a console.
# @arg container: The DOM element into which the console is inserted.
@ -143,21 +143,21 @@ class JQConsole
# 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) ->
constructor: (outer_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
prompt_label
else
DEFAULT_PROMPT_LABEL
@prompt_label_continue = (prompt_continue_label or
DEFAULT_PROMPT_CONINUE_LABEL)
@ -193,60 +193,76 @@ class JQConsole
# A table of custom shortcuts, mapping character codes to callbacks.
@shortcuts = {}
@$container = $('<div/>').appendTo outer_container
@$container.css
'top': 0
'left': 0
'right': 0
'bottom': 0
'position': 'absolute'
'overflow': 'auto'
# The main console area. Everything else happens inside this.
@$console = $('<pre class="jqconsole"/>').appendTo @container
@$console.css
position: 'absolute'
top: 0
bottom: 0
right: 0
left: 0
margin: 0
overflow: 'auto'
@$console = $('<pre class="jqconsole"/>').appendTo @$container
@$console.css
'margin': 0
'position': 'relative'
'min-height': '100%'
'box-sizing': 'border-box'
'-moz-box-sizing': 'border-box'
'-webkit-box-sizing': 'border-box'
# 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 = $(EMPTY_DIV).appendTo @$container
@$input_container.css
position: 'relative'
position: 'absolute'
width: 1
height: 0
overflow: 'hidden'
@$input_source = $('<textarea/>')
@$input_source.attr('wrap', 'off').css
# On android autocapitlize works for input.
@$input_source = if @isAndroid then $('<input/>') else $('<textarea/>')
@$input_source.attr
wrap: 'off'
autocapitalize: 'off'
autocorrect: 'off'
spellcheck: 'false'
autocomplete: 'off'
@$input_source.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
# 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 =
@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
$(outer_container).data 'jqconsole', this
#### Reset methods
@ -260,7 +276,7 @@ class JQConsole
# Resets the matching configuration.
ResetMatchings: ->
@matchings =
@matchings =
openings: {}
closings: {}
clss: []
@ -279,23 +295,23 @@ class JQConsole
@$input_container.detach()
@$console.html ''
@$prompt.appendTo @$console
@$input_container.appendTo @container
@$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) ->
@ -306,16 +322,16 @@ class JQConsole
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
@ -330,10 +346,10 @@ class JQConsole
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
@ -343,24 +359,26 @@ class JQConsole
# 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_right.detach()
@$prompt_cursor.text ''
lines = @$console.text().split NEWLINE
@$prompt_cursor.html '&nbsp;'
@$prompt_cursor.after @$prompt_right
return lines[lines.length - 1].length
# Returns the 0-based number of the line on which the cursor currently is.
@ -415,7 +433,7 @@ class JQConsole
@_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.
@ -431,12 +449,12 @@ class JQConsole
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
# Adds a dom node, where any text would have been inserted
# @arg node: The node to insert.
Append: (node) ->
$node = $(node).insertBefore @$prompt
@ -444,7 +462,7 @@ class JQConsole
# 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.
@ -536,26 +554,29 @@ class JQConsole
# @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 =
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}")
$elems = @$console
.find(".#{CLASS_HEADER}")
.nextUntil(".#{CLASS_PROMPT}")
.addBack()
return (
for elem in $elems
@ -563,7 +584,7 @@ class JQConsole
$(elem).text().replace /^\s+/, '>>> '
else
$(elem).text()
).join ' '
).join ''
# Gets the current prompt state.
GetState: ->
@ -573,12 +594,12 @@ class JQConsole
'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
@ -599,6 +620,17 @@ class JQConsole
@_MoveTo all_lines, false
return undefined
# Clear the console keeping only the prompt.
Clear: ->
@$console
.find(".#{CLASS_HEADER}")
.nextUntil(".#{CLASS_PROMPT}")
.addBack()
.text ''
# Bug in Chrome were the cursor's position is not recalculated
@$prompt_cursor.detach()
@$prompt_after.before @$prompt_cursor
###------------------------ Private Methods -------------------------------###
_CheckInputQueue: ->
@ -660,7 +692,7 @@ class JQConsole
# Binds all the required input and focus events.
_SetupEvents: ->
# Click to focus.
if @isMobile
@$console.click (e) =>
@ -672,14 +704,14 @@ class JQConsole
# paste on linux desktop.
if e.which == 2
@Focus()
else
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()
@ -691,17 +723,16 @@ class JQConsole
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, =>
@$input_source.bind 'paste', =>
handlePaste = =>
# Opera fires input on composition end.
return if @in_composition
@ -715,24 +746,25 @@ class JQConsole
@$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 'compositionstart', @_StartComposition
@$input_source.bind 'compositionend', (e) =>
# Wait for the input element to update so we don't rely on buggy e.data
setTimeout((=> @_EndComposition(e)), 0)
# Android has an out of screen text input for autocorrect and autocomplete
# and since it doesn't allow disabling we use composition events and more
# hacks to get input to work.
if @isAndroid
# Text is handled via composition events but for things like spaces
# we need to emulate a composition start.
@$input_source.bind 'input', @_StartComposition
@$input_source.bind 'input', @_UpdateComposition
else
@$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) =>
@ -741,26 +773,17 @@ class JQConsole
# 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.
# 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
@ -770,12 +793,12 @@ class JQConsole
_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
@ -789,8 +812,8 @@ class JQConsole
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()
when KEY_PAGE_UP then @_ScrollPage 'up'
when KEY_PAGE_DOWN then @_ScrollPage 'down'
# Allow other Shift shortcuts to pass through to the browser.
else return true
return false
@ -807,8 +830,8 @@ class JQConsole
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()
when KEY_PAGE_UP then @_ScrollPage 'up'
when KEY_PAGE_DOWN then @_ScrollPage 'down'
# Let any other key continue its way to keypress.
else return true
return false
@ -839,6 +862,7 @@ class JQConsole
# Handles the user pressing the Enter key.
# @arg shift: Whether the shift key is held.
_HandleEnter: (shift) ->
@_EndComposition()
if shift
@_InsertNewLine true
else
@ -863,7 +887,7 @@ class JQConsole
@input_callback = null
if callback then callback text
@_CheckInputQueue()
if @multiline_callback
if @async_multiline
@multiline_callback text, continuation
@ -871,8 +895,8 @@ class JQConsole
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) ->
@ -882,11 +906,11 @@ class JQConsole
$prompt_rel_opposite = if back then @$prompt_after else @$prompt_before
MoveToLimit = if back
$.proxy @MoveToStart, @
else
else
$.proxy @MoveToEnd, @
MoveDirection = if back
$.proxy @_MoveLeft, @
else
$.proxy @_MoveLeft, @
else
$.proxy @_MoveRight, @
which_end = if back then 'last' else 'first'
where_append = if back then 'prependTo' else 'appendTo'
@ -900,7 +924,7 @@ class JQConsole
which_end
where_append
}
# Moves the cursor vertically in the current prompt,
# in the same column. (Used by _MoveUp, _MoveDown)
_VerticalMove: (up) ->
@ -911,7 +935,7 @@ class JQConsole
MoveToLimit
MoveDirection
} = @_GetDirectionals(up)
if $prompt_relative.is EMPTY_SELECTOR then return
pos = @$prompt_left.text().length
MoveToLimit()
@ -919,8 +943,8 @@ class JQConsole
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
@ -928,7 +952,7 @@ class JQConsole
# 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) ->
@ -941,7 +965,7 @@ class JQConsole
where_append
} = @_GetDirectionals(back)
regexp = if back then /\w*\W*$/ else /^\w*\W*/
text = $prompt_which.text()
if text
if whole_word
@ -960,12 +984,12 @@ class JQConsole
$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) ->
@ -975,7 +999,7 @@ class JQConsole
# @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) ->
{
@ -985,7 +1009,7 @@ class JQConsole
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() == ''
@ -1069,57 +1093,58 @@ class JQConsole
@_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 down/up one page (with animation).
_ScrollPage: (dir) ->
target = @$container[0].scrollTop
if dir == 'up'
target -= @$container.height()
else
target += @$container.height()
@$container.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
@$container.scrollTop @$container[0].scrollHeight
# Move the input element to the cursor position.
pos = @$prompt_cursor.position()
@$input_container.css
left: pos.left
top: pos.top
# Give time for mobile browsers to zoom in on textarea
setTimeout @ScrollWindowToPrompt.bind(@), 50
ScrollWindowToPrompt: ->
# 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
line_height = @$prompt_cursor.height()
screen_top = @$window.scrollTop()
screen_left = @$window.scrollLeft()
doc_height = document.documentElement.clientHeight
pos = @$prompt_cursor.offset()
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
# Selects the prompt label appropriate to the current mode.
# @arg continuation: If true, returns the continuation prompt rather than
# the main one.
@ -1128,14 +1153,7 @@ class JQConsole
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 <span> having a class
# @arg $elem: The JqDom element in question
# @arg index: the index of the character to be wrapped
@ -1146,7 +1164,7 @@ class JQConsole
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
@ -1170,11 +1188,11 @@ class JQConsole
current_count++
else if ch is opposing_char
current_count--
if current_count is 0
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
[
@ -1187,7 +1205,7 @@ class JQConsole
config['closing_char']
]
{$prompt_which, $prompt_relative} = @_GetDirectionals(back)
current_count = 1
found = false
# check current line first
@ -1210,40 +1228,40 @@ class JQConsole
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
# 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
# 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
@ -1261,48 +1279,48 @@ class JQConsole
@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
return if @in_composition
@in_composition = true
@_ShowComposition()
setTimeout @_UpdateComposition, 0
# Ends a multibyte character composition.
_EndComposition: =>
@$input_source.unbind E_KEYPRESS, @_EndComposition
@in_composition = false
return if not @in_composition
@_HideComposition()
@$prompt_left.text @$prompt_left.text() + @$composition.text()
@$composition.text ''
@$input_source.val ''
@in_composition = false
# 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

View file

@ -0,0 +1,29 @@
{jqconsole, createScroll, typer: {typeA, keyDown, type}} = jqconsoleSetup()
describe 'Matching', ->
afterEach ->
jqconsole.AbortPrompt()
describe '#RegisterMatching', ->
it 'Adds matching', (done) ->
jqconsole.Prompt true, ->
jqconsole.RegisterMatching '(', ')', 'parens'
type 'foo ( bar )'
keyDown 39
check = ->
$parens = jqconsole.$prompt.find '.parens'
assert.equal $parens.first().text(), '('
assert.equal $parens.last().text(), ')'
done()
setTimeout check, 0
describe '#UnRegsiterMatching', ->
it 'Removes matching', (done) ->
jqconsole.Prompt true, ->
jqconsole.UnRegisterMatching '(', ')'
type 'foo ( bar )'
keyDown 39
check = ->
assert.ok !jqconsole.$prompt.find('.parens').length
done()
setTimeout check, 0

View file

@ -0,0 +1,65 @@
jqconsole = type = keyDown = null
describe 'Misc methods', ->
beforeEach ->
{jqconsole, typer: {keyDown, type}} = jqconsoleSetup()
jqconsole.Prompt true, ->
describe '#GetColumn', ->
it 'should get the column number of the cursor', ->
label_length = 'headerprompt_label'.length
assert.equal jqconsole.GetColumn(), label_length
type ' '
assert.equal jqconsole.GetColumn(), label_length + 3
describe '#GetLine', ->
it 'should get the line number of the cursor', ->
assert.equal jqconsole.GetLine(), 0
keyDown 13, shiftKey: on
assert.equal jqconsole.GetLine(), 1
keyDown 13, shiftKey: on
assert.equal jqconsole.GetLine(), 2
describe '#SetIndentWidth', ->
it 'changes the indent width', ->
l = jqconsole.GetColumn()
jqconsole.SetIndentWidth 10
keyDown 9
assert.equal jqconsole.GetColumn(), l + 10
describe '#GetIndentWidth', ->
it 'gets the indent width', ->
jqconsole.SetIndentWidth 20
assert.equal jqconsole.GetIndentWidth(), 20
describe '#Dump', ->
it 'dumps the console content', ->
type 'foo'
keyDown 13
jqconsole.Write('wat')
assert.equal jqconsole.Dump(), 'headerprompt_labelfoo\nwat'
describe '#SetPromptLabel', ->
it 'Sets the prompt label for the next prompt', ->
jqconsole.SetPromptLabel 'foobar123', 'shitmang'
keyDown 13, shiftKey: on
jqconsole.AbortPrompt()
jqconsole.Prompt true, ->
assert.ok jqconsole.Dump().indexOf 'foobar123' > -1
assert.ok jqconsole.Dump().indexOf 'shitmang' > -1
describe '#Disable', ->
it 'disables the console', ->
jqconsole.Disable()
assert.ok jqconsole.$input_source.attr 'disabled'
describe '#Enable', ->
it 'enables the console', ->
jqconsole.Disable()
jqconsole.Enable()
assert.ok not jqconsole.$input_source.attr 'disabled'
describe '#Clear', ->
it 'clears the console', ->
jqconsole.Clear()
assert.equal jqconsole.Dump(), ''

View file

@ -390,5 +390,11 @@ describe 'Prompt Interaction', ->
type('foo')
keyDown 13
describe '#Input', ->
it 'should enable history', (done) ->
jqconsole.Prompt true, done.bind(null, null)
jqconsole.Input (text) ->
assert.equal text, 'foo'
setTimeout (-> keyDown(13)), 0
type 'foo'
keyDown 13