diff --git a/jquery.console.js b/jquery.console.js index 794fc23..e8f0be8 100644 --- a/jquery.console.js +++ b/jquery.console.js @@ -37,799 +37,809 @@ // Safari 4.0.5 (6531.22.7) (Mac) // Google Chrome 5.0.375.55 (Mac) -(function($){ - var isWebkit = !!~navigator.userAgent.indexOf(' AppleWebKit/'); - - $.fn.console = function(config){ - //////////////////////////////////////////////////////////////////////// - // Constants - // Some are enums, data types, others just for optimisation - var keyCodes = { - // left - 37: moveBackward, - // right - 39: moveForward, - // up - 38: previousHistory, - // down - 40: nextHistory, - // backspace - 8: backDelete, - // delete - 46: forwardDelete, - // end - 35: moveToEnd, - // start - 36: moveToStart, - // return - 13: commandTrigger, - // tab - 18: doNothing, - // tab - 9: doComplete - }; - var ctrlCodes = { - // C-a - 65: moveToStart, - // C-e - 69: moveToEnd, - // C-d - 68: forwardDelete, - // C-n - 78: nextHistory, - // C-p - 80: previousHistory, - // C-b - 66: moveBackward, - // C-f - 70: moveForward, - // C-k - 75: deleteUntilEnd, - // C-l - 76: clearScreen, - // C-u - 85: clearCurrentPrompt - }; - if(config.ctrlCodes) { - $.extend(ctrlCodes, config.ctrlCodes); - } - var altCodes = { - // M-f - 70: moveToNextWord, - // M-b - 66: moveToPreviousWord, - // M-d - 68: deleteNextWord - }; - var shiftCodes = { - // return - 13: newLine, - }; - var cursor = ' '; - - //////////////////////////////////////////////////////////////////////// - // Globals - var container = $(this); - var inner = $('
'); - // erjiang: changed this from a text input to a textarea so we - // can get pasted newlines - var typer = $(''); - // Prompt - var promptBox; - var prompt; - var continuedPromptLabel = config && config.continuedPromptLabel? - config.continuedPromptLabel : "> "; - var column = 0; - var promptText = ''; - var restoreText = ''; - var continuedText = ''; - var fadeOnReset = config.fadeOnReset !== undefined ? config.fadeOnReset : true; - // Prompt history stack - var history = []; - var ringn = 0; - // For reasons unknown to The Sword of Michael himself, Opera - // triggers and sends a key character when you hit various - // keys like PgUp, End, etc. So there is no way of knowing - // when a user has typed '#' or End. My solution is in the - // typer.keydown and typer.keypress functions; I use the - // variable below to ignore the keypress event if the keydown - // event succeeds. - var cancelKeyPress = 0; - // When this value is false, the prompt will not respond to input - var acceptInput = true; - // When this value is true, the command has been canceled - var cancelCommand = false; - - // External exports object - var extern = {}; - - //////////////////////////////////////////////////////////////////////// - // Main entry point - (function(){ - extern.promptLabel = config && config.promptLabel? config.promptLabel : "> "; - container.append(inner); - inner.append(typer); - typer.css({position:'absolute',top:0,left:'-9999px'}); - if (config.welcomeMessage) - message(config.welcomeMessage,'jquery-console-welcome'); - newPromptBox(); - if (config.autofocus) { - inner.addClass('jquery-console-focus'); - typer.focus(); - setTimeout(function(){ - inner.addClass('jquery-console-focus'); - typer.focus(); - },100); - } - extern.inner = inner; - extern.typer = typer; - extern.scrollToBottom = scrollToBottom; - extern.report = report; - extern.showCompletion = showCompletion; - extern.clearScreen = clearScreen; - })(); - - //////////////////////////////////////////////////////////////////////// - // Reset terminal - extern.reset = function(){ - var welcome = (typeof config.welcomeMessage != 'undefined'); - - var removeElements = function() { - inner.find('div').each(function(){ - if (!welcome) { - $(this).remove(); - } else { - welcome = false; - } - }); - }; - - if (fadeOnReset) { - inner.parent().fadeOut(function() { - removeElements(); - newPromptBox(); - inner.parent().fadeIn(focusConsole); - }); - } - else { - removeElements(); - newPromptBox(); - focusConsole(); - } - }; - - var focusConsole = function() { - inner.addClass('jquery-console-focus'); - typer.focus(); - }; - - extern.focus = function(){ - focusConsole(); - } - - //////////////////////////////////////////////////////////////////////// - // Reset terminal - extern.notice = function(msg,style){ - var n = $('
').append($('
').text(msg)) - .css({visibility:'hidden'}); - container.append(n); - var focused = true; - if (style=='fadeout') - setTimeout(function(){ - n.fadeOut(function(){ - n.remove(); - }); - },4000); - else if (style=='prompt') { - var a = $('
OK
'); - n.append(a); - focused = false; - a.click(function(){ n.fadeOut(function(){ n.remove();inner.css({opacity:1}) }); }); - } - var h = n.height(); - n.css({height:'0px',visibility:'visible'}) - .animate({height:h+'px'},function(){ - if (!focused) inner.css({opacity:0.5}); - }); - n.css('cursor','default'); - return n; - }; - - //////////////////////////////////////////////////////////////////////// - // Make a new prompt box - function newPromptBox() { - column = 0; - promptText = ''; - ringn = 0; // Reset the position of the history ring - enableInput(); - promptBox = $('
'); - var label = $(''); - var labelText = extern.continuedPrompt? continuedPromptLabel : extern.promptLabel; - promptBox.append(label.text(labelText).show()); - label.html(label.html().replace(' ',' ')); - prompt = $(''); - promptBox.append(prompt); - inner.append(promptBox); - updatePromptDisplay(); - }; - - //////////////////////////////////////////////////////////////////////// - // Handle setting focus - container.click(function(){ - // Don't mess with the focus if there is an active selection - if (window.getSelection().toString()) { - return false; - } - - inner.addClass('jquery-console-focus'); - inner.removeClass('jquery-console-nofocus'); - if (isWebkit) { - typer.focusWithoutScrolling(); - } else { - typer.css('position', 'fixed').focus(); - } - scrollToBottom(); - return false; - }); - - //////////////////////////////////////////////////////////////////////// - // Handle losing focus - typer.blur(function(){ - inner.removeClass('jquery-console-focus'); - inner.addClass('jquery-console-nofocus'); - }); - - //////////////////////////////////////////////////////////////////////// - // Bind to the paste event of the input box so we know when we - // get pasted data - typer.bind('paste', function(e) { - // wipe typer input clean just in case - typer.val(""); - // this timeout is required because the onpaste event is - // fired *before* the text is actually pasted - setTimeout(function() { - typer.consoleInsert(typer.val()); - typer.val(""); - }, 0); - }); - - //////////////////////////////////////////////////////////////////////// - // Handle key hit before translation - // For picking up control characters like up/left/down/right - - typer.keydown(function(e){ - cancelKeyPress = 0; - var keyCode = e.keyCode; - // C-c: cancel the execution - if(e.ctrlKey && keyCode == 67) { - cancelKeyPress = keyCode; - cancelExecution(); - return false; - } - if (acceptInput) { - if (e.shiftKey && keyCode in shiftCodes) { - cancelKeyPress = keyCode; - (shiftCodes[keyCode])(); - return false; - } else if (e.altKey && keyCode in altCodes) { - cancelKeyPress = keyCode; - (altCodes[keyCode])(); - return false; - } else if (e.ctrlKey && keyCode in ctrlCodes) { - cancelKeyPress = keyCode; - (ctrlCodes[keyCode])(); - return false; - } else if (keyCode in keyCodes) { - cancelKeyPress = keyCode; - (keyCodes[keyCode])(); - return false; - } - } - }); - - //////////////////////////////////////////////////////////////////////// - // Handle key press - typer.keypress(function(e){ - var keyCode = e.keyCode || e.which; - if (isIgnorableKey(e)) { - return false; - } - // C-v: don't insert on paste event - if ((e.ctrlKey || e.metaKey) && String.fromCharCode(keyCode).toLowerCase() == 'v') { - return true; - } - if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32){ - if (cancelKeyPress) return false; - if ( - typeof config.charInsertTrigger == 'undefined' || ( - typeof config.charInsertTrigger == 'function' && - config.charInsertTrigger(keyCode,promptText) - ) - ){ - typer.consoleInsert(keyCode); +(function($) { + var isWebkit = !!~navigator.userAgent.indexOf(' AppleWebKit/'); + + $.fn.console = function(config) { + //////////////////////////////////////////////////////////////////////// + // Constants + // Some are enums, data types, others just for optimisation + var keyCodes = { + // left + 37: moveBackward, + // right + 39: moveForward, + // up + 38: previousHistory, + // down + 40: nextHistory, + // backspace + 8: backDelete, + // delete + 46: forwardDelete, + // end + 35: moveToEnd, + // start + 36: moveToStart, + // return + 13: commandTrigger, + // tab + 18: doNothing, + // tab + 9: doComplete + }; + var ctrlCodes = { + // C-a + 65: moveToStart, + // C-e + 69: moveToEnd, + // C-d + 68: forwardDelete, + // C-n + 78: nextHistory, + // C-p + 80: previousHistory, + // C-b + 66: moveBackward, + // C-f + 70: moveForward, + // C-k + 75: deleteUntilEnd, + // C-l + 76: clearScreen, + // C-u + 85: clearCurrentPrompt + }; + if (config.ctrlCodes) { + $.extend(ctrlCodes, config.ctrlCodes); } - } - if (isWebkit) return false; - }); - - function isIgnorableKey(e) { - // for now just filter alt+tab that we receive on some platforms when - // user switches windows (goes away from the browser) - return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey); - }; - - //////////////////////////////////////////////////////////////////////// - // Rotate through the command history - function rotateHistory(n){ - if (history.length == 0) return; - ringn += n; - if (ringn < 0) ringn = history.length; - else if (ringn > history.length) ringn = 0; - var prevText = promptText; - if (ringn == 0) { - promptText = restoreText; - } else { - promptText = history[ringn - 1]; - } - if (config.historyPreserveColumn) { - if (promptText.length < column + 1) { - column = promptText.length; - } else if (column == 0) { - column = promptText.length; - } - } else { - column = promptText.length; - } - updatePromptDisplay(); - }; - - function previousHistory() { - rotateHistory(-1); - }; - - function nextHistory() { - rotateHistory(1); - }; - - // Add something to the history ring - function addToHistory(line){ - history.push(line); - restoreText = ''; - }; - - // Delete the character at the current position - function deleteCharAtPos(){ - if (column < promptText.length){ - promptText = - promptText.substring(0,column) + - promptText.substring(column+1); - restoreText = promptText; - return true; - } else return false; - }; - - function backDelete() { - if (moveColumn(-1)){ - deleteCharAtPos(); - updatePromptDisplay(); - } - }; - - function forwardDelete() { - if (deleteCharAtPos()){ - updatePromptDisplay(); - } - }; - - function deleteUntilEnd() { - while(deleteCharAtPos()) { - updatePromptDisplay(); - } - }; - - function clearCurrentPrompt() { - extern.promptText(""); - }; - - function clearScreen() { - inner.children(".jquery-console-prompt-box, .jquery-console-message").slice(0, -1).remove(); - extern.report(" "); - extern.focus(); - }; - - function deleteNextWord() { - // A word is defined within this context as a series of alphanumeric - // characters. - // Delete up to the next alphanumeric character - while( - column < promptText.length && - !isCharAlphanumeric(promptText[column]) - ) { - deleteCharAtPos(); - updatePromptDisplay(); - } - // Then, delete until the next non-alphanumeric character - while( - column < promptText.length && - isCharAlphanumeric(promptText[column]) - ) { - deleteCharAtPos(); - updatePromptDisplay(); - } - }; + var altCodes = { + // M-f + 70: moveToNextWord, + // M-b + 66: moveToPreviousWord, + // M-d + 68: deleteNextWord + }; + var shiftCodes = { + // return + 13: newLine, + }; + var cursor = ' '; + + //////////////////////////////////////////////////////////////////////// + // Globals + var container = $(this); + var inner = $('
'); + // erjiang: changed this from a text input to a textarea so we + // can get pasted newlines + var typer = $(''); + // Prompt + var promptBox; + var prompt; + var continuedPromptLabel = config && config.continuedPromptLabel ? + config.continuedPromptLabel : "> "; + var column = 0; + var promptText = ''; + var restoreText = ''; + var continuedText = ''; + var fadeOnReset = config.fadeOnReset !== undefined ? config.fadeOnReset : true; + // Prompt history stack + var history = []; + var ringn = 0; + // For reasons unknown to The Sword of Michael himself, Opera + // triggers and sends a key character when you hit various + // keys like PgUp, End, etc. So there is no way of knowing + // when a user has typed '#' or End. My solution is in the + // typer.keydown and typer.keypress functions; I use the + // variable below to ignore the keypress event if the keydown + // event succeeds. + var cancelKeyPress = 0; + // When this value is false, the prompt will not respond to input + var acceptInput = true; + // When this value is true, the command has been canceled + var cancelCommand = false; + + // External exports object + var extern = {}; + + //////////////////////////////////////////////////////////////////////// + // Main entry point + (function() { + extern.promptLabel = config && config.promptLabel ? config.promptLabel : "> "; + container.append(inner); + inner.append(typer); + typer.css({ position: 'absolute', top: 0, left: '-9999px' }); + if (config.welcomeMessage) + message(config.welcomeMessage, 'jquery-console-welcome'); + newPromptBox(); + if (config.autofocus) { + inner.addClass('jquery-console-focus'); + typer.focus(); + setTimeout(function() { + inner.addClass('jquery-console-focus'); + typer.focus(); + }, 100); + } + extern.inner = inner; + extern.typer = typer; + extern.scrollToBottom = scrollToBottom; + extern.report = report; + extern.showCompletion = showCompletion; + extern.clearScreen = clearScreen; + })(); + + //////////////////////////////////////////////////////////////////////// + // Reset terminal + extern.reset = function() { + var welcome = (typeof config.welcomeMessage != 'undefined'); + + var removeElements = function() { + inner.find('div').each(function() { + if (!welcome) { + $(this).remove(); + } else { + welcome = false; + } + }); + }; + + if (fadeOnReset) { + inner.parent().fadeOut(function() { + removeElements(); + newPromptBox(); + inner.parent().fadeIn(focusConsole); + }); + } else { + removeElements(); + newPromptBox(); + focusConsole(); + } + }; - function newLine() { - var lines = promptText.split("\n"); - var last_line = lines.slice(-1)[0]; - var spaces = last_line.match(/^(\s*)/g)[0]; - var new_line = "\n" + spaces; - promptText += new_line; - moveColumn(new_line.length); - updatePromptDisplay(); - }; + var focusConsole = function() { + inner.addClass('jquery-console-focus'); + typer.focus(); + }; - //////////////////////////////////////////////////////////////////////// - // Validate command and trigger it if valid, or show a validation error - function commandTrigger() { - var line = promptText; - if (typeof config.commandValidate == 'function') { - var ret = config.commandValidate(line); - if (ret == true || ret == false) { - if (ret) { - handleCommand(); - } - } else { - commandResult(ret,"jquery-console-message-error"); + extern.focus = function() { + focusConsole(); } - } else { - handleCommand(); - } - }; - // Scroll to the bottom of the view - function scrollToBottom() { - var version = jQuery.fn.jquery.split('.'); - var major = parseInt(version[0]); - var minor = parseInt(version[1]); - - // check if we're using jquery > 1.6 - if ((major == 1 && minor > 6) || major > 1) { - inner.prop({ scrollTop: inner.prop("scrollHeight") }); - } - else { - inner.attr({ scrollTop: inner.attr("scrollHeight") }); - } - }; + //////////////////////////////////////////////////////////////////////// + // Reset terminal + extern.notice = function(msg, style) { + var n = $('
').append($('
').text(msg)) + .css({ visibility: 'hidden' }); + container.append(n); + var focused = true; + if (style == 'fadeout') + setTimeout(function() { + n.fadeOut(function() { + n.remove(); + }); + }, 4000); + else if (style == 'prompt') { + var a = $('
OK
'); + n.append(a); + focused = false; + a.click(function() { + n.fadeOut(function() { + n.remove(); + inner.css({ opacity: 1 }) + }); + }); + } + var h = n.height(); + n.css({ height: '0px', visibility: 'visible' }) + .animate({ height: h + 'px' }, function() { + if (!focused) inner.css({ opacity: 0.5 }); + }); + n.css('cursor', 'default'); + return n; + }; + + //////////////////////////////////////////////////////////////////////// + // Make a new prompt box + function newPromptBox() { + column = 0; + promptText = ''; + ringn = 0; // Reset the position of the history ring + enableInput(); + promptBox = $('
'); + var label = $(''); + var labelText = extern.continuedPrompt ? continuedPromptLabel : extern.promptLabel; + promptBox.append(label.text(labelText).show()); + label.html(label.html().replace(' ', ' ')); + prompt = $(''); + promptBox.append(prompt); + inner.append(promptBox); + updatePromptDisplay(); + }; + + //////////////////////////////////////////////////////////////////////// + // Handle setting focus + container.click(function() { + // Don't mess with the focus if there is an active selection + if (window.getSelection().toString()) { + return false; + } - function cancelExecution() { - if(typeof config.cancelHandle == 'function') { - config.cancelHandle(); - } - } - - //////////////////////////////////////////////////////////////////////// - // Handle a command - function handleCommand() { - if (typeof config.commandHandle == 'function') { - disableInput(); - addToHistory(promptText); - var text = promptText; - if (extern.continuedPrompt) { - if (continuedText) - continuedText += '\n' + promptText; - else continuedText = promptText; - } else continuedText = undefined; - if (continuedText) text = continuedText; - var ret = config.commandHandle(text,function(msgs){ - commandResult(msgs); + inner.addClass('jquery-console-focus'); + inner.removeClass('jquery-console-nofocus'); + if (isWebkit) { + typer.focusWithoutScrolling(); + } else { + typer.css('position', 'fixed').focus(); + } + scrollToBottom(); + return false; }); - if (extern.continuedPrompt && !continuedText) - continuedText = promptText; - if (typeof ret == 'boolean') { - if (ret) { - // Command succeeded without a result. - commandResult(); - } else { - commandResult( - 'Command failed.', - "jquery-console-message-error" - ); - } - } else if (typeof ret == "string") { - commandResult(ret,"jquery-console-message-success"); - } else if (typeof ret == 'object' && ret.length) { - commandResult(ret); - } else if (extern.continuedPrompt) { - commandResult(); - } - } - }; - //////////////////////////////////////////////////////////////////////// - // Disable input - function disableInput() { - acceptInput = false; - }; + //////////////////////////////////////////////////////////////////////// + // Handle losing focus + typer.blur(function() { + inner.removeClass('jquery-console-focus'); + inner.addClass('jquery-console-nofocus'); + }); - // Enable input - function enableInput() { - acceptInput = true; - } - - //////////////////////////////////////////////////////////////////////// - // Reset the prompt in invalid command - function commandResult(msg,className) { - column = -1; - updatePromptDisplay(); - if (typeof msg == 'string') { - message(msg,className); - } else if ($.isArray(msg)) { - for (var x in msg) { - var ret = msg[x]; - message(ret.msg,ret.className); + //////////////////////////////////////////////////////////////////////// + // Bind to the paste event of the input box so we know when we + // get pasted data + typer.bind('paste', function(e) { + // wipe typer input clean just in case + typer.val(""); + // this timeout is required because the onpaste event is + // fired *before* the text is actually pasted + setTimeout(function() { + typer.consoleInsert(typer.val()); + typer.val(""); + }, 0); + }); + //helper function to figure out new character added to input + difference = function(value1, value2) { + var output = []; + for (i = 0; i < value2.length; i++) { + if (value1[i] !== value2[i]) { + output.push(value2[i]); + } + } + return output.join(""); } - } else { // Assume it's a DOM node or jQuery object. - inner.append(msg); - } - newPromptBox(); - }; - - //////////////////////////////////////////////////////////////////////// - // Report some message into the console - function report(msg,className) { - var text = promptText; - promptBox.remove(); - commandResult(msg,className); - extern.promptText(text); - }; - - //////////////////////////////////////////////////////////////////////// - // Display a message - function message(msg,className) { - var mesg = $('
'); - if (className) mesg.addClass(className); - mesg.filledText(msg).hide(); - inner.append(mesg); - mesg.show(); - }; + //////////////////////////////////////////////////////////////////////// + // Handle key hit before translation + // For picking up control characters like up/left/down/right + typer.keydown(function(e) { + typer.oldValue = typer.val(); + oldValue = typer.val(); + cancelKeyPress = 0; + var keyCode = e.which || e.code; + // C-c: cancel the execution + if (e.ctrlKey && keyCode == 67) { + cancelKeyPress = keyCode; + cancelExecution(); + return false; + } + if (acceptInput) { + if (e.shiftKey && keyCode in shiftCodes) { + cancelKeyPress = keyCode; + (shiftCodes[keyCode])(); + return false; + } else if (e.altKey && keyCode in altCodes) { + cancelKeyPress = keyCode; + (altCodes[keyCode])(); + return false; + } else if (e.ctrlKey && keyCode in ctrlCodes) { + cancelKeyPress = keyCode; + (ctrlCodes[keyCode])(); + return false; + } else if (keyCode in keyCodes) { + cancelKeyPress = keyCode; + (keyCodes[keyCode])(); + return false; + } + } + if (isIgnorableKey(e)) { + return false; + } + // C-v: don't insert on paste event + if ((e.ctrlKey || e.metaKey) && String.fromCharCode(keyCode).toLowerCase() == 'v') { + return true; + } + if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32) { + if (cancelKeyPress) return false; + } - //////////////////////////////////////////////////////////////////////// - // Handle normal character insertion - // data can either be a number, which will be interpreted as the - // numeric value of a single character, or a string - typer.consoleInsert = function(data){ - // TODO: remove redundant indirection - var text = (typeof data == 'number') ? String.fromCharCode(data) : data; - var before = promptText.substring(0,column); - var after = promptText.substring(column); - promptText = before + text + after; - moveColumn(text.length); - restoreText = promptText; - updatePromptDisplay(); - }; - //////////////////////////////////////////////////////////////////////// - // Move to another column relative to this one - // Negative means go back, positive means go forward. - function moveColumn(n){ - if (column + n >= 0 && column + n <= promptText.length){ - column += n; - return true; - } else return false; - }; + }); - function moveForward() { - if(moveColumn(1)) { - updatePromptDisplay(); - return true; - } - return false; - }; - function moveBackward() { - if(moveColumn(-1)) { - updatePromptDisplay(); - return true; - } - return false; - }; + //after input event get new string, figure out the difference and add the character to the typer. + typer.on("input", function(e) { + var newValue = typer.val(); + typer.consoleInsert(difference(typer.oldValue, newValue)); + typer.val(""); + }) + + + function isIgnorableKey(e) { + // for now just filter alt+tab that we receive on some platforms when + // user switches windows (goes away from the browser) + return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey); + }; + + //////////////////////////////////////////////////////////////////////// + // Rotate through the command history + function rotateHistory(n) { + if (history.length == 0) return; + ringn += n; + if (ringn < 0) ringn = history.length; + else if (ringn > history.length) ringn = 0; + var prevText = promptText; + if (ringn == 0) { + promptText = restoreText; + } else { + promptText = history[ringn - 1]; + } + if (config.historyPreserveColumn) { + if (promptText.length < column + 1) { + column = promptText.length; + } else if (column == 0) { + column = promptText.length; + } + } else { + column = promptText.length; + } + updatePromptDisplay(); + }; + + function previousHistory() { + rotateHistory(-1); + }; + + function nextHistory() { + rotateHistory(1); + }; + + // Add something to the history ring + function addToHistory(line) { + history.push(line); + restoreText = ''; + }; + + // Delete the character at the current position + function deleteCharAtPos() { + if (column < promptText.length) { + promptText = + promptText.substring(0, column) + + promptText.substring(column + 1); + restoreText = promptText; + return true; + } else return false; + }; + + function backDelete() { + if (moveColumn(-1)) { + deleteCharAtPos(); + updatePromptDisplay(); + } + }; - function moveToStart() { - if (moveColumn(-column)) - updatePromptDisplay(); - }; + function forwardDelete() { + if (deleteCharAtPos()) { + updatePromptDisplay(); + } + }; - function moveToEnd() { - if (moveColumn(promptText.length-column)) - updatePromptDisplay(); - }; + function deleteUntilEnd() { + while (deleteCharAtPos()) { + updatePromptDisplay(); + } + }; + + function clearCurrentPrompt() { + extern.promptText(""); + }; + + function clearScreen() { + inner.children(".jquery-console-prompt-box, .jquery-console-message").slice(0, -1).remove(); + extern.report(" "); + extern.focus(); + }; + + function deleteNextWord() { + // A word is defined within this context as a series of alphanumeric + // characters. + // Delete up to the next alphanumeric character + while ( + column < promptText.length && + !isCharAlphanumeric(promptText[column]) + ) { + deleteCharAtPos(); + updatePromptDisplay(); + } + // Then, delete until the next non-alphanumeric character + while ( + column < promptText.length && + isCharAlphanumeric(promptText[column]) + ) { + deleteCharAtPos(); + updatePromptDisplay(); + } + }; + + function newLine() { + var lines = promptText.split("\n"); + var last_line = lines.slice(-1)[0]; + var spaces = last_line.match(/^(\s*)/g)[0]; + var new_line = "\n" + spaces; + promptText += new_line; + moveColumn(new_line.length); + updatePromptDisplay(); + }; + + //////////////////////////////////////////////////////////////////////// + // Validate command and trigger it if valid, or show a validation error + function commandTrigger() { + var line = promptText; + if (typeof config.commandValidate == 'function') { + var ret = config.commandValidate(line); + if (ret == true || ret == false) { + if (ret) { + handleCommand(); + } + } else { + commandResult(ret, "jquery-console-message-error"); + } + } else { + handleCommand(); + } + }; - function moveToNextWord() { - while( - column < promptText.length && - !isCharAlphanumeric(promptText[column]) && - moveForward() - ) {} - while( - column < promptText.length && - isCharAlphanumeric(promptText[column]) && - moveForward() - ) {} - }; + // Scroll to the bottom of the view + function scrollToBottom() { + var version = jQuery.fn.jquery.split('.'); + var major = parseInt(version[0]); + var minor = parseInt(version[1]); - function moveToPreviousWord() { - // Move backward until we find the first alphanumeric - while( - column -1 >= 0 && - !isCharAlphanumeric(promptText[column-1]) && - moveBackward() - ) {} - // Move until we find the first non-alphanumeric - while( - column -1 >= 0 && - isCharAlphanumeric(promptText[column-1]) && - moveBackward() - ) {} - }; + // check if we're using jquery > 1.6 + if ((major == 1 && minor > 6) || major > 1) { + inner.prop({ scrollTop: inner.prop("scrollHeight") }); + } else { + inner.attr({ scrollTop: inner.attr("scrollHeight") }); + } + }; - function isCharAlphanumeric(charToTest) { - if(typeof charToTest == 'string') { - var code = charToTest.charCodeAt(); - return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) || - (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) || - (code >= '0'.charCodeAt() && code <= '9'.charCodeAt()); - } - return false; - }; + function cancelExecution() { + if (typeof config.cancelHandle == 'function') { + config.cancelHandle(); + } + } - function doComplete() { - if(typeof config.completeHandle == 'function') { - doCompleteDirectly(); - } else { - issueComplete(); + //////////////////////////////////////////////////////////////////////// + // Handle a command + function handleCommand() { + if (typeof config.commandHandle == 'function') { + disableInput(); + addToHistory(promptText); + var text = promptText; + if (extern.continuedPrompt) { + if (continuedText) + continuedText += '\n' + promptText; + else continuedText = promptText; + } else continuedText = undefined; + if (continuedText) text = continuedText; + var ret = config.commandHandle(text, function(msgs) { + commandResult(msgs); + }); + if (extern.continuedPrompt && !continuedText) + continuedText = promptText; + if (typeof ret == 'boolean') { + if (ret) { + // Command succeeded without a result. + commandResult(); + } else { + commandResult( + 'Command failed.', + "jquery-console-message-error" + ); + } + } else if (typeof ret == "string") { + commandResult(ret, "jquery-console-message-success"); + } else if (typeof ret == 'object' && ret.length) { + commandResult(ret); + } else if (extern.continuedPrompt) { + commandResult(); + } } - }; + }; + + //////////////////////////////////////////////////////////////////////// + // Disable input + function disableInput() { + acceptInput = false; + }; - function doCompleteDirectly() { - if(typeof config.completeHandle == 'function') { - var completions = config.completeHandle(promptText); - var len = completions.length; - if (len === 1) { - extern.promptText(promptText + completions[0]); - } else if (len > 1 && config.cols) { - var prompt = promptText; - // Compute the number of rows that will fit in the width - var max = 0; - for (var i = 0;i < len;i++) { - max = Math.max(max, completions[i].length); - } - max += 2; - var n = Math.floor(config.cols / max); - var buffer = ""; - var col = 0; - for (i = 0;i < len;i++) { - var completion = completions[i]; - buffer += completions[i]; - for (var j = completion.length;j < max;j++) { - buffer += " "; - } - if (++col >= n) { - buffer += "\n"; - col = 0; - } - } - commandResult(buffer,"jquery-console-message-value"); - extern.promptText(prompt); + // Enable input + function enableInput() { + acceptInput = true; } - } - }; - function issueComplete() { - if (typeof config.completeIssuer == 'function') { - config.completeIssuer(promptText); + //////////////////////////////////////////////////////////////////////// + // Reset the prompt in invalid command + function commandResult(msg, className) { + column = -1; + updatePromptDisplay(); + if (typeof msg == 'string') { + message(msg, className); + } else if ($.isArray(msg)) { + for (var x in msg) { + var ret = msg[x]; + message(ret.msg, ret.className); + } + } else { // Assume it's a DOM node or jQuery object. + inner.append(msg); } - }; + newPromptBox(); + }; + + //////////////////////////////////////////////////////////////////////// + // Report some message into the console + function report(msg, className) { + var text = promptText; + promptBox.remove(); + commandResult(msg, className); + extern.promptText(text); + }; + + //////////////////////////////////////////////////////////////////////// + // Display a message + function message(msg, className) { + var mesg = $('
'); + if (className) mesg.addClass(className); + mesg.filledText(msg).hide(); + inner.append(mesg); + mesg.show(); + }; + + //////////////////////////////////////////////////////////////////////// + // Handle normal character insertion + // data can either be a number, which will be interpreted as the + // numeric value of a single character, or a string + typer.consoleInsert = function(data) { + // TODO: remove redundant indirection + var text = (typeof data == 'number') ? String.fromCharCode(data) : data; + var before = promptText.substring(0, column); + var after = promptText.substring(column); + promptText = before + text + after; + moveColumn(text.length); + restoreText = promptText; + updatePromptDisplay(); + }; + + //////////////////////////////////////////////////////////////////////// + // Move to another column relative to this one + // Negative means go back, positive means go forward. + function moveColumn(n) { + if (column + n >= 0 && column + n <= promptText.length) { + column += n; + return true; + } else return false; + }; + + function moveForward() { + if (moveColumn(1)) { + updatePromptDisplay(); + return true; + } + return false; + }; - function showCompletion(promptText, completions) { + function moveBackward() { + if (moveColumn(-1)) { + updatePromptDisplay(); + return true; + } + return false; + }; + + function moveToStart() { + if (moveColumn(-column)) + updatePromptDisplay(); + }; + + function moveToEnd() { + if (moveColumn(promptText.length - column)) + updatePromptDisplay(); + }; + + function moveToNextWord() { + while ( + column < promptText.length && + !isCharAlphanumeric(promptText[column]) && + moveForward() + ) {} + while ( + column < promptText.length && + isCharAlphanumeric(promptText[column]) && + moveForward() + ) {} + }; + + function moveToPreviousWord() { + // Move backward until we find the first alphanumeric + while ( + column - 1 >= 0 && + !isCharAlphanumeric(promptText[column - 1]) && + moveBackward() + ) {} + // Move until we find the first non-alphanumeric + while ( + column - 1 >= 0 && + isCharAlphanumeric(promptText[column - 1]) && + moveBackward() + ) {} + }; + + function isCharAlphanumeric(charToTest) { + if (typeof charToTest == 'string') { + var code = charToTest.charCodeAt(); + return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) || + (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) || + (code >= '0'.charCodeAt() && code <= '9'.charCodeAt()); + } + return false; + }; - var len = completions.length; - if (len === 1) { + function doComplete() { + if (typeof config.completeHandle == 'function') { + doCompleteDirectly(); + } else { + issueComplete(); + } + }; + + function doCompleteDirectly() { + if (typeof config.completeHandle == 'function') { + var completions = config.completeHandle(promptText); + var len = completions.length; + if (len === 1) { extern.promptText(promptText + completions[0]); - } else if (len > 1 && config.cols) { + } else if (len > 1 && config.cols) { var prompt = promptText; // Compute the number of rows that will fit in the width var max = 0; for (var i = 0; i < len; i++) { - max = Math.max(max, completions[i].length); + max = Math.max(max, completions[i].length); } max += 2; var n = Math.floor(config.cols / max); var buffer = ""; var col = 0; for (i = 0; i < len; i++) { - var completion = completions[i]; - buffer += completions[i]; - for (var j = completion.length; j < max; j++) { - buffer += " "; - } - if (++col >= n) { - buffer += "\n"; - col = 0; - } + var completion = completions[i]; + buffer += completions[i]; + for (var j = completion.length; j < max; j++) { + buffer += " "; + } + if (++col >= n) { + buffer += "\n"; + col = 0; + } } commandResult(buffer, "jquery-console-message-value"); extern.promptText(prompt); + } } - }; + }; - function doNothing() {}; + function issueComplete() { + if (typeof config.completeIssuer == 'function') { + config.completeIssuer(promptText); + } + }; - extern.promptText = function(text){ - if (typeof text === 'string') { - promptText = text; - column = promptText.length; - updatePromptDisplay(); - } - return promptText; - }; + function showCompletion(promptText, completions) { - //////////////////////////////////////////////////////////////////////// - // Update the prompt display - function updatePromptDisplay(){ - var line = promptText; - var html = ''; - if (column > 0 && line == ''){ - // When we have an empty line just display a cursor. - html = cursor; - } else if (column == promptText.length){ - // We're at the end of the line, so we need to display - // the text *and* cursor. - html = htmlEncode(line) + cursor; - } else { - // Grab the current character, if there is one, and - // make it the current cursor. - var before = line.substring(0, column); - var current = line.substring(column,column+1); - if (current){ - current = - '' + - htmlEncode(current) + - ''; - } - var after = line.substring(column+1); - html = htmlEncode(before) + current + htmlEncode(after); - } - prompt.html(html); - scrollToBottom(); - }; + var len = completions.length; + if (len === 1) { + extern.promptText(promptText + completions[0]); + } else if (len > 1 && config.cols) { + var prompt = promptText; + // Compute the number of rows that will fit in the width + var max = 0; + for (var i = 0; i < len; i++) { + max = Math.max(max, completions[i].length); + } + max += 2; + var n = Math.floor(config.cols / max); + var buffer = ""; + var col = 0; + for (i = 0; i < len; i++) { + var completion = completions[i]; + buffer += completions[i]; + for (var j = completion.length; j < max; j++) { + buffer += " "; + } + if (++col >= n) { + buffer += "\n"; + col = 0; + } + } + commandResult(buffer, "jquery-console-message-value"); + extern.promptText(prompt); + } + }; - // Simple HTML encoding - // Simply replace '<', '>' and '&' - // TODO: Use jQuery's .html() trick, or grab a proper, fast - // HTML encoder. - function htmlEncode(text){ - return ( - text.replace(/&/g,'&') - .replace(/') - ); + function doNothing() {}; + + extern.promptText = function(text) { + if (typeof text === 'string') { + promptText = text; + column = promptText.length; + updatePromptDisplay(); + } + return promptText; + }; + + //////////////////////////////////////////////////////////////////////// + // Update the prompt display + function updatePromptDisplay() { + var line = promptText; + var html = ''; + if (column > 0 && line == '') { + // When we have an empty line just display a cursor. + html = cursor; + } else if (column == promptText.length) { + // We're at the end of the line, so we need to display + // the text *and* cursor. + html = htmlEncode(line) + cursor; + } else { + // Grab the current character, if there is one, and + // make it the current cursor. + var before = line.substring(0, column); + var current = line.substring(column, column + 1); + if (current) { + current = + '' + + htmlEncode(current) + + ''; + } + var after = line.substring(column + 1); + html = htmlEncode(before) + current + htmlEncode(after); + } + prompt.html(html); + scrollToBottom(); + }; + + // Simple HTML encoding + // Simply replace '<', '>' and '&' + // TODO: Use jQuery's .html() trick, or grab a proper, fast + // HTML encoder. + function htmlEncode(text) { + return ( + text.replace(/&/g, '&') + .replace(/') + ); + }; + + return extern; + }; + // Simple utility for printing messages + $.fn.filledText = function(txt) { + $(this).text(txt); + $(this).html($(this).html().replace(/\t/g, '  ').replace(/\n/g, '
')); + return this; }; - return extern; - }; - // Simple utility for printing messages - $.fn.filledText = function(txt){ - $(this).text(txt); - $(this).html($(this).html().replace(/\t/g, '  ').replace(/\n/g,'
')); - return this; - }; - - // Alternative method for focus without scrolling - $.fn.focusWithoutScrolling = function(){ - var x = window.scrollX, y = window.scrollY; - $(this).focus(); - window.scrollTo(x, y); - }; -})(jQuery); + // Alternative method for focus without scrolling + $.fn.focusWithoutScrolling = function() { + var x = window.scrollX, + y = window.scrollY; + $(this).focus(); + window.scrollTo(x, y); + }; +})(jQuery); \ No newline at end of file