');\n if (html.match(/<\\/div>/)) {\n // match multiple echo output\n stdout = $html.find('div[data-index]').map(function() {\n return process_div(this);\n }).get().join('\\n');\n // match inside single echo output\n if (!stdout && html.match(/style=\"width: 100%;?\"/)) {\n stdout = process_div($html);\n }\n text = stdout;\n }\n var $prompt = $html.find('.cmd-prompt');\n if ($prompt.length) {\n if (text.length) {\n text += '\\n';\n }\n text += $prompt.text();\n }\n var $cmd_lines = $html.find('[role=\"presentation\"]');\n if ($cmd_lines.length) {\n text += $cmd_lines.map(process_selected_line).get().join('');\n }\n if (!text.length && html) {\n text = $html.text();\n }\n return text.replace(/\\xA0/g, ' '); // fix space\n }\n // ---------------------------------------------------------------------\n // :: copy given DOM element text to clipboard\n // ---------------------------------------------------------------------\n var support_copy = (function() {\n if (typeof document === 'undefined') {\n return false;\n }\n if (!is_function(document.queryCommandSupported)) {\n return false;\n }\n return document.queryCommandSupported('copy');\n })();\n // ---------------------------------------------------------------------\n var text_to_clipboard;\n if (support_copy) {\n text_to_clipboard = function text_to_clipboard($textarea, text) {\n var val = $textarea.val();\n var had_focus = $textarea.is(':focus');\n var pos = $textarea.caret();\n if (window.navigator && window.navigator.clipboard) {\n navigator.clipboard.writeText(text);\n } else if (had_focus) {\n $textarea.val(text).focus();\n $textarea[0].select();\n document.execCommand('copy');\n $textarea.val(val);\n $textarea.caret(pos);\n } else {\n var $text = $('
').css({\n position: 'fixed',\n top: 0,\n left: 0\n }).appendTo('body');\n $text.val(text).focus();\n $text[0].select();\n document.execCommand('copy');\n $text.blur();\n $text.remove();\n }\n return true;\n };\n } else {\n text_to_clipboard = $.noop;\n }\n // ---------------------------------------------------------------------\n var get_textarea_selection = (function() {\n function noop() {\n return '';\n }\n if (typeof document === 'undefined') {\n return noop;\n }\n var textarea = document.createElement('textarea');\n var selectionStart = 'selectionStart' in textarea;\n textarea = null;\n if (selectionStart) {\n return function(textarea) {\n var length = textarea.selectionEnd - textarea.selectionStart;\n return textarea.value.substr(textarea.selectionStart, length);\n };\n } else if (document.selection) {\n return function() {\n var range = document.selection.createRange();\n return range.text();\n };\n } else {\n return noop;\n }\n })();\n // ---------------------------------------------------------------------\n function clear_textarea_selection(textarea) {\n textarea.selectionStart = textarea.selectionEnd = 0;\n }\n // ---------------------------------------------------------------------\n // :: return string that are common in all elements of the array\n // ---------------------------------------------------------------------\n function common_string(string, array, matchCase) {\n if (!array.length) {\n return '';\n }\n var type = string_case(string);\n var result = [];\n for (var j = string.length; j < array[0].length; ++j) {\n var push = false;\n var candidate = array[0].charAt(j),\n candidateLower = candidate.toLowerCase();\n for (var i = 1; i < array.length; ++i) {\n push = true;\n var current = array[i].charAt(j),\n currentLower = current.toLowerCase();\n if (candidate !== current) {\n if (matchCase || type === 'mixed') {\n push = false;\n break;\n } else if (candidateLower === currentLower) {\n if (type === 'lower') {\n candidate = candidate.toLowerCase();\n } else if (type === 'upper') {\n candidate = candidate.toUpperCase();\n } else {\n push = false;\n break;\n }\n } else {\n push = false;\n break;\n }\n }\n }\n if (push) {\n result.push(candidate);\n } else {\n break;\n }\n }\n return string + result.join('');\n }\n // ---------------------------------------------------------------------\n function trigger_terminal_change(next) {\n terminals.forEach(function(term) {\n term.settings().onTerminalChange.call(term, next);\n });\n }\n // ---------------------------------------------------------------------\n var select = (function() {\n if (root.getSelection) {\n var selection = root.getSelection();\n if (selection.setBaseAndExtent) {\n return function(start, end) {\n var selection = root.getSelection();\n selection.setBaseAndExtent(start, 0, end, 1);\n };\n } else {\n return function(start, end) {\n var selection = root.getSelection();\n var range = document.createRange();\n range.setStart(start, 0);\n range.setEnd(end, end.childNodes.length);\n selection.removeAllRanges();\n selection.addRange(range);\n };\n }\n } else {\n return $.noop;\n }\n })();\n // -------------------------------------------------------------------------\n function process_command(original, fn) {\n var string = original.trim();\n var array = string.match(command_re) || [];\n if (array.length) {\n var name = array.shift();\n var args = $.map(array, function(arg) {\n if (arg.match(/^[\"']/)) {\n arg = arg.replace(/\\n/g, '\\\\u0000\\\\u0000\\\\u0000\\\\u0000');\n arg = fn(arg);\n return arg.replace(/\\x00\\x00\\x00\\x00/g, '\\n');\n }\n return fn(arg);\n });\n var quotes = $.map(array, function(arg) {\n var m = arg.match(/^(['\"`]).*\\1$/);\n return m && m[1] || '';\n });\n var rest = string.slice(name.length).trim();\n return {\n command: original,\n name: name,\n args: args,\n args_quotes: quotes,\n rest: rest\n };\n } else {\n return {\n command: original,\n name: '',\n args: [],\n args_quotes: [],\n rest: ''\n };\n }\n }\n // -------------------------------------------------------------------------\n $.terminal = {\n version: '2.31.0',\n date: 'Mon, 27 Dec 2021 10:26:13 +0000',\n // colors from https://www.w3.org/wiki/CSS/Properties/color/keywords\n color_names: [\n 'transparent', 'currentcolor', 'black', 'silver', 'gray', 'white',\n 'maroon', 'red', 'purple', 'fuchsia', 'green', 'lime', 'olive',\n 'yellow', 'navy', 'blue', 'teal', 'aqua', 'aliceblue',\n 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque',\n 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown',\n 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral',\n 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue',\n 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey',\n 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange',\n 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen',\n 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise',\n 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',\n 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',\n 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green',\n 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo',\n 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',\n 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',\n 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey',\n 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',\n 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',\n 'lime', 'limegreen', 'linen', 'magenta', 'maroon',\n 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',\n 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',\n 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',\n 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',\n 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',\n 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',\n 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',\n 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',\n 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',\n 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',\n 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat',\n 'white', 'whitesmoke', 'yellow', 'yellowgreen', 'rebeccapurple'],\n // for unit tests\n Cycle: Cycle,\n History: History,\n Stack: Stack,\n // ---------------------------------------------------------------------\n // :: Validate html color (it can be name or hex)\n // ---------------------------------------------------------------------\n valid_color: function valid_color(color) {\n if (color.match(color_re)) {\n return true;\n } else {\n return $.inArray(color.toLowerCase(), $.terminal.color_names) !== -1;\n }\n },\n // ---------------------------------------------------------------------\n // :: function check if given string contain invalid strings\n // ---------------------------------------------------------------------\n unclosed_strings: function unclosed_strings(string) {\n return !!string.match(unclosed_strings_re);\n },\n // ---------------------------------------------------------------------\n // :: Escape all special regex characters, so it can be use as regex to\n // :: match exact string that contain those characters\n // ---------------------------------------------------------------------\n escape_regex: function escape_regex(str) {\n if (typeof str === 'string') {\n var special = /([-\\\\^$[\\]()+{}?*.|])/g;\n return str.replace(special, '\\\\$1');\n }\n },\n // ---------------------------------------------------------------------\n // :: test if string contain formatting\n // ---------------------------------------------------------------------\n have_formatting: function have_formatting(str) {\n return typeof str === 'string' && !!str.match(format_exist_re);\n },\n is_formatting: function is_formatting(str) {\n return typeof str === 'string' && !!str.match(format_full_re);\n },\n // ---------------------------------------------------------------------\n is_extended_command: function is_extended_command(str) {\n return typeof str === 'string' &&\n str.match(format_exec_re) &&\n !$.terminal.is_formatting(str);\n },\n // ---------------------------------------------------------------------\n each_extended_command: function(string, fn) {\n var parts = string.split(format_exec_split_re);\n return $.map(parts, function(string) {\n if ($.terminal.is_extended_command(string)) {\n var command = string.replace(/^\\[\\[|\\]\\]$/g, '');\n return fn(command) || '';\n }\n return string;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: return array of formatting and text between them\n // ---------------------------------------------------------------------\n format_split: function format_split(str) {\n return str.split(format_split_re).filter(Boolean);\n },\n // ---------------------------------------------------------------------\n // :: replace that return position after replace for working with\n // :: replacement that change length of the string\n // :: source https://stackoverflow.com/a/46756077/387194\n // ---------------------------------------------------------------------\n tracking_replace: function tracking_replace(string, rex, replacement, position) {\n if (!(rex instanceof RegExp)) {\n throw new Error('tracking_replace: Second argument need to be RegExp');\n }\n function substring(string, start, end) {\n return string.slice(start, end);\n }\n function length(string) {\n return $.terminal.strip(string).length;\n }\n var new_string = \"\";\n var match;\n var index = 0;\n var rep_string;\n var new_position = position;\n var start;\n rex.lastIndex = 0; // Just to be sure\n while ((match = rex.exec(string))) {\n // if regex don't have g flag lastIndex will not work\n if (rex.global) {\n // Add any of the original string we just skipped\n var last_index = length(substring(string, 0, rex.lastIndex));\n start = last_index - length(match[0]);\n } else {\n start = match.index;\n last_index = start + length(match[0]);\n }\n if (index < start) {\n new_string += substring(string, index, start);\n }\n index = last_index;\n // Build the replacement string. This just handles $$ and $n,\n // you may want to add handling for $`, $', and $&.\n if (typeof replacement === 'function') {\n rep_string = replacement.apply(null, match);\n } else {\n rep_string = replacement.replace(/\\$(\\$|\\d)/g, function(m, c0) {\n if (c0 === \"$\") {\n return \"$\";\n }\n return match[c0];\n });\n }\n // Add on the replacement\n new_string += rep_string;\n // If the position is affected...\n if (start < position) {\n // ... update it:\n var rep_len = length(rep_string);\n rep_len += count_selfclosing_formatting(rep_string);\n if (last_index < position) {\n // It's after the replacement, move it\n new_position = Math.max(\n 0,\n new_position +\n rep_len -\n length(match[0])\n );\n } else {\n // It's *in* the replacement, put it just after\n new_position += rep_len - (position - start);\n }\n }\n // If the regular expression doesn't have the g flag, break here so\n // we do just one replacement (and so we don't have an endless loop!)\n if (!rex.global) {\n break;\n }\n }\n // Add on any trailing text in the string\n if (index < length(string)) {\n new_string += substring(string, index);\n }\n // Return the string and the updated position\n if (string === new_string) {\n return [string, position];\n }\n return [new_string, new_position];\n },\n // ---------------------------------------------------------------------\n // :: helper function used by substring and split_equal it loop over\n // :: string and execute callback with text count and other data\n // ---------------------------------------------------------------------\n iterate_formatting: function iterate_formatting(string, callback) {\n function is_space(i) {\n return string.slice(i - 6, i) === ' ' ||\n string.slice(i - 1, i).match(/\\s/);\n }\n // ----------------------------------------------------------------\n function match_entity(index) {\n return string.slice(index).match(entity_re);\n }\n // ----------------------------------------------------------------\n function is_open_formatting(i) {\n return string[i] === '[' && string[i + 1] === '[';\n }\n // ----------------------------------------------------------------\n function is_escape_bracket(i) {\n return string[i - 1] !== '\\\\' && string[i] === '\\\\' &&\n string[i + 1] === ']';\n }\n // ----------------------------------------------------------------\n function is_text(i) {\n return not_formatting && (string[i] !== ']' || !have_formatting)\n && !opening;\n }\n // ----------------------------------------------------------------\n // :: function will skip to next character in main loop\n // :: TODO: improve performance of emoji regex and check whole\n // :: string it's complex string if not use simple function\n // ----------------------------------------------------------------\n var get_next_character = make_next_char_fun(string);\n function next_iteration() {\n var char = get_next_character(substring);\n if (char.length > 1 && $.terminal.length(substring) > 1) {\n return char.length - 1;\n }\n return 0;\n }\n // ----------------------------------------------------------------\n function is_next_space() {\n return (is_space(i) && (not_formatting || opening)) &&\n (space === -1 && prev_space !== i || space !== -1);\n }\n // ----------------------------------------------------------------\n // :: last iteration or one before closing formatting\n // ----------------------------------------------------------------\n var last = false;\n function is_last() {\n if (i === string.length - 1 && !last) {\n last = true;\n } else {\n last = formatting && !!substring.match(/^.]$/);\n }\n return last;\n }\n // ----------------------------------------------------------------\n var have_formatting = $.terminal.have_formatting(string);\n var formatting = '';\n var in_text = false;\n var count = 0;\n var match;\n var space = -1;\n var space_count = -1;\n var prev_space;\n var length = 0;\n var offset = 0;\n var re_ent = /(&[^;]+);$/;\n for (var i = 0; i < string.length; i++) {\n var substring = string.slice(i);\n match = substring.match(format_start_re);\n if (match) {\n formatting = match[1];\n in_text = false;\n } else if (formatting) {\n if (string[i] === ']') {\n if (in_text) {\n formatting = '';\n in_text = false;\n } else {\n in_text = true;\n }\n }\n } else {\n in_text = true;\n }\n var not_formatting = (formatting && in_text) || !formatting;\n var opening = is_open_formatting(i);\n if (is_next_space()) {\n space = i;\n space_count = count;\n }\n var braket = string[i].match(/[[\\]]/);\n offset = 0;\n if (not_formatting) {\n // treat entity as one character\n if (string[i] === '&') {\n match = match_entity(i);\n if (match) {\n i += match[1].length - 2; // 2 because continue adds 1 to i\n continue;\n }\n ++count;\n ++length;\n } else if (is_escape_bracket(i)) {\n // escape \\] and \\\\ counts as one character\n ++count;\n ++length;\n offset = 1;\n i += 1;\n } else if (!braket || !have_formatting) {\n ++count;\n ++length;\n }\n }\n if (is_text(i)) {\n if (strlen(string[i]) === 2) {\n length++;\n }\n var char = get_next_character(substring);\n var size = char.length;\n // begining of enity that we've skipped, we are at the end\n if (char === ';') {\n match = string.slice(0, i + 1).match(re_ent);\n if (match) {\n offset = match[1].length;\n size = offset + 1;\n }\n }\n var data = {\n last: is_last(),\n count: count,\n index: i - offset,\n formatting: formatting,\n length: length,\n text: in_text,\n size: size,\n space: space,\n space_count: space_count\n };\n var ret = callback(data);\n if (ret === false) {\n break;\n } else if (ret) {\n if (ret.count !== undefined) {\n count = ret.count;\n }\n if (ret.length !== undefined) {\n length = ret.length;\n }\n if (ret.space !== undefined) {\n prev_space = space;\n space = ret.space;\n }\n if (ret.index !== undefined) {\n i = ret.index;\n continue;\n }\n }\n } else if (i === string.length - 1 && !last) {\n // last iteration, if formatting have last bracket,\n // from formatting, then last iteration\n // was already called (in if) #550\n callback({\n last: true,\n count: count + 1,\n index: i,\n formatting: formatting,\n length: 0,\n text: in_text,\n space: space\n });\n }\n // handle emoji, suroggate pairs and combine characters\n if (in_text) {\n i += next_iteration();\n }\n }\n },\n // ---------------------------------------------------------------------\n // :: function return string splitted into single characters\n // :: each character is wrapped into formatting from input string\n // :: or empty formatting so it will create span when using with ::format\n // ---------------------------------------------------------------------\n partition: function partition(string) {\n if (!$.terminal.have_formatting(string)) {\n var chars = $.terminal.split_characters(string);\n return chars.map(wrap);\n }\n var result = [];\n function wrap(string) {\n if (string.match(/\\\\$/)) {\n string += '\\\\';\n }\n return '[[;;]' + string + ']';\n }\n function formatting(string) {\n if ($.terminal.is_formatting(string)) {\n if (string.match(/\\\\]$/)) {\n string = string.replace(/\\\\]/g, '\\\\\\\\]');\n }\n } else {\n string = wrap(string);\n }\n return string;\n }\n $.terminal.iterate_formatting(string, function(data) {\n if (data.text) {\n var text = [];\n if (data.formatting) {\n text.push(data.formatting);\n }\n text.push(string.substring(data.index, data.index + data.size));\n if (data.formatting) {\n text.push(']');\n }\n result.push(formatting(text.join('')));\n }\n });\n return result;\n },\n // ---------------------------------------------------------------------\n // :: formatting aware substring function\n // ---------------------------------------------------------------------\n substring: function substring(string, start_index, end_index) {\n var chars = $.terminal.split_characters(string);\n if (!chars.slice(start_index, end_index).length) {\n return '';\n }\n if (!$.terminal.have_formatting(string)) {\n return chars.slice(start_index, end_index).join('');\n }\n var start = 0;\n var end;\n var start_formatting = '';\n var end_formatting = '';\n var prev_index;\n var offset = 1;\n $.terminal.iterate_formatting(string, function(data) {\n if (start_index && data.count === start_index + 1) {\n start = data.index;\n if (data.formatting) {\n start_formatting = data.formatting;\n }\n }\n if (end_index && data.count === end_index) {\n end_formatting = data.formatting;\n prev_index = data.index;\n offset = data.size;\n }\n if (data.count === end_index + 1) {\n end = data.index;\n if (data.formatting) {\n end = prev_index + offset;\n }\n }\n });\n if (start_index && !start) {\n return '';\n }\n if (end === undefined) {\n end = string.length;\n }\n string = start_formatting + string.slice(start, end);\n if (end_formatting) {\n string = string.replace(/(\\[\\[^\\]]+)?\\]$/, '');\n string += ']';\n }\n return string;\n },\n // ---------------------------------------------------------------------\n // :: add format text as 5th paramter to formatting it's used for\n // :: data attribute in format function - and fix unclosed &\n // ---------------------------------------------------------------------\n normalize: function normalize(string) {\n string = string.replace(format_re, function(_, format, text) {\n if (format.match(self_closing_re) && text === '') {\n return '[[' + format + '] ]';\n }\n if (text === '') {\n return '';\n }\n function safe(string) {\n return string.replace(/\\\\\\]/g, ']').replace(/\\n/g, '\\\\n')\n .replace(/ /g, ' ');\n }\n format = safe(format);\n var semicolons = format.match(/;/g).length;\n // missing semicolons\n if (semicolons >= 4) {\n var args = format.split(/;/);\n var start = args.slice(0, 4).join(';');\n var arg = args.slice(4).join(';');\n return '[[' + start + ';' + (arg || text) + ']' + text + ']';\n } else if (semicolons === 2) {\n semicolons = ';;';\n } else if (semicolons === 3) {\n semicolons = ';';\n }\n // return '[[' + format + ']' + text + ']';\n // closing braket will break formatting so we need to escape\n // those using html entity equvalent\n // space is hack for images that break iterate_formatting\n format += semicolons + safe(text);\n return '[[' + format + ']' + text + ']';\n });\n return $.terminal.amp(string);\n },\n // ---------------------------------------------------------------------\n // :: split text into lines with equal length so each line can be\n // :: rendered separately (text formatting can be longer then a line).\n // ---------------------------------------------------------------------\n split_equal: function split_equal(str, length, keep_words) {\n var prev_format = '';\n var result = [];\n var array = $.terminal.normalize(str).split(/\\n/g);\n for (var i = 0, len = array.length; i < len; ++i) {\n if (array[i] === '') {\n result.push('');\n continue;\n }\n var line = array[i];\n var get_next_character = make_next_char_fun(line);\n var first_index = 0;\n var output;\n var line_length = line.length;\n var last_bracket = !!line.match(/\\[\\[[^\\]]+\\](?:[^\\][]|\\\\\\])+\\]$/);\n $.terminal.iterate_formatting(line, function(data) {\n var chr, substring;\n if (data.length >= length || data.last ||\n (data.length === length - 1 &&\n strlen(line[data.index + 1]) === 2)) {\n var can_break = false;\n // TODO: this need work\n if (keep_words && data.space !== -1) {\n // replace html entities with characters\n var stripped = text(line).substring(data.space_count);\n // real length, not counting formatting\n stripped = stripped.slice(0, length).trim();\n var text_len = strlen(stripped);\n if (stripped.match(/\\s/) || text_len < length) {\n can_break = true;\n }\n }\n // if words is true we split at last space and make next loop\n // continue where the space where located\n var after_index = data.index + data.size;\n if (last_bracket) {\n after_index += 1;\n }\n var new_index;\n if (keep_words && data.space !== -1 &&\n after_index !== line_length && can_break) {\n output = line.slice(first_index, data.space);\n new_index = data.space - 1;\n } else {\n substring = line.slice(data.index);\n chr = get_next_character(substring);\n output = line.slice(first_index, data.index) + chr;\n if (data.last && last_bracket && chr !== ']') {\n output += ']';\n }\n new_index = data.index + chr.length - 1;\n }\n if (keep_words) {\n output = output.replace(/^( |\\s)+|( |\\s)+$/g, '');\n }\n first_index = (new_index || data.index) + 1;\n if (prev_format) {\n var closed_formatting = output.match(/^[^\\]]*\\]/);\n output = prev_format + output;\n if (closed_formatting) {\n prev_format = '';\n }\n }\n var matched = output.match(format_re);\n if (matched) {\n var last = matched[matched.length - 1];\n if (last[last.length - 1] !== ']') {\n prev_format = last.match(format_begin_re)[1];\n output += ']';\n } else if (output.match(format_end_re)) {\n output = output.replace(format_end_re, '');\n prev_format = last.match(format_begin_re)[1];\n }\n }\n result.push(output);\n // modify loop by returing new data\n return {index: new_index, length: 0, space: -1};\n }\n });\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: Escape & that's not part of entity\n // ---------------------------------------------------------------------\n amp: function amp(str) {\n return str.replace(/&(?!#[0-9]+;|#x[0-9a-f]+;|[a-z]+;)/gi, '&');\n },\n // ---------------------------------------------------------------------\n // :: Encode formating as html for insertion into DOM\n // ---------------------------------------------------------------------\n encode: function encode(str, options) {\n var settings = $.extend({\n tabs: 4,\n before: ''\n }, options);\n return $.terminal.amp(str).replace(//g, '>')\n .replace(/ /g, ' ').split('\\n').map(function(line) {\n var splitted = line.split(/((?:\\[\\[[^\\]]+\\])?\\t(?:\\])?)/);\n splitted = splitted.filter(Boolean);\n return splitted.map(function(str, i) {\n if (str.match(/\\t/)) {\n return str.replace(/\\t([^\\t]*)$/, function(_, end) {\n if (i !== 0 && splitted[i - 1].match(/\\t\\]?$/)) {\n var sp = new Array(settings.tabs + 1).join(' ');\n return sp + end;\n } else {\n var before = splitted.slice(i - 1, i).join('');\n if (settings.before && i <= 1) {\n before = settings.before + before;\n }\n var len = $.terminal.length(before);\n var chars = settings.tabs - (len % settings.tabs);\n if (chars === 0) {\n chars = 4;\n }\n return new Array(chars + 1).join(' ') + end;\n }\n });\n }\n return str;\n }).join('');\n }).join('\\n');\n },\n // -----------------------------------------------------------------------\n // :: Default formatter that allow for nested formatting, example:\n // :: [[;;#000]hello [[;#f00;]red] world]\n // -----------------------------------------------------------------------\n nested_formatting: function nested_formatting(string) {\n if (!$.terminal.have_formatting(string)) {\n return string;\n }\n var stack = [];\n var re = /((?:\\[\\[(?:[^\\][]|\\\\\\])+\\])?(?:[^\\][]|\\\\\\])*\\]?)/;\n var format_re = /\\[\\[([^\\][]+)\\][\\s\\S]*/;\n var format_split_re = /^\\[\\[([^;]*);([^;]*);([^\\]]*)\\]/;\n var class_i = 3; // index of the class in formatting\n var attrs_i = 5; // index of attributes in formatting\n // ---------------------------------------------------------------------------\n function unique(value, index, self) {\n return self.indexOf(value) === index;\n }\n // ---------------------------------------------------------------------------\n function update_style(new_style, old_style) {\n new_style = parse_style(new_style);\n if (!old_style) {\n return new_style;\n }\n return $.extend(old_style, new_style);\n }\n // ---------------------------------------------------------------------------\n function parse_style(string) {\n var style = {};\n string.split(/\\s*;\\s*/).forEach(function(string) {\n var parts = string.split(':').map(function(string) {\n return string.trim();\n });\n var prop = parts[0];\n var value = parts[1];\n style[prop] = value;\n });\n return style;\n }\n // ---------------------------------------------------------------------------\n function stringify_formatting(input) {\n var result = input.slice();\n if (input[attrs_i]) {\n result[attrs_i] = stringify_attrs(input[attrs_i]);\n }\n if (input[class_i]) {\n result[class_i] = stringify_class(input[class_i]);\n }\n result[0] = stringify_styles(input[0]);\n return result.join(';');\n }\n // ---------------------------------------------------------------------------\n function stringify_styles(input) {\n var ignore = input.filter(function(s) {\n return s[0] === '-';\n }).map(function(s) {\n return s[1];\n });\n return input.filter(function(s) {\n return ignore.indexOf(s) === -1 && ignore.indexOf(s[1]) === -1;\n }).join('');\n }\n // ---------------------------------------------------------------------------\n function stringify_attrs(attrs) {\n return JSON.stringify(attrs, function(key, value) {\n if (key === 'style') {\n return stringify_style(value);\n }\n return value;\n });\n }\n // ---------------------------------------------------------------------------\n function stringify_class(klass) {\n return klass.filter(unique).join(' ');\n }\n // ---------------------------------------------------------------------------\n function stringify_style(style) {\n return Object.keys(style).map(function(prop) {\n return prop + ':' + style[prop];\n }).join(';');\n }\n // ---------------------------------------------------------------------------\n function get_inherit_style(stack) {\n function update_attrs(value) {\n if (!output[attrs_i]) {\n output[attrs_i] = {};\n }\n try {\n var new_attrs = JSON.parse(value);\n if (new_attrs.style) {\n var new_style = new_attrs.style;\n var old_style = output[attrs_i].style;\n new_attrs.style = update_style(new_style, old_style);\n output[attrs_i] = $.extend(\n new_attrs,\n output[attrs_i],\n {\n style: update_style(new_style, old_style)\n }\n );\n } else {\n output[attrs_i] = $.extend(\n new_attrs,\n output[attrs_i]\n );\n }\n } catch (e) {\n warn('Invalid JSON ' + value);\n }\n }\n var output = [[], '', ''];\n if (!stack.length) {\n return output;\n }\n for (var i = stack.length; i--;) {\n var formatting = stack[i].split(';');\n if (formatting.length > 5) {\n var last = formatting.slice(5).join(';');\n formatting = formatting.slice(0, 5).concat(last);\n }\n var style = formatting[0].split(/(-?[@!gbiuso])/g).filter(Boolean);\n style.forEach(function(s) {\n if (output[0].indexOf(s) === -1) {\n output[0].push(s);\n }\n });\n for (var j = 1; j < formatting.length; ++j) {\n var value = formatting[j].trim();\n if (value) {\n if (j === class_i) {\n if (!output[class_i]) {\n output[class_i] = [];\n }\n var classes = value.split(/\\s+/);\n output[class_i] = output[class_i].concat(classes);\n } else if (j === attrs_i) {\n update_attrs(value);\n } else if (!output[j]) {\n output[j] = value;\n }\n }\n }\n }\n return stringify_formatting(output);\n }\n return string.split(re).filter(Boolean).map(function(string) {\n var style;\n if (string.match(/^\\[\\[/) && !$.terminal.is_extended_command(string)) {\n var formatting = string.replace(format_re, '$1');\n var is_formatting = $.terminal.is_formatting(string);\n string = string.replace(format_split_re, '');\n stack.push(formatting);\n if ($.terminal.nested_formatting.__inherit__) {\n style = get_inherit_style(stack);\n } else {\n style = formatting;\n }\n if (!is_formatting) {\n string += ']';\n } else {\n stack.pop();\n }\n string = '[[' + style + ']' + string;\n } else {\n var pop = false;\n if (string.match(/\\]/)) {\n pop = true;\n }\n if (stack.length) {\n if ($.terminal.nested_formatting.__inherit__) {\n style = get_inherit_style(stack);\n } else {\n style = stack[stack.length - 1];\n }\n string = '[[' + style + ']' + string;\n }\n if (pop) {\n stack.pop();\n } else if (stack.length) {\n string += ']';\n }\n }\n return string;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: safe function that will render text as it is\n // ---------------------------------------------------------------------\n escape_formatting: function escape_formatting(string) {\n return $.terminal.escape_brackets(string);\n },\n // ---------------------------------------------------------------------\n // :: apply custom formatters only to text\n // ---------------------------------------------------------------------\n apply_formatters: function apply_formatters(string, settings) {\n if (string === \"\") {\n if (settings && typeof settings.position === 'number') {\n return [\"\", settings.position];\n } else {\n return \"\";\n }\n }\n function test_lengths(formatter, index, ret, string) {\n if (!formatter.__no_warn__ &&\n $.terminal.length(ret) !== $.terminal.length(string)) {\n warn('Your formatter[' + index + '] change length of the string, ' +\n 'you should use [regex, replacement] formatter or function ' +\n ' that return [replacement, position] instead');\n }\n }\n function should_format(options) {\n if (!settings || !options) {\n return true;\n }\n var props = ['echo', 'command', 'prompt'];\n var have_any = props.some(function(name) {\n return options[name] === true;\n });\n if (!have_any) {\n return true;\n }\n for (var i = props.length; i--;) {\n var prop = props[i];\n if (options[prop] === true && settings[prop] === true) {\n return true;\n }\n }\n return false;\n }\n settings = settings || {};\n var formatters = settings.formatters || $.terminal.defaults.formatters;\n var i = 0;\n function apply_function_formatter(formatter, input) {\n var options = $.extend({}, settings, {\n position: input[1]\n });\n var ret = formatter(input[0], options);\n if (typeof ret === 'string') {\n test_lengths(formatter, i - 1, ret, input[0]);\n if (typeof ret === 'string') {\n return [ret, options.position];\n }\n return input;\n } else if (is_array(ret) && ret.length === 2) {\n return ret;\n } else {\n return input;\n }\n }\n var input;\n if (typeof settings.position === 'number') {\n input = [string, settings.position];\n } else {\n input = [string, 0];\n }\n try {\n var result = formatters.reduce(function(input, formatter) {\n i++;\n // __meta__ is for safe formatter that can handle formatters\n // inside formatters. for other usage we use format_split so one\n // formatter don't mess with formatter that was previous\n // on the list\n if (typeof formatter === 'function' && formatter.__meta__) {\n return apply_function_formatter(formatter, input);\n } else {\n var length = 0;\n var found_position = false;\n var splitted = $.terminal.format_split(input[0]);\n var partials = splitted.map(function(string) {\n var position;\n var this_len = text(string).length;\n // first position that match is used for this partial\n if (input[1] < length + this_len && !found_position) {\n position = input[1] - length;\n found_position = true;\n } else if (found_position) {\n // -1 indicate that we will not track position because it\n // was in one of the previous parial strings\n position = -1;\n } else {\n // initial position for replacers\n position = input[1];\n }\n // length is used to correct position after replace\n var length_before = length;\n var result;\n length += this_len;\n if ($.terminal.is_formatting(string)) {\n if (found_position) {\n return [string, position];\n }\n return [string, -1];\n } else {\n if (is_array(formatter)) {\n var options = formatter[2] || {};\n result = [string, position < 0 ? 0 : position];\n if (result[0].match(formatter[0]) &&\n should_format(formatter[2])) {\n if (options.loop) {\n while (result[0].match(formatter[0])) {\n result = $.terminal.tracking_replace(\n result[0],\n formatter[0],\n formatter[1],\n result[1]\n );\n }\n } else {\n result = $.terminal.tracking_replace(\n result[0],\n formatter[0],\n formatter[1],\n result[1]\n );\n }\n }\n if (position < 0) {\n return [result[0], -1];\n }\n } else if (typeof formatter === 'function') {\n result = apply_function_formatter(formatter, [\n string, position\n ]);\n }\n if (typeof result !== 'undefined') {\n // correct position becuase it's relative\n // to partial and we need global for whole string\n if (result[1] !== -1) {\n result[1] += length_before;\n }\n var after_len = text(result[0]).length;\n if (after_len !== this_len) {\n }\n return result;\n }\n return [string, -1];\n }\n });\n var position_partial = partials.filter(function(partial) {\n return partial[1] !== -1;\n })[0];\n var string = partials.map(function(partial) {\n return partial[0];\n }).join('');\n var position;\n if (typeof position_partial === 'undefined') {\n position = input[1];\n } else {\n position = position_partial[1];\n }\n // to make sure that output position is not outside the string\n var max = text(string).length;\n max += count_selfclosing_formatting(string);\n if (position > max) {\n position = max;\n }\n if (string === input[0]) {\n return input;\n }\n var before = $.terminal.strip(input[0]);\n var after = $.terminal.strip(string);\n if (before === after) {\n return [string, input[1]];\n }\n return [string, position];\n }\n }, input);\n if (typeof settings.position === 'number') {\n var codepoint_len = $.terminal.strip(result[0]).length;\n if ($.terminal.length(result[0]) < codepoint_len) {\n var position = result[1];\n position = normalize_position(result[0], position);\n var max = $.terminal.length(result[0]);\n if (position > max) {\n position = max;\n }\n result[1] = position;\n }\n return result;\n } else {\n return result[0];\n }\n } catch (e) {\n var msg = 'Error in formatter [' + (i - 1) + ']';\n formatters.splice(i - 1);\n throw new $.terminal.Exception('formatting', msg, e.stack);\n }\n },\n // ---------------------------------------------------------------------\n // :: Replace terminal formatting with html\n // ---------------------------------------------------------------------\n format: function format(str, options) {\n var settings = $.extend({}, {\n linksNoReferrer: false,\n linksNoFollow: false,\n allowedAttributes: [],\n charWidth: undefined,\n escape: true,\n anyLinks: false\n }, options || {});\n // -----------------------------------------------------------------\n function filter_attr_names(names) {\n if (names.length && settings.allowedAttributes.length) {\n return names.filter(function(name) {\n if (name === 'data-text') {\n return false;\n }\n var allowed = false;\n var filters = settings.allowedAttributes;\n for (var i = 0; i < filters.length; ++i) {\n if (filters[i] instanceof RegExp) {\n if (filters[i].test(name)) {\n allowed = true;\n break;\n }\n } else if (filters[i] === name) {\n allowed = true;\n break;\n }\n }\n return allowed;\n });\n }\n return [];\n }\n // -----------------------------------------------------------------\n function clean_data(data, text) {\n if (data === '') {\n return text;\n } else {\n return data.replace(/]/g, ']')\n .replace(/>/g, '>').replace(/');\n if (str.match(/^\\s*\\{[^}]*\\}\\s*$/)) {\n attrs = JSON.parse(str);\n data_text = splitted[0];\n }\n } catch (e) {\n }\n }\n if (text === '' && !style.match(/@/)) {\n return ''; //'';\n }\n text = safe(text);\n text = text.replace(/\\\\\\]/g, ']');\n if (settings.escape) {\n // inside formatting we need to unescape escaped slashes\n // but this escape is not needed when echo - don't know why\n text = text.replace(/\\\\\\\\/g, '\\\\');\n }\n var style_str = '';\n if (style.indexOf('b') !== -1) {\n style_str += 'font-weight:bold;';\n }\n var text_decoration = [];\n if (style.indexOf('u') !== -1) {\n text_decoration.push('underline');\n }\n if (style.indexOf('s') !== -1) {\n text_decoration.push('line-through');\n }\n if (style.indexOf('o') !== -1) {\n text_decoration.push('overline');\n }\n if (text_decoration.length) {\n style_str += 'text-decoration:' +\n text_decoration.join(' ') + ';';\n }\n if (style.indexOf('i') !== -1) {\n style_str += 'font-style:italic;';\n }\n if ($.terminal.valid_color(color)) {\n style_str += [\n 'color:' + color,\n '--color:' + color,\n '--original-color:' + color\n ].join(';') + ';';\n if (style.indexOf('!') !== -1) {\n style_str += '--link-color:' + color + ';';\n }\n if (style.indexOf('g') !== -1) {\n style_str += 'text-shadow:0 0 5px ' + color + ';';\n }\n }\n if ($.terminal.valid_color(background)) {\n style_str += [\n 'background-color:' + background,\n '--background:' + background\n ].join(';') + ';';\n }\n var data = clean_data(data_text, text);\n var extra = extra_css(text, settings);\n if (extra) {\n text = wide_characters(text, settings);\n style_str += extra;\n }\n var result;\n if (style.indexOf('!') !== -1) {\n result = pre_process_link(data);\n } else if (style.indexOf('@') !== -1) {\n result = pre_process_image(data);\n } else {\n result = '' + text + '';\n } else if (style.indexOf('@') !== -1) {\n result += ' data-text/>';\n } else {\n result += ' data-text=\"' + data.replace(/\"/g, '"') + '\">' +\n '' + text + '';\n }\n return result;\n }\n if (typeof str === 'string') {\n // support for formating foo[[u;;]bar]baz[[b;#fff;]quux]zzz\n var splitted = $.terminal.format_split(str);\n str = $.map(splitted, function(text) {\n if (text === '') {\n return text;\n } else if ($.terminal.is_formatting(text)) {\n // fix inside formatting because encode is called\n // before format\n text = text.replace(/\\[\\[[^\\]]+\\]/, function(text) {\n return text.replace(/ /g, ' ');\n });\n return text.replace(format_parts_re, format);\n } else {\n text = safe(text);\n text = text.replace(/\\\\\\]/, ']');\n var data = text;\n var extra = extra_css(text, settings);\n var prefix;\n if (extra.length) {\n text = wide_characters(text, settings);\n prefix = '' + text + '';\n }\n }).join('');\n return str.replace(/ <\\/span>/gi, ' ');\n } else {\n return '';\n }\n },\n // ---------------------------------------------------------------------\n // :: Replace brackets with html entities\n // ---------------------------------------------------------------------\n escape_brackets: function escape_brackets(string) {\n return string.replace(/\\[/g, '[')\n .replace(/\\]/g, ']')\n .replace(/\\\\/g, '\');\n },\n // ---------------------------------------------------------------------\n // :: complmentary function\n // ---------------------------------------------------------------------\n unescape_brackets: function unescape_brackets(string) {\n return string.replace(/[/g, '[')\n .replace(/]/g, ']')\n .replace(/\/g, '\\\\');\n },\n // ---------------------------------------------------------------------\n // :: return number of characters without formatting\n // ---------------------------------------------------------------------\n length: function(string, raw) {\n if (!string) {\n return 0;\n }\n return $.terminal.split_characters(raw ? string : text(string)).length;\n },\n // ---------------------------------------------------------------------\n // :: split characters handling emoji, surogate pairs and combine chars\n // ---------------------------------------------------------------------\n split_characters: function split_characters(string) {\n var result = [];\n var get_next_character = make_next_char_fun(string);\n while (string.length) {\n var chr = get_next_character(string);\n string = string.slice(chr.length);\n result.push(chr);\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: return string where array items are in columns padded spaces\n // :: after adding align tabs arr.join('\\t\\t') looks much better\n // ---------------------------------------------------------------------\n columns: function(array, cols, space) {\n var no_formatting = array.map(function(string) {\n return $.terminal.strip(string);\n });\n var lengths = no_formatting.map(function(string) {\n return strlen(string);\n });\n if (typeof space === 'undefined') {\n space = 4;\n }\n var length = Math.max.apply(null, lengths) + space;\n // we need value - 1 because index starts from 0\n var column_limit = Math.floor(cols / length) - 1;\n if (column_limit < 1) {\n return array.join('\\n');\n }\n var lines = [];\n for (var i = 0, len = array.length; i < len; i += column_limit) {\n var line = array.slice(i, i + column_limit);\n var last = line.pop();\n lines.push(line.reduce(function(acc, string) {\n var stripped = $.terminal.strip(string);\n var pad = new Array(length - stripped.length + 1).join(' ');\n acc.push(string + pad);\n return acc;\n }, []).join('') + last);\n }\n return lines.join('\\n');\n },\n // ---------------------------------------------------------------------\n // :: Remove formatting from text\n // ---------------------------------------------------------------------\n strip: function strip(str) {\n if (!$.terminal.have_formatting(str)) {\n return str;\n }\n return $.terminal.format_split(str).map(function(str) {\n if ($.terminal.is_formatting(str)) {\n str = str.replace(format_parts_re, '$6');\n return str.replace(/\\\\([[\\]])/g, function(whole, bracket) {\n return bracket;\n });\n }\n return str;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: Return active terminal\n // ---------------------------------------------------------------------\n active: function active() {\n return terminals.front();\n },\n // ---------------------------------------------------------------------\n // :: Implmentation detail id is always length of terminals Cycle\n // ---------------------------------------------------------------------\n last_id: function last_id() {\n var len = terminals.length();\n return len - 1;\n },\n // ---------------------------------------------------------------------\n // :: Function that works with strings like 'asd' 'asd\\' asd' \"asd asd\"\n // :: asd\\ 123 -n -b / [^ ]+ / /\\s+/ asd\\ a it creates a regex and\n // :: numbers and replaces escape characters in double quotes\n // :: if strict is set to false it only strips single and double quotes\n // :: and escapes spaces\n // ---------------------------------------------------------------------\n parse_argument: function parse_argument(arg, strict) {\n function parse_string(string) {\n // split string to string literals and non-strings\n return string.split(string_re).map(function(string) {\n // remove quotes if before are even number of slashes\n // we don't remove slases becuase they are handled by JSON.parse\n if (string.match(/^['\"`]/)) {\n // fixing regex to match empty string is not worth it\n if (string === '\"\"' || string === \"''\" || string === \"``\") {\n return '';\n }\n var quote = string[0];\n var re = new RegExp(\"(\\\\\\\\\\\\\\\\(?:\\\\\\\\\\\\\\\\)*)\" + quote, \"g\");\n string = string.replace(re, '$1').replace(/^[`'\"]|[`'\"]$/g, '');\n if (quote === \"'\") {\n string = string.replace(/\"/g, '\\\\\"');\n }\n }\n string = '\"' + string + '\"';\n // use build in function to parse rest of escaped characters\n return JSON.parse(string);\n }).join('');\n }\n if (strict === false) {\n if (arg[0] === \"'\" && arg[arg.length - 1] === \"'\") {\n return arg.replace(/^'|'$/g, '');\n } else if (arg[0] === \"`\" && arg[arg.length - 1] === \"`\") {\n return arg.replace(/^`|`$/g, '');\n } else if (arg[0] === '\"' && arg[arg.length - 1] === '\"') {\n return arg.replace(/^\"|\"$/g, '').replace(/\\\\([\" ])/g, '$1');\n } else if (arg.match(/\\/.*\\/[gimy]*$/)) {\n return arg;\n } else if (arg.match(/['\"`]]/)) {\n // part of arg is in quote\n return parse_string(arg);\n } else {\n return arg.replace(/\\\\ /g, ' ');\n }\n }\n if (arg === 'true') {\n return true;\n } else if (arg === 'false') {\n return false;\n }\n var regex = arg.match(re_re);\n if (regex) {\n return new RegExp(regex[1], regex[2]);\n } else if (arg.match(/['\"`]/)) {\n return parse_string(arg);\n } else if (arg.match(/^-?[0-9]+$/)) {\n return parseInt(arg, 10);\n } else if (arg.match(float_re)) {\n return parseFloat(arg);\n } else {\n return arg.replace(/\\\\(['\"() ])/g, '$1');\n }\n },\n // ---------------------------------------------------------------------\n // :: function split and parse arguments\n // ---------------------------------------------------------------------\n parse_arguments: function parse_arguments(string) {\n return $.map(string.match(command_re) || [], $.terminal.parse_argument);\n },\n // ---------------------------------------------------------------------\n // :: Function split and strips single and double quotes\n // :: and escapes spaces\n // ---------------------------------------------------------------------\n split_arguments: function split_arguments(string) {\n return $.map(string.match(command_re) || [], function(arg) {\n return $.terminal.parse_argument(arg, false);\n });\n },\n // ---------------------------------------------------------------------\n // :: Function that returns an object {name,args}. Arguments are parsed\n // :: using the function parse_arguments\n // ---------------------------------------------------------------------\n parse_command: function parse_command(string) {\n return process_command(string, $.terminal.parse_argument);\n },\n // ---------------------------------------------------------------------\n // :: Same as parse_command but arguments are parsed using split_arguments\n // ---------------------------------------------------------------------\n split_command: function split_command(string) {\n return process_command(string, function(arg) {\n return $.terminal.parse_argument(arg, false);\n });\n },\n // ---------------------------------------------------------------------\n // :; function that parse arguments like yargs library\n // ---------------------------------------------------------------------\n parse_options: function parse_options(arg, options) {\n var settings = $.extend({}, {\n boolean: []\n }, options);\n if (typeof arg === 'string') {\n return parse_options($.terminal.split_arguments(arg), options);\n }\n var result = {\n _: []\n };\n function token(value) {\n this.value = value;\n }\n var rest = arg.reduce(function(acc, arg) {\n var str = typeof arg === 'string' ? arg : '';\n if (str.match(/^--?[^-]/) && acc instanceof token) {\n result[acc.value] = true;\n }\n if (str.match(/^--[^-]/)) {\n var name = str.replace(/^--/, '');\n if (settings.boolean.indexOf(name) === -1) {\n return new token(name);\n } else {\n result[name] = true;\n }\n } else if (str.match(/^-[^-]/)) {\n var single = str.replace(/^-/, '').split('');\n if (settings.boolean.indexOf(single.slice(-1)[0]) === -1) {\n var last = single.pop();\n }\n single.forEach(function(single) {\n result[single] = true;\n });\n if (last) {\n return new token(last);\n }\n } else if (acc instanceof token) {\n result[acc.value] = arg;\n } else if (arg) {\n result._.push(arg);\n }\n return null;\n }, null);\n if (rest instanceof token) {\n result[rest.value] = true;\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: function executed for each text inside [[ .... ]] in echo\n // ---------------------------------------------------------------------\n extended_command: function extended_command(term, string, options) {\n var settings = $.extend({\n invokeMethods: false\n }, options);\n var deferred = new $.Deferred();\n try {\n change_hash = false;\n var m = string.match(extended_command_re);\n if (m) {\n if (!settings.invokeMethods) {\n warn('To invoke terminal or cmd methods you need to enable ' +\n 'invokeMethods option');\n deferred.reject();\n } else {\n string = m[1];\n var obj = m[2] === 'terminal' ? term : term.cmd();\n var fn = m[3];\n try {\n var args = eval('[' + m[4] + ']');\n if (!obj[fn]) {\n term.error('Unknow function ' + fn);\n } else {\n var ret = obj[fn].apply(term, args);\n if (ret && ret.then) {\n return ret;\n }\n }\n deferred.resolve();\n } catch (e) {\n term.error('Invalid invocation in ' +\n $.terminal.escape_brackets(string));\n deferred.reject();\n }\n }\n } else {\n return term.exec(string, true).done(function() {\n change_hash = true;\n });\n }\n } catch (e) {\n // error is process in exec\n deferred.reject();\n }\n return deferred.promise();\n },\n // ---------------------------------------------------------------------\n // :: ES6 iterator for a given string that handle emoji and formatting\n // ---------------------------------------------------------------------\n iterator: function(string) {\n function formatting(string) {\n if ($.terminal.is_formatting(string)) {\n if (string.match(/\\]\\\\\\]/)) {\n string = string.replace(/\\]\\\\\\]/g, ']\\\\\\\\]');\n }\n }\n return string;\n }\n if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {\n var len = $.terminal.length(string);\n var i = 0;\n var obj = {};\n obj[Symbol.iterator] = function() {\n return {\n next: function() {\n if (i < len) {\n var text = $.terminal.substring(string, i, i + 1);\n i++;\n return {\n value: formatting(text)\n };\n } else {\n return {\n done: true\n };\n }\n }\n };\n };\n return obj;\n }\n },\n // ---------------------------------------------------------------------\n // :: object that can be used in string methods intead of regex\n // ---------------------------------------------------------------------\n formatter: new (function() {\n try {\n this[Symbol.split] = function(string) {\n return $.terminal.format_split(string);\n };\n this[Symbol.match] = function(string) {\n return string.match(format_re);\n };\n this[Symbol.replace] = function(string, replacer) {\n return string.replace(format_parts_re, replacer);\n };\n this[Symbol.search] = function(string) {\n return string.search(format_re);\n };\n } catch (e) {\n }\n })(),\n // ---------------------------------------------------------------------\n // :: helper function that add formatter before nested_formatting\n // ---------------------------------------------------------------------\n new_formatter: function(formatter) {\n var formatters = $.terminal.defaults.formatters;\n for (var i = 0; i < formatters.length; ++i) {\n if (formatters[i] === $.terminal.nested_formatting) {\n formatters.splice(i, 0, formatter);\n return;\n }\n }\n formatters.push(formatter);\n }\n };\n // -------------------------------------------------------------------------\n $.terminal.Exception = function Terminal_Exception(type, message, stack) {\n if (arguments.length === 1) {\n this.message = arguments[0];\n this.type = 'TERMINAL';\n } else {\n this.type = type;\n this.message = message;\n if (stack) {\n this.stack = stack;\n }\n }\n };\n $.terminal.Exception.prototype = new Error();\n $.terminal.Exception.prototype.toString = function() {\n return this.message + '\\n' + this.stack;\n };\n // -----------------------------------------------------------------------\n // Helper plugins and functions\n // -----------------------------------------------------------------------\n $.fn.visible = function() {\n return this.css('visibility', 'visible');\n };\n $.fn.hidden = function() {\n return this.css('visibility', 'hidden');\n };\n // -----------------------------------------------------------------------\n var warnings = [];\n function warn(msg) {\n msg = '[jQuery Terminal] ' + msg;\n if (warnings.indexOf(msg) === -1) {\n warnings.push(msg);\n /* eslint-disable */\n if (console) {\n if (console.warn) {\n console.warn(msg);\n } else if (console.log) {\n console.log(msg);\n }\n /* eslint-enable */\n } else {\n // prevent catching in outer try..catch\n setTimeout(function() {\n throw new Error('WARN: ' + msg);\n }, 0);\n }\n }\n }\n // -----------------------------------------------------------------------\n // JSON-RPC CALL\n // -----------------------------------------------------------------------\n var ids = {}; // list of url based ids of JSON-RPC\n $.jrpc = function(url, method, params, success, error) {\n var deferred = new $.Deferred();\n var options;\n if ($.isPlainObject(url)) {\n options = url;\n } else {\n options = {\n url: url,\n method: method,\n params: params,\n success: success,\n error: error\n };\n }\n function validJSONRPC(response) {\n return $.isNumeric(response.id) &&\n (typeof response.result !== 'undefined' ||\n typeof response.error !== 'undefined');\n }\n ids[options.url] = ids[options.url] || 0;\n var request = {\n 'jsonrpc': '2.0',\n 'method': options.method,\n 'params': options.params,\n 'id': ++ids[options.url]\n };\n $.ajax({\n url: options.url,\n beforeSend: function beforeSend(jxhr, settings) {\n if (is_function(options.request)) {\n options.request(jxhr, request);\n }\n settings.data = JSON.stringify(request);\n },\n success: function success(response, status, jqXHR) {\n var content_type = jqXHR.getResponseHeader('Content-Type');\n if (!content_type.match(/(application|text)\\/json/)) {\n warn('Response Content-Type is neither application/json' +\n ' nor text/json');\n }\n var json;\n try {\n json = JSON.parse(response);\n } catch (e) {\n if (options.error) {\n options.error(jqXHR, 'Invalid JSON', e);\n } else {\n throw new $.terminal.Exception('JSON', 'Invalid JSON', e.stack);\n }\n deferred.reject({message: 'Invalid JSON', response: response});\n return;\n }\n if (is_function(options.response)) {\n options.response(jqXHR, json);\n }\n if (validJSONRPC(json) || options.method === 'system.describe') {\n // don't catch errors in success callback\n if (options.success) {\n options.success(json, status, jqXHR);\n }\n deferred.resolve(json);\n } else {\n if (options.error) {\n options.error(jqXHR, 'Invalid JSON-RPC');\n }\n deferred.reject({message: 'Invalid JSON-RPC', response: response});\n }\n },\n error: options.error,\n contentType: 'application/json',\n dataType: 'text',\n async: true,\n cache: false,\n // timeout: 1,\n type: 'POST'\n });\n return deferred.promise();\n };\n // -----------------------------------------------------------------------\n $.rpc = function(url, method, params) {\n var deferred = new $.Deferred();\n function success(res) {\n if (res.error) {\n deferred.reject(res.error);\n } else {\n deferred.resolve(res.result);\n }\n }\n function error(jqXHR, status, message) {\n deferred.reject({message: message});\n }\n $.jrpc(url, method, params, success, error);\n return deferred.promise();\n };\n // -----------------------------------------------------------------------\n function terminal_ready(term) {\n return !!(term.closest('body').length &&\n term.is(':visible') &&\n term.find('.cmd-prompt').length);\n }\n // -----------------------------------------------------------------------\n // :: Create fake terminal to calcualte the dimention of one character\n // :: this will make terminal work if terminal div is not added to the\n // :: DOM at init like with:\n // :: $('').terminal().echo('foo bar').appendTo('body');\n // -----------------------------------------------------------------------\n function get_char_size(term) {\n var rect;\n if (terminal_ready(term)) {\n var $prompt = term.find('.cmd-prompt').clone().css({\n visiblity: 'hidden',\n position: 'absolute'\n });\n $prompt.appendTo(term.find('.cmd')).html(' ');\n rect = $prompt[0].getBoundingClientRect();\n $prompt.remove();\n } else {\n var temp = $('
').appendTo('body');\n temp.addClass(term.attr('class')).attr('id', term.attr('id'));\n if (term) {\n var style = term.attr('style');\n if (style) {\n style = style.split(/\\s*;\\s*/).filter(function(s) {\n return !s.match(/display\\s*:\\s*none/i);\n }).join(';');\n temp.attr('style', style);\n }\n }\n rect = temp.find('.terminal-line')[0].getBoundingClientRect();\n }\n var result = {\n width: rect.width,\n height: rect.height\n };\n if (temp) {\n temp.remove();\n }\n return result;\n }\n // -----------------------------------------------------------------------\n // :: calculate numbers of characters\n // -----------------------------------------------------------------------\n function get_num_chars(terminal, char_size) {\n var width = terminal.find('.terminal-fill').width();\n var result = Math.floor(width / char_size.width);\n // random number to not get NaN in node.js but big enough to\n // not wrap exception\n return result || 1000;\n }\n // -----------------------------------------------------------------------\n // :: Calculate number of lines that fit without scroll\n // -----------------------------------------------------------------------\n function get_num_rows(terminal, char_size) {\n var height = terminal.find('.terminal-fill').height();\n return Math.floor(height / char_size.height);\n }\n // -----------------------------------------------------------------------\n function all(array, fn) {\n var same = array.filter(function(item) {\n return item[fn]() === item;\n });\n return same.length === array.length;\n }\n // -----------------------------------------------------------------------\n function string_case(string) {\n var array = string.split('');\n if (all(array, 'toLowerCase')) {\n return 'lower';\n } else if (all(array, 'toUpperCase')) {\n return 'upper';\n } else {\n return 'mixed';\n }\n }\n // -----------------------------------------------------------------------\n function same_case(string) {\n return string_case(string) !== 'mixed';\n }\n // -----------------------------------------------------------------------\n // fix for jQuery bug\n function is_function(object) {\n return get_type(object) === 'function';\n }\n // -----------------------------------------------------------------------\n function is_object(object) {\n return object && typeof object === 'object';\n }\n // -----------------------------------------------------------------------\n function is_promise(object) {\n return is_object(object) && is_function(object.then || object.done);\n }\n // -----------------------------------------------------------------------\n function is_deferred(object) {\n return is_promise(object) && is_function(object.promise);\n }\n // -----------------------------------------------------------------------\n if (!Array.isArray) {\n Array.isArray = function(arg) {\n return Object.prototype.toString.call(arg) === '[object Array]';\n };\n }\n // -----------------------------------------------------------------------\n function is_array(object) {\n return Array.isArray(object);\n }\n // -----------------------------------------------------------------------\n function get_type(object) {\n if (typeof object === 'function') {\n return 'function';\n }\n if (object === null) {\n return object + '';\n }\n if (Array.isArray(object)) {\n return 'array';\n }\n if (typeof object === 'object') {\n return 'object';\n }\n return typeof object;\n }\n // -----------------------------------------------------------------------\n // :: TERMINAL PLUGIN CODE\n // -----------------------------------------------------------------------\n var version_set = !$.terminal.version.match(/^\\{\\{/);\n var copyright = 'Copyright (c) 2011-2021 Jakub T. Jankiewicz ' +\n '';\n var version_string = version_set ? ' v. ' + $.terminal.version : ' ';\n // regex is for placing version string aligned to the right\n var reg = new RegExp(' {' + version_string.length + '}$');\n var name_ver = 'jQuery Terminal Emulator' +\n (version_set ? version_string : '');\n // -----------------------------------------------------------------------\n // :: Terminal Signatures\n // -----------------------------------------------------------------------\n var signatures = [\n ['jQuery Terminal', '(c) 2011-2021 jcubic'],\n [name_ver, copyright.replace(/^Copyright | *<.*>/g, '')],\n [name_ver, copyright.replace(/^Copyright /, '')],\n [\n ' _______ ________ __',\n ' / / _ /_ ____________ _/__ ___/______________ _____ / /',\n ' __ / / // / // / _ / _/ // / / / _ / _/ / / \\\\/ / _ \\\\/ /',\n '/ / / // / // / ___/ // // / / / ___/ // / / / / /\\\\ / // / /__',\n '\\\\___/____ \\\\\\\\__/____/_/ \\\\__ / /_/____/_//_/_/_/_/_/ \\\\/\\\\__\\\\_\\\\___/',\n ' \\\\/ /____/ '\n .replace(reg, ' ') + version_string,\n copyright\n ],\n [\n ' __ _____ ________ ' +\n ' __',\n ' / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ ' +\n ' / /',\n ' __ / // // // // // _ // _// // / / // _ // _// // // \\\\/ // _ ' +\n '\\\\/ /',\n '/ / // // // // // ___// / / // / / // ___// / / / / // // /\\\\ // // ' +\n '/ /__',\n '\\\\___//____ \\\\\\\\___//____//_/ _\\\\_ / /_//____//_/ /_/ /_//_//_/ /_/ \\\\' +\n '__\\\\_\\\\___/',\n (' \\\\/ /____/ ' +\n ' ').replace(reg, '') + version_string,\n copyright\n ]\n ];\n // -----------------------------------------------------------------------\n // :: Default options\n // -----------------------------------------------------------------------\n $.terminal.nested_formatting.__meta__ = true;\n // if set to false nested formatting will not inherit styles colors and attribues\n $.terminal.nested_formatting.__inherit__ = true;\n // nested formatting will always return different length so we silent the warning\n $.terminal.nested_formatting.__no_warn__ = true;\n $.terminal.defaults = {\n prompt: '> ',\n history: true,\n exit: true,\n clear: true,\n enabled: true,\n maskChar: '*',\n wrap: true,\n checkArity: true,\n raw: false,\n tabindex: 1,\n invokeMethods: false,\n exceptionHandler: null,\n pauseEvents: true,\n softPause: false,\n mousewheel: null,\n touchscroll: null,\n memory: false,\n cancelableAjax: true,\n processArguments: true,\n execAnimation: false,\n execAnimationDelay: 100,\n linksNoReferrer: false,\n useCache: true,\n anyLinks: false,\n linksNoFollow: false,\n processRPCResponse: null,\n completionEscape: true,\n onCommandChange: null,\n mobileDelete: is_mobile,\n onPositionChange: null,\n convertLinks: true,\n extra: {},\n tabs: 4,\n historySize: 60,\n scrollObject: null,\n historyState: false,\n importHistory: false,\n historyFilter: null,\n echoCommand: true,\n scrollOnEcho: true,\n login: null,\n outputLimit: -1,\n formatters: [$.terminal.nested_formatting],\n unixFormatting: {\n escapeBrackets: false,\n ansiParser: {},\n ansiArt: false\n },\n onAjaxError: null,\n pasteImage: true,\n scrollBottomOffset: 20,\n wordAutocomplete: true,\n caseSensitiveAutocomplete: true,\n caseSensitiveSearch: true,\n clickTimeout: 200,\n holdTimeout: 400,\n holdRepeatTimeout: 200,\n repeatTimeoutKeys: [],\n mobileIngoreAutoSpace: [],\n request: $.noop,\n response: $.noop,\n describe: 'procs',\n onRPCError: null,\n keymap: null,\n doubleTab: null,\n doubleTabEchoCommand: false,\n completion: false,\n onInit: $.noop,\n onClear: $.noop,\n onBlur: $.noop,\n onFocus: $.noop,\n onTerminalChange: $.noop,\n onExit: $.noop,\n onPush: $.noop,\n onPop: $.noop,\n keypress: $.noop,\n keydown: $.noop,\n renderHandler: null,\n onAfterRedraw: $.noop,\n onEchoCommand: $.noop,\n onPaste: $.noop,\n onFlush: $.noop,\n onBeforeCommand: null,\n onAfterCommand: null,\n onBeforeEcho: null,\n onAfterEcho: null,\n onBeforeLogin: null,\n onAfterLogout: null,\n onBeforeLogout: null,\n allowedAttributes: ['title', /^aria-/, 'id', /^data-/],\n strings: {\n comletionParameters: 'From version 1.0.0 completion function need to' +\n ' have two arguments',\n wrongPasswordTryAgain: 'Wrong username or password try again!',\n wrongPassword: 'Wrong username or password!',\n ajaxAbortError: 'Error while aborting ajax call!',\n wrongArity: \"Wrong number of arguments. Function '%s' expects %s got\" +\n ' %s!',\n commandNotFound: \"Command '%s' Not Found!\",\n oneRPCWithIgnore: 'You can use only one rpc with describe == false ' +\n 'or rpc without system.describe',\n oneInterpreterFunction: \"You can't use more than one function (rpc \" +\n 'without system.describe or with option describe == false count' +\n 's as one)',\n loginFunctionMissing: \"You didn't specify a login function\",\n noTokenError: 'Access denied (no token)',\n serverResponse: 'Server responded',\n wrongGreetings: 'Wrong value of greetings parameter',\n notWhileLogin: \"You can't call `%s' function while in login\",\n loginIsNotAFunction: 'Authenticate must be a function',\n canExitError: \"You can't exit from main interpreter\",\n invalidCompletion: 'Invalid completion',\n invalidSelector: 'Sorry, but terminal said that you use invalid ' +\n 'selector!',\n invalidTerminalId: 'Invalid Terminal ID',\n login: 'login',\n password: 'password',\n recursiveLoop: 'Recursive loop in echo detected, skip',\n notAString: '%s function: argument is not a string',\n redrawError: 'Internal error, wrong position in cmd redraw',\n invalidStrings: 'Command %s have unclosed strings',\n defunctTerminal: \"You can't call method on terminal that was destroyed\"\n }\n };\n // -------------------------------------------------------------------------\n // :: All terminal globals\n // -------------------------------------------------------------------------\n var requests = []; // for canceling on CTRL+D\n var terminals = new Cycle(); // list of terminals global in this scope\n // state for all terminals, terminals can't have own array fo state because\n // there is only one popstate event\n var save_state = []; // hold objects returned by export_view by history API\n var hash_commands;\n var change_hash = false; // don't change hash on Init\n var fire_hash_change = true;\n var first_instance = true; // used by history state\n $.fn.terminal = function(init_interpreter, options) {\n function StorageHelper(memory) {\n if (memory) {\n this.storage = {};\n }\n this.set = function(key, value) {\n if (memory) {\n this.storage[key] = value;\n } else {\n $.Storage.set(key, value);\n }\n };\n this.get = function(key) {\n if (memory) {\n return this.storage[key];\n } else {\n return $.Storage.get(key);\n }\n };\n this.remove = function(key) {\n if (memory) {\n delete this.storage[key];\n } else {\n $.Storage.remove(key);\n }\n };\n }\n // ---------------------------------------------------------------------\n // :: helper function\n // ---------------------------------------------------------------------\n function get_processed_command(command) {\n if ($.terminal.unclosed_strings(command)) {\n var string = $.terminal.escape_brackets(command);\n var message = sprintf(strings().invalidStrings, \"`\" + string + \"`\");\n throw new $.terminal.Exception(message);\n } else if (is_function(settings.processArguments)) {\n return process_command(command, settings.processArguments);\n } else if (settings.processArguments) {\n return $.terminal.parse_command(command);\n } else {\n return $.terminal.split_command(command);\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function that use option to render objects\n // ---------------------------------------------------------------------\n function preprocess_value(value, options) {\n if ($.terminal.Animation && value instanceof $.terminal.Animation) {\n value.start(self);\n return false;\n }\n if (is_function(settings.renderHandler)) {\n var ret = settings.renderHandler.call(self, value, options, self);\n if (ret === false) {\n return false;\n }\n if (typeof ret === 'string' || is_node(ret) || is_promise(ret)) {\n return ret;\n } else {\n return value;\n }\n }\n return value;\n }\n // ---------------------------------------------------------------------\n // :: call when line is out of view when outputLimit is used\n // :: NOTE: it's not called when less plugin is used onClear is called\n // :: instead because less call term::clear() after export old view\n // ---------------------------------------------------------------------\n function unmount(node) {\n var index = node.data('index');\n var line = lines[index];\n var options = line[1];\n if (is_function(options.unmount)) {\n options.unmount.call(self, node);\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function used in render and in update\n // ---------------------------------------------------------------------\n function prepare_render(value, options) {\n if (is_node(value)) {\n var settings = $.extend({}, options, {\n raw: true,\n finalize: function(div) {\n div.find('.terminal-render-item').replaceWith(value);\n if (options && is_function(options.finalize)) {\n options.finalize(div, self);\n }\n }\n });\n return ['
', settings];\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function that renders DOM nodes and jQuery objects\n // ---------------------------------------------------------------------\n function render(value, options) {\n var ret = prepare_render(value, options);\n if (ret) {\n self.echo.apply(self, ret);\n return true;\n }\n }\n // ---------------------------------------------------------------------\n function get_node(index) {\n return output.find('[data-index=' + index + ']');\n }\n // ---------------------------------------------------------------------\n // :: test if object can be rendered\n // ---------------------------------------------------------------------\n function is_node(object) {\n return object instanceof $.fn.init || object instanceof Element;\n }\n // ---------------------------------------------------------------------\n // :: Display object on terminal\n // ---------------------------------------------------------------------\n function display_object(object) {\n object = preprocess_value(object);\n if (object === false) {\n return;\n }\n if (render(object)) {\n return;\n }\n if (typeof object === 'string') {\n self.echo(object);\n } else if (is_array(object)) {\n self.echo($.map(object, function(object) {\n return JSON.stringify(object);\n }).join(' '));\n } else if (typeof object === 'object') {\n self.echo(JSON.stringify(object));\n } else {\n self.echo(object);\n }\n }\n // ---------------------------------------------------------------------\n // :: Display line code in the file if line numbers are present\n // ---------------------------------------------------------------------\n function print_line(url_spec, cols) {\n var re = /(.*):([0-9]+):([0-9]+)$/;\n // google chrome have line and column after filename\n var m = url_spec.match(re);\n if (m) {\n // TODO: do we need to call pause/resume or return promise?\n self.pause(settings.softPause);\n $.get(m[1], function(response) {\n var file = m[1];\n var code = response.split('\\n');\n var n = +m[2] - 1;\n var start = n > 2 ? n - 2 : 0;\n var lines = code.slice(start, n + 3).map(function(line, i) {\n var prefix = '[' + (n + i - 1) + ']: ';\n var limit = cols - prefix.length - 4;\n if (line.length > limit) {\n line = line.substring(0, limit) + '...';\n }\n if (n > 2 ? i === 2 : i === n) {\n line = '[[;#f00;]' +\n $.terminal.escape_brackets(line) + ']';\n }\n return prefix + line;\n }).filter(Boolean).join('\\n');\n if (lines.length) {\n self.echo('[[b;white;]' + file + ']');\n self.echo(lines).resume();\n }\n }, 'text');\n }\n }\n // ---------------------------------------------------------------------\n // :: Helper function\n // ---------------------------------------------------------------------\n function display_json_rpc_error(error) {\n if (is_function(settings.onRPCError)) {\n settings.onRPCError.call(self, error);\n } else {\n self.error('[RPC] ' + error.message);\n if (error.error && error.error.message) {\n error = error.error;\n // more detailed error message\n var msg = '\\t' + error.message;\n if (error.file) {\n msg += ' in file \"' + error.file.replace(/.*\\//, '') + '\"';\n }\n if (error.at) {\n msg += ' at line ' + error.at;\n }\n self.error(msg);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Create interpreter function from url string\n // ---------------------------------------------------------------------\n function make_basic_json_rpc(url, auth) {\n var interpreter = function(method, params) {\n self.pause(settings.softPause);\n $.jrpc({\n url: url,\n method: method,\n params: params,\n request: function(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n response: function(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n success: function success(json) {\n if (json.error) {\n display_json_rpc_error(json.error);\n } else if (is_function(settings.processRPCResponse)) {\n settings.processRPCResponse.call(self, json.result, self);\n } else if (json.result !== null) {\n display_object(json.result);\n }\n self.resume();\n },\n error: ajax_error\n });\n };\n // this is the interpreter function\n return function(command, terminal) {\n if (command === '') {\n return;\n }\n try {\n command = get_processed_command(command);\n } catch (e) {\n // exception can be thrown on invalid regex\n display_exception(e, 'TERMINAL (get_processed_command)');\n return;\n // throw e; // this will show stack in other try..catch\n }\n if (!auth || command.name === 'help') {\n // allows to call help without a token\n interpreter(command.name, command.args);\n } else {\n var token = terminal.token(true);\n if (token) {\n interpreter(command.name, [token].concat(command.args));\n } else {\n // should never happen\n terminal.error('[AUTH] ' + strings().noTokenError);\n }\n }\n };\n }\n // ---------------------------------------------------------------------\n // :: Create interpreter function from Object. If the value is object\n // :: it will create nested interpreters\n // ---------------------------------------------------------------------\n function make_object_interpreter(object, arity, login, fallback) {\n // function that maps commands to object methods\n // it keeps terminal context\n return function(user_command, terminal) {\n if (user_command === '') {\n return;\n }\n var command;\n try {\n command = get_processed_command(user_command);\n } catch (e) {\n // exception can be thrown on invalid regex\n if (is_function(settings.exception)) {\n settings.exception(e, self);\n } else {\n self.error('Error: ' + (e.message || e));\n }\n return;\n // throw e; // this will show stack in other try..catch\n }\n var val = object[command.name];\n var type = get_type(val);\n if (type === 'function') {\n if (arity && val.length !== command.args.length) {\n self.error(\n '[Arity] ' +\n sprintf(\n strings().wrongArity,\n command.name,\n val.length,\n command.args.length\n )\n );\n } else {\n return val.apply(self, command.args);\n }\n } else if (type === 'object' || type === 'string') {\n var commands = [];\n if (type === 'object') {\n commands = Object.keys(val);\n val = make_object_interpreter(\n val,\n arity,\n login\n );\n }\n terminal.push(val, {\n prompt: command.name + '> ',\n name: command.name,\n completion: type === 'object' ? commands : undefined\n });\n } else if (is_function(fallback)) {\n fallback(user_command, self);\n } else if (is_function(settings.onCommandNotFound)) {\n settings.onCommandNotFound.call(self, user_command, self);\n } else {\n terminal.error(sprintf(strings().commandNotFound, command.name));\n }\n };\n }\n // ---------------------------------------------------------------------\n function ajax_error(xhr, status, error) {\n self.resume(); // onAjaxError can use pause/resume call it first\n if (is_function(settings.onAjaxError)) {\n settings.onAjaxError.call(self, xhr, status, error);\n } else if (status !== 'abort') {\n self.error('[AJAX] ' + status + ' - ' +\n strings().serverResponse + ':\\n' +\n $.terminal.escape_brackets(xhr.responseText));\n }\n }\n // ---------------------------------------------------------------------\n // :: function create interpreter object based on JSON-RPC meta data\n // ---------------------------------------------------------------------\n function make_json_rpc_object(url, auth, success) {\n function jrpc_success(json) {\n if (json.error) {\n display_json_rpc_error(json.error);\n } else if (is_function(settings.processRPCResponse)) {\n settings.processRPCResponse.call(self, json.result, self);\n } else {\n display_object(json.result);\n }\n self.resume();\n }\n function jrpc_request(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n }\n function jrpc_response(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n }\n function response(response) {\n var procs = response;\n // we check if it's false before we call this function but\n // it don't hurt to be explicit here\n if (settings.describe !== false && settings.describe !== '') {\n settings.describe.split('.').forEach(function(field) {\n procs = procs[field];\n });\n }\n if (procs && procs.length) {\n var interpreter_object = {};\n $.each(procs, function(_, proc) {\n if ($.isPlainObject(proc) && typeof proc.name === 'string') {\n interpreter_object[proc.name] = function() {\n var append = auth && proc.name !== 'help';\n var args = Array.prototype.slice.call(arguments);\n var args_len = args.length + (append ? 1 : 0);\n if (settings.checkArity && proc.params &&\n proc.params.length !== args_len) {\n self.error(\n '[Arity] ' +\n sprintf(\n strings().wrongArity,\n proc.name,\n proc.params.length,\n args_len\n )\n );\n } else {\n self.pause(settings.softPause);\n if (append) {\n var token = self.token(true);\n if (token) {\n args = [token].concat(args);\n } else {\n self.error('[AUTH] ' +\n strings().noTokenError);\n }\n }\n $.jrpc({\n url: url,\n method: proc.name,\n params: args,\n request: jrpc_request,\n response: jrpc_response,\n success: jrpc_success,\n error: ajax_error\n });\n }\n };\n }\n });\n var login = typeof auth === 'string' ? auth : 'login';\n interpreter_object.help = interpreter_object.help || function(fn) {\n if (typeof fn === 'undefined') {\n var names = procs.map(function(proc) {\n return proc.name;\n }).join(', ') + ', help';\n self.echo('Available commands: ' + names);\n } else {\n var found = false;\n $.each(procs, function(_, proc) {\n if (proc.name === fn) {\n found = true;\n var msg = '';\n msg += '[[bu;;]' + proc.name + ']';\n if (proc.params) {\n var params = proc.params;\n if (auth && proc.name !== login) {\n params = params.slice(1);\n }\n msg += ' ' + params.join(' ');\n }\n if (proc.help) {\n msg += '\\n' + proc.help;\n }\n self.echo(msg);\n return false;\n }\n });\n if (!found) {\n if (fn === 'help') {\n self.echo('[[bu;;]help] [method]\\ndisplay help ' +\n 'for the method or list of methods if not' +\n ' specified');\n } else {\n var msg = 'Method `' + fn + \"' not found \";\n self.error(msg);\n }\n }\n }\n };\n success(interpreter_object);\n } else {\n success(null);\n }\n }\n return $.jrpc({\n url: url,\n method: 'system.describe',\n params: [],\n success: response,\n request: jrpc_request,\n response: jrpc_response,\n error: function error() {\n success(null);\n }\n });\n }\n // ---------------------------------------------------------------------\n // :: function create interpeter function and call finalize with\n // :: interpreter and optional completion\n // ---------------------------------------------------------------------\n function make_interpreter(user_intrp, login, finalize) {\n finalize = finalize || $.noop;\n var type = get_type(user_intrp);\n var object;\n var result = {};\n var rpc_count = 0; // only one rpc can be use for array\n var fn_interpreter;\n if (type === 'array') {\n object = {};\n // recur will be called when previous acync call is finished\n (function recur(interpreters, success) {\n if (interpreters.length) {\n var first = interpreters[0];\n var rest = interpreters.slice(1);\n var type = get_type(first);\n if (type === 'string') {\n self.pause(settings.softPause);\n if (settings.describe === false) {\n if (++rpc_count === 1) {\n fn_interpreter = make_basic_json_rpc(first, login);\n } else {\n self.error(strings().oneRPCWithIgnore);\n }\n recur(rest, success);\n } else {\n make_json_rpc_object(first, login, function(new_obj) {\n if (new_obj) {\n $.extend(object, new_obj);\n } else if (++rpc_count === 1) {\n fn_interpreter = make_basic_json_rpc(\n first,\n login\n );\n } else {\n self.error(strings().oneRPCWithIgnore);\n }\n self.resume();\n recur(rest, success);\n });\n }\n } else if (type === 'function') {\n if (fn_interpreter) {\n self.error(strings().oneInterpreterFunction);\n } else {\n fn_interpreter = first;\n }\n recur(rest, success);\n } else if (type === 'object') {\n $.extend(object, first);\n recur(rest, success);\n }\n } else {\n success();\n }\n })(user_intrp, function() {\n finalize({\n interpreter: make_object_interpreter(\n object,\n false,\n login,\n fn_interpreter && fn_interpreter.bind(self)\n ),\n completion: Object.keys(object)\n });\n });\n } else if (type === 'string') {\n if (settings.describe === false) {\n object = {\n interpreter: make_basic_json_rpc(user_intrp, login)\n };\n if ($.isArray(settings.completion)) {\n object.completion = settings.completion;\n }\n finalize(object);\n } else {\n self.pause(settings.softPause);\n make_json_rpc_object(user_intrp, login, function(object) {\n if (object) {\n result.interpreter = make_object_interpreter(\n object,\n false,\n login\n );\n result.completion = Object.keys(object);\n } else {\n // no procs in system.describe\n result.interpreter = make_basic_json_rpc(user_intrp, login);\n }\n finalize(result);\n self.resume();\n });\n }\n } else if (type === 'object') {\n finalize({\n interpreter: make_object_interpreter(\n user_intrp,\n settings.checkArity,\n login\n ),\n completion: Object.keys(user_intrp)\n });\n } else {\n // allow $('').terminal();\n if (type === 'undefined') {\n user_intrp = $.noop;\n } else if (type !== 'function') {\n var msg = type + ' is invalid interpreter value';\n throw new $.terminal.Exception(msg);\n }\n // single function don't need bind\n finalize({\n interpreter: user_intrp,\n completion: settings.completion\n });\n }\n }\n // ---------------------------------------------------------------------\n // :: Create JSON-RPC authentication function\n // ---------------------------------------------------------------------\n function make_json_rpc_login(url, login) {\n var method = get_type(login) === 'boolean' ? 'login' : login;\n return function(user, passwd, callback) {\n self.pause(settings.softPause);\n $.jrpc({\n url: url,\n method: method,\n params: [user, passwd],\n request: function(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n response: function(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n success: function success(response) {\n if (!response.error && response.result) {\n callback(response.result);\n } else {\n // null will trigger message that login fail\n callback(null);\n }\n self.resume();\n },\n error: ajax_error\n });\n };\n // default name is login so you can pass true\n }\n // ---------------------------------------------------------------------\n // :: display Exception on terminal\n // ---------------------------------------------------------------------\n function display_exception(e, label, silent) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, label);\n } else {\n self.exception(e, label);\n if (!silent) {\n setTimeout(function() {\n throw e;\n }, 0);\n }\n }\n }\n // ---------------------------------------------------------------------\n function links(string) {\n function format(_, style, color, background, _class, data, text) {\n function formatting(s, text) {\n return '[[' + [\n style + (s || ''),\n color,\n background,\n _class,\n text || data\n ].join(';') + ']';\n }\n function escaped(_) {\n return ']' + formatting('!', _) + _ + ']' + formatting();\n }\n if (!style.match(/!/)) {\n var m = text.match(email_full_re) || text.match(url_full_re);\n if (m) {\n return formatting('!', m[1]) + text + ']';\n } else if (text.match(email_re) || text.match(url_nf_re)) {\n var output = text.replace(email_re, escaped)\n .replace(url_nf_re, escaped);\n return formatting('', data) + output + ']';\n }\n }\n return _;\n }\n function linkify(string) {\n return string.replace(email_re, '[[!;;]$1]').\n replace(url_nf_re, '[[!;;]$1]');\n }\n if (!$.terminal.have_formatting(string)) {\n return linkify(string);\n }\n return $.terminal.format_split(string).map(function(str) {\n if ($.terminal.is_formatting(str)) {\n return str.replace(format_parts_re, format);\n } else {\n return linkify(str);\n }\n }).join('');\n }\n // ---------------------------------------------------------------------\n function should_wrap(string, options) {\n return (strlen(text(string)) > options.cols ||\n string.match(/\\n/)) &&\n ((settings.wrap === true &&\n options.wrap === undefined) ||\n settings.wrap === false &&\n options.wrap === true);\n }\n // ---------------------------------------------------------------------\n var line_cache;\n if ('Map' in root) {\n line_cache = new Map();\n }\n // ---------------------------------------------------------------------\n function process_extended_commands(string, line, line_settings) {\n if (line_settings.exec || line.options.clear_exec) {\n return $.terminal.each_extended_command(string, function(command) {\n // redraw should not execute commands and it have\n // and lines variable have all extended commands\n if (line_settings.exec) {\n line.options.exec = false;\n line.options.clear_exec = true;\n var trim = command.trim();\n if (prev_exec_cmd && prev_exec_cmd === trim) {\n prev_exec_cmd = '';\n self.error(strings().recursiveLoop);\n } else {\n prev_exec_cmd = trim;\n $.terminal.extended_command(self, command, {\n invokeMethods: line_settings.invokeMethods\n }).then(function() {\n prev_exec_cmd = '';\n });\n }\n }\n });\n }\n return string;\n }\n // ---------------------------------------------------------------------\n function process_line(line) {\n // prevent exception in display exception\n try {\n var use_cache = !is_function(line.value);\n var line_settings = $.extend({\n exec: true,\n raw: false,\n finalize: $.noop,\n useCache: use_cache,\n invokeMethods: false,\n formatters: true,\n convertLinks: settings.convertLinks\n }, line.options || {});\n var string = stringify_value(line.value);\n if (string && is_function(string.then)) {\n // handle function that return a promise #629\n return string.then(function(string) {\n process_line($.extend(line, {\n value: string,\n options: line_settings\n }));\n });\n }\n if (string !== '') {\n if (!line_settings.raw) {\n if (settings.useCache && line_settings.useCache) {\n var key = string;\n if (line_cache && line_cache.has(key)) {\n var data = line_cache.get(key);\n buffer.append(\n data.input,\n line.index,\n line_settings,\n data.raw\n );\n return true;\n }\n }\n if (line_settings.formatters) {\n try {\n string = $.terminal.apply_formatters(\n string,\n $.extend(settings, {echo: true})\n );\n } catch (e) {\n display_exception(e, 'FORMATTING');\n }\n }\n string = process_extended_commands(string, line, line_settings);\n if (string === '') {\n return;\n }\n if (line_settings.convertLinks) {\n string = links(string);\n }\n var raw_string = string;\n string = crlf($.terminal.normalize(string));\n string = $.terminal.encode(string, {\n tabs: settings.tabs\n });\n //string = $.terminal.normalize(string);\n var array;\n var cols = line_settings.cols = self.cols();\n if (should_wrap(string, line_settings)) {\n var words = line_settings.keepWords;\n array = $.terminal.split_equal(string, cols, words);\n } else if (string.match(/\\n/)) {\n array = string.split(/\\n/);\n }\n }\n } else {\n raw_string = '';\n }\n var arg = array || string;\n if (line_cache && key && use_cache) {\n line_cache.set(key, {input: arg, raw: raw_string});\n }\n buffer.append(arg, line.index, line_settings, raw_string);\n } catch (e) {\n buffer.clear();\n // don't display exception if exception throw in terminal\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL');\n } else {\n alert_exception('[Internal Exception(process_line)]', e);\n }\n }\n // is it work with unpromise that ignore undefined\n return true;\n }\n // ---------------------------------------------------------------------\n // :: Update terminal lines\n // ---------------------------------------------------------------------\n function redraw(options) {\n options = $.extend({}, {\n // should be used when single line is updated\n update: false,\n // should be used if you want to scroll to bottom after redraw\n scroll: true\n }, options || {});\n if (!options.update) {\n command_line.resize(num_chars);\n // we don't want reflow while processing lines\n var detached_output = output.empty().detach();\n }\n try {\n buffer.clear();\n unpromise(lines.render(self.rows(), function(lines_to_show) {\n return lines_to_show.map(function(line) {\n return process_line(line);\n });\n }), function() {\n self.flush(options);\n if (!options.update) {\n command_line.before(detached_output); // reinsert output\n }\n fire_event('onAfterRedraw');\n });\n } catch (e) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL (redraw)');\n } else {\n alert_exception('[redraw]', e);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Function limit output lines based on outputLimit option\n // ---------------------------------------------------------------------\n function limit_lines() {\n if (settings.outputLimit >= 0) {\n var limit;\n if (settings.outputLimit === 0) {\n limit = self.rows();\n } else {\n limit = settings.outputLimit;\n }\n var $lines = output.find('> div > div');\n if ($lines.length + 1 > limit) {\n var max = $lines.length - limit + 1;\n var for_remove = $lines.slice(0, max);\n // you can't get parent if you remove the\n // element so we first get the parent\n var parents = for_remove.parent();\n for_remove.remove();\n parents.each(function() {\n var $self = $(this);\n if ($self.is(':empty')) {\n unmount($self);\n // there can be divs inside parent that\n // was not removed\n $self.remove();\n }\n });\n lines.limit_snapshot(max);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Display user greetings or terminal signature\n // ---------------------------------------------------------------------\n function show_greetings() {\n if (settings.greetings === undefined) {\n // signature have ascii art so it's not suite for screen readers\n self.echo(self.signature, {finalize: a11y_hide, formatters: false});\n } else if (settings.greetings) {\n var type = typeof settings.greetings;\n if (type === 'string') {\n self.echo(settings.greetings);\n } else if (type === 'function') {\n self.echo(function() {\n try {\n return settings.greetings.call(self, self.echo);\n } catch (e) {\n settings.greetings = null;\n display_exception(e, 'greetings');\n }\n });\n } else {\n self.error(strings().wrongGreetings);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Display prompt and last command\n // ---------------------------------------------------------------------\n function echo_command(command) {\n if (typeof command === 'undefined') {\n command = self.get_command();\n }\n // true will return last rendered string\n var prompt = command_line.prompt(true);\n var mask = command_line.mask();\n switch (typeof mask) {\n case 'string':\n command = command.replace(/./g, mask);\n break;\n case 'boolean':\n if (mask) {\n command = command.replace(/./g, settings.maskChar);\n } else {\n command = $.terminal.escape_formatting(command);\n }\n break;\n }\n var options = {\n exec: false,\n formatters: false,\n finalize: function finalize(div) {\n a11y_hide(div.addClass('terminal-command'));\n fire_event('onEchoCommand', [div, command]);\n }\n };\n command = $.terminal.apply_formatters(command, {command: true});\n self.echo(prompt + command, options);\n }\n // ---------------------------------------------------------------------\n function have_scrollbar() {\n return fill.outerWidth() !== self.outerWidth();\n }\n // ---------------------------------------------------------------------\n // :: Helper function that restore state. Call import_view or exec\n // ---------------------------------------------------------------------\n function restore_state(spec) {\n // spec [terminal_id, state_index, command]\n var terminal = terminals.get()[spec[0]];\n if (!terminal) {\n throw new $.terminal.Exception(strings().invalidTerminalId);\n }\n var command_idx = spec[1];\n if (save_state[command_idx]) { // state exists\n terminal.import_view(save_state[command_idx]);\n } else {\n // restore state\n change_hash = false;\n var command = spec[2];\n if (command) {\n terminal.exec(command).done(function() {\n change_hash = true;\n save_state[command_idx] = terminal.export_view();\n });\n }\n }\n /*if (spec[3].length) {\n restore_state(spec[3]);\n }*/\n }\n // ---------------------------------------------------------------------\n function make_label_error(label) {\n return function(e) {\n self.error('[' + label + '] ' + (e.message || e)).resume();\n };\n }\n // ---------------------------------------------------------------------\n // :: Helper function\n // ---------------------------------------------------------------------\n function maybe_update_hash() {\n if (change_hash) {\n fire_hash_change = false;\n location.hash = '#' + JSON.stringify(hash_commands);\n setTimeout(function() {\n fire_hash_change = true;\n }, 100);\n }\n }\n // ---------------------------------------------------------------------\n // :: Wrapper over interpreter, it implements exit and catches all\n // :: exeptions from user code and displays them on the terminal\n // ---------------------------------------------------------------------\n var first_command = true;\n var resume_callbacks = [];\n function commands(command, silent, exec) {\n function init_state() {\n // execHash need first empty command too\n if (settings.historyState || settings.execHash && exec) {\n if (!save_state.length) {\n // first command in first terminal don't have hash\n self.save_state();\n } else {\n self.save_state(null);\n }\n }\n }\n // -----------------------------------------------------------------\n function before_async_exec() {\n // variables defined later in commands\n if (!exec) {\n change_hash = true;\n if (settings.historyState) {\n self.save_state(command, false);\n }\n change_hash = saved_change_hash;\n }\n }\n // -----------------------------------------------------------------\n function after_exec() {\n deferred.resolve();\n fire_event('onAfterCommand', [command]);\n }\n // -----------------------------------------------------------------\n function show(result) {\n if (typeof result !== 'undefined') {\n display_object(result);\n }\n after_exec();\n self.resume();\n }\n // -----------------------------------------------------------------\n function is_animation_promise(ret) {\n return is_function(ret.done || ret.then) && animating;\n }\n // -----------------------------------------------------------------\n function invoke() {\n // Call user interpreter function\n var result = interpreter.interpreter.call(self, command, self);\n before_async_exec();\n if (result) {\n // auto pause/resume when user return promises\n // it should not pause when user return promise from read()\n if (!force_awake) {\n if (is_animation_promise(result)) {\n paused = true;\n } else {\n self.pause(settings.softPause);\n }\n }\n force_awake = false;\n var error = make_label_error('Command');\n // when for native Promise object work only in jQuery 3.x\n if (is_function(result.done || result.then)) {\n return unpromise(result, show, error);\n } else {\n return $.when(result).done(show).catch(error);\n }\n } else {\n if (paused) {\n resume_callbacks.push(function() {\n // exec with resume/pause in user code\n after_exec();\n });\n } else {\n after_exec();\n }\n return deferred.promise();\n }\n }\n // -----------------------------------------------------------------\n // first command store state of the terminal before the command get\n // executed\n if (first_command) {\n first_command = false;\n init_state();\n }\n try {\n // this callback can disable commands\n if (fire_event('onBeforeCommand', [command]) === false) {\n return;\n }\n if (exec) {\n prev_exec_cmd = command.trim();\n prev_command = $.terminal.split_command(prev_exec_cmd);\n } else {\n prev_command = $.terminal.split_command(command);\n }\n if (!ghost()) {\n // exec execute this function wihout the help of cmd plugin\n // that add command to history on enter\n if (exec && (is_function(settings.historyFilter) &&\n settings.historyFilter(command) ||\n command.match(settings.historyFilter))) {\n command_line.history().append(command);\n }\n }\n var interpreter = interpreters.top();\n if (!silent && settings.echoCommand) {\n echo_command(command);\n }\n // new promise will be returned to exec that will resolve his\n // returned promise\n var deferred = new $.Deferred();\n // we need to save sate before commands is deleyd because\n // execute_extended_command disable it and it can be executed\n // after delay\n var saved_change_hash = change_hash;\n if (command.match(/^\\s*login\\s*$/) && self.token(true)) {\n before_async_exec();\n if (self.level() > 1) {\n self.logout(true);\n } else {\n self.logout();\n }\n after_exec();\n } else if (settings.exit && command.match(/^\\s*exit\\s*$/) &&\n !in_login) {\n before_async_exec();\n var level = self.level();\n if (level === 1 && self.get_token() || level > 1) {\n if (self.get_token(true)) {\n self.set_token(undefined, true);\n }\n self.pop();\n }\n after_exec();\n } else if (settings.clear && command.match(/^\\s*clear\\s*$/) &&\n !in_login) {\n before_async_exec();\n self.clear();\n after_exec();\n } else {\n var ret = invoke();\n if (ret) {\n return ret;\n }\n }\n return deferred.promise();\n } catch (e) {\n display_exception(e, 'USER', exec);\n self.resume();\n if (exec) {\n throw e;\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: The logout function removes Storage, disables history and runs\n // :: the login function. This function is called only when options.login\n // :: function is defined. The check for this is in the self.pop method\n // ---------------------------------------------------------------------\n function global_logout() {\n if (fire_event('onBeforeLogout', [], true) === false) {\n return;\n }\n clear_loging_storage();\n fire_event('onAfterlogout', [], true);\n self.login(global_login_fn, true, initialize);\n }\n // ---------------------------------------------------------------------\n function clear_loging_storage() {\n var name = self.prefix_name(true) + '_';\n storage.remove(name + 'token');\n storage.remove(name + 'login');\n }\n // ---------------------------------------------------------------------\n // :: Save the interpreter name for use with purge\n // ---------------------------------------------------------------------\n function maybe_append_name(interpreter_name) {\n var storage_key = self.prefix_name() + '_interpreters';\n var names = storage.get(storage_key);\n if (names) {\n names = JSON.parse(names);\n } else {\n names = [];\n }\n if ($.inArray(interpreter_name, names) === -1) {\n names.push(interpreter_name);\n storage.set(storage_key, JSON.stringify(names));\n }\n }\n // ---------------------------------------------------------------------\n // :: Function enables history, sets prompt, runs interpreter function\n // ---------------------------------------------------------------------\n function prepare_top_interpreter(silent) {\n var interpreter = interpreters.top();\n var name = self.prefix_name(true);\n if (!ghost()) {\n maybe_append_name(name);\n }\n var login = self.login_name(true);\n command_line.name(name + (login ? '_' + login : ''));\n var prompt = interpreter.prompt;\n if (is_function(prompt)) {\n prompt = context_callback_proxy(prompt);\n }\n if (prompt !== command_line.prompt()) {\n if (is_function(interpreter.prompt)) {\n // prevent flicker of old prompt until async prompt finishes\n command_line.prompt('');\n }\n command_line.prompt(interpreter.prompt);\n }\n if (typeof interpreter.history !== 'undefined') {\n self.history().toggle(interpreter.history);\n }\n if ($.isPlainObject(interpreter.keymap)) {\n command_line.keymap(null).keymap($.extend(\n {},\n terminal_init_keymap,\n $.omap(interpreter.keymap, function(name, fun) {\n return function() {\n var args = [].slice.call(arguments);\n try {\n return fun.apply(self, args);\n } catch (e) {\n display_exception(e, 'USER KEYMAP');\n }\n };\n })\n ));\n }\n command_line.set('');\n init_queue.resolve();\n if (!silent && is_function(interpreter.onStart)) {\n interpreter.onStart.call(self, self);\n }\n }\n // ---------------------------------------------------------------------\n function fire_event(name, args, skip_local) {\n args = (args || []).concat([self]); // create new array\n // even can be fired before interpreters is created\n var top = interpreters && interpreters.top();\n if (top && is_function(top[name]) && !skip_local) {\n try {\n return top[name].apply(self, args);\n } catch (e) {\n delete top[name];\n display_exception(e, name);\n }\n } else if (is_function(settings[name])) {\n try {\n return settings[name].apply(self, args);\n } catch (e) {\n settings[name] = null;\n display_exception(e, name);\n }\n }\n }\n var scroll_to_view = (function() {\n function scroll_to_view(visible) {\n if (!visible) {\n // try catch for Node.js unit tests\n try {\n self.scroll_to(self.find('.cmd-cursor-line'));\n return true;\n } catch (e) {\n return true;\n }\n }\n }\n // we don't want debounce in Unit Tests\n if (typeof global !== 'undefined' && typeof global.it === 'function') {\n return scroll_to_view;\n }\n return debounce(scroll_to_view, 100, {\n leading: true,\n trailing: false\n });\n })();\n // ---------------------------------------------------------------------\n function make_cursor_visible() {\n var cursor = self.find('.cmd-cursor-line');\n return cursor.is_fully_in_viewport(self).then(scroll_to_view);\n }\n // ---------------------------------------------------------------------\n function replace_hash(state) {\n if (typeof history !== 'undefined' && history.replaceState) {\n var new_hash = '#' + JSON.stringify(state);\n var url = location.href.replace(/#.*$/, new_hash);\n history.replaceState(null, '', url);\n }\n }\n // ---------------------------------------------------------------------\n function hashchange() {\n if (fire_hash_change && settings.execHash) {\n try {\n if (location.hash) {\n var hash = location.hash.replace(/^#/, '');\n hash_commands = JSON.parse(decodeURIComponent(hash));\n } else {\n hash_commands = [];\n }\n if (hash_commands.length) {\n restore_state(hash_commands[hash_commands.length - 1]);\n } else if (save_state[0]) {\n self.import_view(save_state[0]);\n }\n } catch (e) {\n display_exception(e, 'TERMINAL');\n }\n }\n }\n // ---------------------------------------------------------------------\n function initialize() {\n prepare_top_interpreter();\n show_greetings();\n if (lines.length) {\n // for case when showing long error before init\n if (echo_delay.length) {\n // for case when greetting is async function\n $.when.apply($, echo_delay).then(self.refresh);\n } else {\n self.refresh();\n }\n }\n function next() {\n onPause = $.noop;\n if (!was_paused && self.enabled()) {\n // resume login if user didn't call pause in onInit\n // if user pause in onInit wait with exec until it\n // resume\n self.resume(true);\n }\n }\n // was_paused flag is workaround for case when user call exec before\n // login and pause in onInit, 3rd exec will have proper timing (will\n // execute after onInit resume)\n var was_paused = false;\n if (is_function(settings.onInit)) {\n onPause = function() { // local in terminal\n was_paused = true;\n };\n var ret;\n try {\n ret = settings.onInit.call(self, self);\n } catch (e) {\n display_exception(e, 'OnInit');\n } finally {\n if (!is_promise(ret)) {\n next();\n } else {\n ret.then(next).catch(function(e) {\n display_exception(e, 'OnInit');\n next();\n });\n }\n }\n }\n if (first_instance) {\n first_instance = false;\n $(window).on('hashchange', hashchange);\n }\n }\n // ---------------------------------------------------------------------\n // :: If Ghost don't store anything in localstorage\n // ---------------------------------------------------------------------\n function ghost() {\n return in_login || command_line.mask() !== false;\n }\n // ---------------------------------------------------------------------\n // :: Keydown event handler\n // ---------------------------------------------------------------------\n function user_key_down(e) {\n var result, top = interpreters.top();\n if (is_function(top.keydown)) {\n result = top.keydown.call(self, e, self);\n if (result !== undefined) {\n return result;\n }\n } else if (is_function(settings.keydown)) {\n result = settings.keydown.call(self, e, self);\n if (result !== undefined) {\n return result;\n }\n }\n }\n var keymap = {\n 'CTRL+D': function(e, original) {\n if (!in_login) {\n if (command_line.get() === '') {\n if (interpreters.size() > 1 ||\n is_function(global_login_fn)) {\n self.pop('');\n } else {\n self.resume();\n }\n } else {\n original();\n }\n }\n return false;\n },\n 'CTRL+C': function() {\n with_selection(function(html) {\n if (html === '') {\n var command = self.get_command();\n var position = self.get_position();\n command = command.slice(0, position) + '^C' +\n command.slice(position + 2);\n echo_command(command);\n self.set_command('');\n } else {\n var clip = self.find('textarea');\n text_to_clipboard(clip, process_selected_html(html));\n }\n });\n return false;\n },\n 'CTRL+L': function() {\n self.clear();\n return false;\n },\n 'TAB': function(e, orignal) {\n // TODO: move this to cmd plugin\n // add completion = array | function\n // !!! Problem complete more then one key need terminal\n var top = interpreters.top(), completion, caseSensitive;\n if (typeof top.caseSensitiveAutocomplete !== 'undefined') {\n caseSensitive = top.caseSensitiveAutocomplete;\n } else {\n caseSensitive = settings.caseSensitiveAutocomplete;\n }\n if (settings.completion &&\n get_type(settings.completion) !== 'boolean' &&\n top.completion === undefined) {\n completion = settings.completion;\n } else {\n completion = top.completion;\n }\n if (completion === 'settings') {\n completion = settings.completion;\n }\n function resolve(commands) {\n // local copy\n commands = commands.slice();\n // default commands should not match for arguments\n if (!self.before_cursor(false).match(/\\s/)) {\n if (settings.clear && $.inArray('clear', commands) === -1) {\n commands.push('clear');\n }\n if (settings.exit && $.inArray('exit', commands) === -1) {\n commands.push('exit');\n }\n }\n self.complete(commands, {\n echo: true,\n word: settings.wordAutocomplete,\n escape: settings.completionEscape,\n caseSensitive: caseSensitive,\n echoCommand: settings.doubleTabEchoCommand,\n doubleTab: settings.doubleTab\n });\n }\n if (completion) {\n switch (get_type(completion)) {\n case 'function':\n var string = self.before_cursor(settings.wordAutocomplete);\n if (completion.length === 3) {\n var error = new Error(strings().comletionParameters);\n display_exception(error, 'USER');\n return false;\n }\n var result = completion.call(self, string, resolve);\n unpromise(result, resolve, make_label_error('Completion'));\n break;\n case 'array':\n resolve(completion);\n break;\n default:\n throw new $.terminal.Exception(strings().invalidCompletion);\n }\n } else {\n orignal();\n }\n return false;\n },\n 'CTRL+V': function(e, original) {\n original(e);\n self.oneTime(200, function() {\n self.scroll_to_bottom();\n });\n return true;\n },\n 'CTRL+TAB': function() {\n if (terminals.length() > 1) {\n self.focus(false);\n return false;\n }\n },\n 'PAGEDOWN': function() {\n self.scroll(self.height());\n },\n 'PAGEUP': function() {\n self.scroll(-self.height());\n }\n };\n // ---------------------------------------------------------------------\n function key_down(e) {\n // Prevent to be executed by cmd: CTRL+D, TAB, CTRL+TAB (if more\n // then one terminal)\n var result, i;\n if (animating) {\n return false;\n }\n if (self.enabled()) {\n if (!self.paused()) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n if (e.which !== 9) { // not a TAB\n tab_count = 0;\n }\n } else {\n if (!settings.pauseEvents) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n }\n if (e.which === 68 && e.ctrlKey) { // CTRL+D (if paused)\n if (settings.pauseEvents) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n }\n if (requests.length) {\n for (i = requests.length; i--;) {\n var r = requests[i];\n if (r.readyState !== 4) {\n try {\n r.abort();\n } catch (error) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(\n self,\n e,\n 'AJAX ABORT'\n );\n } else {\n self.error(strings().ajaxAbortError);\n }\n }\n }\n }\n requests = [];\n }\n self.resume();\n }\n return false;\n }\n }\n }\n // ---------------------------------------------------------------------\n function key_press(e) {\n var top = interpreters.top();\n if (enabled && (!paused || !settings.pauseEvents)) {\n if (is_function(top.keypress)) {\n return top.keypress.call(self, e, self);\n } else if (is_function(settings.keypress)) {\n return settings.keypress.call(self, e, self);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Typing animation generator\n // ---------------------------------------------------------------------\n function typed(finish_typing_fn) {\n return function typing_animation(message, options) {\n var formattted = $.terminal.apply_formatters(message);\n animating = true;\n var prompt = self.get_prompt();\n var char_i = 0;\n var len = $.terminal.length(formattted);\n if (message.length > 0) {\n var new_prompt = '';\n if (options.prompt) {\n new_prompt = options.prompt;\n } else {\n self.set_prompt('');\n }\n var bottom = self.is_bottom();\n var interval = setInterval(function() {\n var chr = $.terminal.substring(formattted, char_i, char_i + 1);\n new_prompt += chr;\n self.set_prompt(new_prompt);\n if (chr === '\\n' && bottom) {\n self.scroll_to_bottom();\n }\n char_i++;\n if (char_i === len) {\n clearInterval(interval);\n setTimeout(function() {\n // swap command with prompt\n finish_typing_fn(message, prompt, options);\n animating = false;\n }, options.delay);\n }\n }, options.delay);\n }\n };\n }\n // ---------------------------------------------------------------------\n var typed_prompt = typed(function(message, _, options) {\n self.set_prompt(message);\n options.finalize();\n });\n // ---------------------------------------------------------------------\n var typed_message = typed(function(message, prompt, options) {\n self.set_prompt(prompt);\n self.echo(message, $.extend({}, options, {typing: false}));\n });\n // ---------------------------------------------------------------------\n var typed_enter = (function() {\n var helper = typed(function(message, prompt, options) {\n self.set_prompt(prompt);\n with_prompt(prompt, function(prompt) {\n self.echo(prompt + message, $.extend({}, options, {typing: false}));\n }, self);\n });\n return function(prompt, message, options) {\n return helper(message, $.extend({}, options, {prompt: prompt}));\n };\n })();\n // ---------------------------------------------------------------------\n function with_typing(kind, else_fn, finalize_fn) {\n return function with_typing_fn(string, options) {\n var d = new $.Deferred();\n when_ready(function ready() {\n var locals = $.extend({\n typing: false,\n delay: settings.execAnimationDelay\n }, options);\n if (locals.typing) {\n if (typeof string !== 'string') {\n return d.reject(kind + ': Typing animation require string');\n }\n if (typeof locals.delay !== 'number' || isNaN(locals.delay)) {\n return d.reject(kind + ': Invalid argument, delay need to' +\n ' be a number');\n }\n var p = self.typing(kind, locals.delay, string, locals);\n p.then(function() {\n d.resolve();\n });\n } else {\n else_fn(string, locals);\n }\n if (is_function(finalize_fn)) {\n finalize_fn(string, locals);\n }\n });\n if (options && options.typing) {\n return d.promise();\n }\n return self;\n };\n }\n // ---------------------------------------------------------------------\n function ready(queue) {\n return function(fun) {\n queue.add(fun);\n };\n }\n // ---------------------------------------------------------------------\n function strings() {\n return $.extend(\n {},\n $.terminal.defaults.strings,\n settings && settings.strings || {}\n );\n }\n // ---------------------------------------------------------------------\n var self = this;\n if (this.length > 1) {\n return this.each(function() {\n $.fn.terminal.call(\n $(this),\n init_interpreter,\n $.extend({name: self.selector}, options)\n );\n });\n }\n var body_terminal;\n if (self.is('body,html')) {\n // terminal already exists on body\n if (self.hasClass('full-screen-terminal')) {\n var data = self.find('> .terminal').data('terminal');\n if (data) {\n return data;\n }\n }\n body_terminal = self;\n self = $('').appendTo('body');\n $('body').addClass('full-screen-terminal');\n } else if (self.data('terminal')) {\n // terminal already exists\n return self.data('terminal');\n }\n // -----------------------------------------------------------------\n // TERMINAL METHODS\n // -----------------------------------------------------------------\n var public_api = $.omap({\n id: function() {\n return terminal_id;\n },\n // -------------------------------------------------------------\n // :: Clear the output\n // -------------------------------------------------------------\n clear: function() {\n if (fire_event('onClear') !== false) {\n buffer.clear();\n lines.clear(function(i) {\n return get_node(i);\n });\n output[0].innerHTML = '';\n self.prop({scrollTop: 0});\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Return an object that can be used with import_view to\n // :: restore the state\n // -------------------------------------------------------------\n export_view: function() {\n var user_export = fire_event('onExport');\n user_export = user_export || {};\n return $.extend({}, {\n focus: enabled,\n mask: command_line.mask(),\n prompt: self.get_prompt(),\n command: self.get_command(),\n position: command_line.position(),\n lines: clone(lines.data()),\n interpreters: interpreters.clone(),\n history: command_line.history().data\n }, user_export);\n },\n // -------------------------------------------------------------\n // :: Restore the state of the previous exported view\n // -------------------------------------------------------------\n import_view: function(view) {\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'import_view'));\n }\n fire_event('onImport', [view]);\n when_ready(function ready() {\n self.set_prompt(view.prompt);\n self.set_command(view.command);\n command_line.position(view.position);\n command_line.mask(view.mask);\n if (view.focus) {\n self.focus();\n }\n lines.import(clone(view.lines).filter(function(line) {\n return line[0];\n }));\n if (view.interpreters instanceof Stack) {\n interpreters = view.interpreters;\n }\n if (settings.importHistory) {\n command_line.history().set(view.history);\n }\n redraw();\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Store current terminal state\n // -------------------------------------------------------------\n save_state: function(command, ignore_hash, index) {\n // save_state.push({view:self.export_view(), join:[]});\n if (typeof index !== 'undefined') {\n save_state[index] = self.export_view();\n } else {\n save_state.push(self.export_view());\n }\n if (!$.isArray(hash_commands)) {\n hash_commands = [];\n }\n if (command !== undefined && !ignore_hash) {\n var state = [\n terminal_id,\n save_state.length - 1,\n command\n ];\n hash_commands.push(state);\n maybe_update_hash();\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Execute a command, it will handle commands that do AJAX\n // :: calls and have delays, if the second argument is set to\n // :: true it will not echo executed command\n // :: if second argument is object is used as options\n // -------------------------------------------------------------\n exec: function(command, silent, options) {\n function invoke(silent) {\n // commands may return promise from user code\n // it will resolve exec promise when user promise\n // is resolved\n var ret = commands(command, silent, true);\n unpromise(ret, function() {\n // reset prev command for push called after exec\n // so push didn't get name/prompt from exec command\n prev_command = null;\n d.resolve();\n }, function() {\n prev_command = null;\n d.reject();\n });\n }\n if (silent && typeof silent === 'object') {\n options = silent;\n silent = null;\n }\n var exec_settings = $.extend({\n deferred: null,\n silent: false,\n typing: false,\n delay: settings.execAnimationDelay\n }, options);\n if (silent === null) {\n silent = exec_settings.silent;\n }\n if (!is_deferred(exec_settings.deferred)) {\n exec_settings.deferred = new $.Deferred();\n }\n var d = exec_settings.deferred;\n cmd_ready(function ready() {\n if ($.isArray(command)) {\n (function recur() {\n var cmd = command.shift();\n if (cmd) {\n self.exec(cmd, silent, options).done(recur);\n } else {\n d.resolve();\n }\n })();\n } else if (paused) {\n // both commands executed here (resume will call Term::exec)\n // delay command multiple time\n delayed_commands.push([command, silent, exec_settings]);\n } else if (exec_settings.typing && !silent) {\n var delay = exec_settings.delay;\n paused = true;\n var ret = self.typing('enter', delay, command, {\n delay: delay\n });\n ret.then(function() {\n paused = false;\n invoke(true);\n });\n d.then(function() {\n paused = false;\n });\n } else {\n invoke(silent);\n }\n });\n // while testing it didn't executed last exec when using this\n // for resolved deferred\n return d.promise();\n },\n // -------------------------------------------------------------\n // :: bypass login function that wait untill you type user/pass\n // :: it hide implementation detail\n // -------------------------------------------------------------\n autologin: function(user, token, silent) {\n self.trigger('terminal.autologin', [user, token, silent]);\n return self;\n },\n // -------------------------------------------------------------\n // :: Function changes the prompt of the command line to login\n // :: with a password and calls the user login function with\n // :: the callback that expects a token. The login is successful\n // :: if the user calls it with value that is truthy\n // -------------------------------------------------------------\n login: function(auth, infinite, success, error) {\n logins.push([].slice.call(arguments));\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'login'));\n }\n if (!is_function(auth)) {\n throw new Error(strings().loginIsNotAFunction);\n }\n in_login = true;\n if (self.token() && self.level() === 1 && !autologin) {\n in_login = false; // logout will call login\n self.logout(true);\n } else if (self.token(true) && self.login_name(true)) {\n in_login = false;\n if (is_function(success)) {\n success();\n }\n return self;\n }\n // don't store login data in history\n if (settings.history) {\n command_line.history().disable();\n }\n function popUserPass() {\n while (self.level() > level) {\n self.pop(undefined, true);\n }\n if (settings.history) {\n command_line.history().enable();\n }\n }\n // so we know how many times call pop\n var level = self.level();\n function login_callback(user, token, silent) {\n if (token) {\n popUserPass();\n var name = self.prefix_name(true) + '_';\n storage.set(name + 'token', token);\n storage.set(name + 'login', user);\n in_login = false;\n fire_event('onAfterLogin', [user, token]);\n if (is_function(success)) {\n // will be used internaly since users know\n // when login success (they decide when\n // it happen by calling the callback -\n // this funtion)\n success();\n }\n } else {\n if (infinite) {\n if (!silent) {\n self.error(strings().wrongPasswordTryAgain);\n }\n self.pop(undefined, true).set_mask(false);\n } else {\n in_login = false;\n if (!silent) {\n self.error(strings().wrongPassword);\n }\n self.pop(undefined, true).pop(undefined, true);\n }\n // used only to call pop in push\n if (is_function(error)) {\n error();\n }\n }\n if (self.paused()) {\n self.resume();\n }\n self.off('terminal.autologin');\n }\n self.on('terminal.autologin', function(event, user, token, silent) {\n if (fire_event('onBeforeLogin', [user, token]) === false) {\n return;\n }\n login_callback(user, token, silent);\n });\n self.push(function(user) {\n self.set_mask(settings.maskChar).push(function(pass) {\n try {\n if (fire_event('onBeforeLogin', [user, pass]) === false) {\n popUserPass();\n return;\n }\n self.pause();\n var ret = auth.call(self, user, pass, function(\n token,\n silent) {\n login_callback(user, token, silent);\n });\n if (ret && is_function(ret.then || ret.done)) {\n (ret.then || ret.done).call(ret, function(token) {\n login_callback(user, token);\n }).catch(function(err) {\n self.pop(undefined, true).pop(undefined, true);\n self.error(err.message);\n if (is_function(error)) {\n error();\n }\n if (self.paused()) {\n self.resume();\n }\n self.off('terminal.autologin');\n });\n }\n } catch (e) {\n display_exception(e, 'AUTH');\n }\n }, {\n prompt: strings().password + ': ',\n name: 'password'\n });\n }, {\n prompt: strings().login + ': ',\n name: 'login'\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: User defined settings and defaults as well\n // -------------------------------------------------------------\n settings: function() {\n return settings;\n },\n // -------------------------------------------------------------\n // :: Get string before cursor\n // -------------------------------------------------------------\n before_cursor: function(word) {\n var pos = command_line.position();\n var command = command_line.get().slice(0, pos);\n var cmd_strings = command.split(' ');\n var string; // string before cursor that will be completed\n if (word) {\n if (cmd_strings.length === 1) {\n string = cmd_strings[0];\n } else {\n var m = command.match(/(\\\\?\")/g);\n var double_quotes = m ? m.filter(function(chr) {\n return !chr.match(/^\\\\/);\n }).length : 0;\n m = command.match(/'/g);\n var single_quote = m ? m.length : 0;\n if (single_quote % 2 === 1) {\n string = command.match(/('[^']*)$/)[0];\n } else if (double_quotes % 2 === 1) {\n string = command.match(/(\"(?:[^\"]|\\\\\")*)$/)[0];\n } else {\n string = cmd_strings[cmd_strings.length - 1];\n for (i = cmd_strings.length - 1; i > 0; i--) {\n // treat escape space as part of the string\n var prev_string = cmd_strings[i - 1];\n if (prev_string[prev_string.length - 1] === '\\\\') {\n string = cmd_strings[i - 1] + ' ' + string;\n } else {\n break;\n }\n }\n }\n }\n } else {\n string = command;\n }\n return string;\n },\n // -------------------------------------------------------------\n // :: complete word or command based on array of words\n // -------------------------------------------------------------\n complete: function(commands, options) {\n options = $.extend({\n word: true,\n echo: false,\n escape: true,\n echoCommand: false,\n caseSensitive: true,\n doubleTab: null\n }, options || {});\n var sensitive = options.caseSensitive;\n // cursor can be in the middle of the command\n // so we need to get the text before the cursor\n var string = self.before_cursor(options.word).replace(/\\\\\"/g, '\"');\n var quote = false;\n if (options.word) {\n if (string.match(/^\"/)) {\n quote = '\"';\n } else if (string.match(/^'/)) {\n quote = \"'\";\n }\n if (quote) {\n string = string.replace(/^[\"']/, '');\n }\n }\n if (tab_count % 2 === 0) {\n command = self.before_cursor(options.word);\n } else {\n var test = self.before_cursor(options.word);\n if (test !== command) {\n // command line changed between TABS - ignore\n return;\n }\n }\n var safe = $.terminal.escape_regex(string);\n if (options.escape) {\n safe = safe.replace(/(\\\\+)([\"'() ])/g, function(_, slash, chr) {\n if (chr.match(/[()]/)) {\n return slash + '\\\\?\\\\' + chr;\n } else {\n return slash + '?' + chr;\n }\n });\n }\n function escape(string) {\n if (quote === '\"') {\n string = string.replace(/\"/g, '\\\\\"');\n }\n if (!quote && options.escape) {\n string = string.replace(/([\"'() ])/g, '\\\\$1');\n }\n return string;\n }\n function matched_strings() {\n var matched = [];\n for (var i = commands.length; i--;) {\n if (commands[i].match(/\\n/) && options.word) {\n warn('If you use commands with newlines you ' +\n 'should use word option for complete or' +\n ' wordAutocomplete terminal option');\n }\n if (regex.test(commands[i])) {\n var match = escape(commands[i]);\n if (!sensitive && same_case(match)) {\n if (string.toLowerCase() === string) {\n match = match.toLowerCase();\n } else if (string.toUpperCase() === string) {\n match = match.toUpperCase();\n }\n }\n matched.push(match);\n }\n }\n return matched;\n }\n var flags = sensitive ? '' : 'i';\n var regex = new RegExp('^' + safe, flags);\n var matched = matched_strings();\n function replace(input, replacement) {\n var text = self.get_command();\n var pos = self.get_position();\n var re = new RegExp('^' + input, 'i');\n var pre = text.slice(0, pos);\n var post = text.slice(pos);\n var to_insert = replacement.replace(re, '') + (quote || '');\n self.set_command(pre + to_insert + post);\n self.set_position((pre + to_insert).length);\n }\n if (matched.length === 1) {\n if (options.escape) {\n replace(safe, matched[0]);\n } else {\n self.insert(matched[0].replace(regex, '') + (quote || ''));\n }\n command = self.before_cursor(options.word);\n return true;\n } else if (matched.length > 1) {\n if (++tab_count >= 2) {\n tab_count = 0;\n if (options.echo) {\n if (is_function(options.doubleTab)) {\n // new API old is keep for backward compatibility\n if (options.echoCommand) {\n echo_command();\n }\n var ret = options.doubleTab.call(\n self,\n string,\n matched,\n echo_command\n );\n if (typeof ret === 'undefined') {\n return true;\n } else {\n return ret;\n }\n } else if (options.doubleTab !== false) {\n echo_command();\n var text = matched.slice().reverse().join('\\t\\t');\n self.echo($.terminal.escape_brackets(text), {\n keepWords: true,\n formatters: false\n });\n }\n return true;\n }\n } else {\n var common = common_string(escape(string), matched, sensitive);\n if (common) {\n replace(safe, common);\n command = self.before_cursor(options.word);\n return true;\n }\n }\n }\n },\n // -------------------------------------------------------------\n // :: Return commands function from top interpreter\n // -------------------------------------------------------------\n commands: function() {\n return interpreters.top().interpreter;\n },\n // -------------------------------------------------------------\n // :: Low Level method that overwrites interpreter\n // -------------------------------------------------------------\n set_interpreter: function(user_intrp, login) {\n var defer = $.Deferred();\n function overwrite_interpreter() {\n self.pause(settings.softPause);\n make_interpreter(user_intrp, login, function(result) {\n self.resume();\n var top = interpreters.top();\n $.extend(top, result);\n prepare_top_interpreter(true);\n defer.resolve();\n });\n }\n if (is_function(login)) {\n self.login(login, true, overwrite_interpreter);\n } else if (get_type(user_intrp) === 'string' && login) {\n self.login(\n make_json_rpc_login(user_intrp, login),\n true,\n overwrite_interpreter\n );\n } else {\n overwrite_interpreter();\n }\n return defer.promise();\n },\n // -------------------------------------------------------------\n // :: Show user greetings or terminal signature\n // -------------------------------------------------------------\n greetings: function() {\n show_greetings();\n return self;\n },\n // -------------------------------------------------------------\n // :: Return true if terminal is paused false otherwise\n // -------------------------------------------------------------\n paused: function() {\n return paused;\n },\n // -------------------------------------------------------------\n // :: Pause the terminal, it should be used for ajax calls\n // -------------------------------------------------------------\n pause: function(visible) {\n cmd_ready(function ready() {\n onPause();\n paused = true;\n command_line.disable(visible || is_android);\n if (!visible) {\n command_line.find('.cmd-prompt').hidden();\n }\n fire_event('onPause');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Resume the previously paused terminal\n // -------------------------------------------------------------\n resume: function(silent) {\n cmd_ready(function ready() {\n paused = false;\n if (enabled && terminals.front() === self) {\n command_line.enable(silent);\n }\n command_line.find('.cmd-prompt').visible();\n var original = delayed_commands;\n delayed_commands = [];\n for (var i = 0; i < original.length; ++i) {\n self.exec.apply(self, original[i]);\n }\n self.trigger('resume');\n var fn = resume_callbacks.shift();\n if (fn) {\n fn();\n }\n self.scroll_to_bottom();\n fire_event('onResume');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return the number of characters that fit into the width of\n // :: the terminal\n // -------------------------------------------------------------\n cols: function() {\n if (settings.numChars) {\n return settings.numChars;\n }\n if (!num_chars || num_chars === 1000) {\n num_chars = get_num_chars(self, char_size);\n }\n return num_chars;\n },\n // -------------------------------------------------------------\n // :: Return the number of lines that fit into the height of the\n // :: terminal\n // -------------------------------------------------------------\n rows: function() {\n if (settings.numRows) {\n return settings.numRows;\n }\n if (!num_rows) {\n num_rows = get_num_rows(self, char_size);\n }\n return num_rows;\n },\n // -------------------------------------------------------------\n // :: Return the History object\n // -------------------------------------------------------------\n history: function() {\n return command_line.history();\n },\n // -------------------------------------------------------------\n // :: Return size of the terminal instance\n // -------------------------------------------------------------\n geometry: function() {\n var style = window.getComputedStyle(self[0]);\n function padding(name) {\n return parseInt(style.getPropertyValue('padding-' + name), 10) || 0;\n }\n var left = padding('left');\n var right = padding('right');\n var top = padding('top');\n var bottom = padding('bottom');\n return {\n terminal: {\n padding: {\n left: left,\n right: right,\n top: top,\n bottom: bottom\n },\n width: old_width + left + right,\n height: old_height + top + bottom\n },\n char: char_size,\n cols: this.cols(),\n rows: this.rows()\n };\n },\n // -------------------------------------------------------------\n // :: toggle recording of history state\n // -------------------------------------------------------------\n history_state: function(toggle) {\n function run() {\n settings.historyState = true;\n if (!save_state.length) {\n self.save_state();\n } else if (terminals.length() > 1) {\n self.save_state(null);\n }\n }\n if (toggle) {\n // if set to true and if set from user command we need\n // not to include the command\n if (typeof window.setImmediate === 'undefined') {\n setTimeout(run, 0);\n } else {\n setImmediate(run);\n }\n } else {\n settings.historyState = false;\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: clear the history state\n // -------------------------------------------------------------\n clear_history_state: function() {\n hash_commands = [];\n save_state = [];\n return self;\n },\n // -------------------------------------------------------------\n // :: Switch to the next terminal\n // -------------------------------------------------------------\n next: function() {\n if (terminals.length() === 1) {\n return self;\n } else {\n terminals.front().disable();\n var next = terminals.rotate().enable();\n // 50 provides buffer in viewport\n var x = next.offset().top - 50;\n $('html,body').animate({scrollTop: x}, 500);\n try {\n trigger_terminal_change(next);\n } catch (e) {\n display_exception(e, 'onTerminalChange');\n }\n return next;\n }\n },\n // -------------------------------------------------------------\n // :: Make the terminal in focus or blur depending on the first\n // :: argument. If there is more then one terminal it will\n // :: switch to next one, if the second argument is set to true\n // :: the events will be not fired. Used on init\n // -------------------------------------------------------------\n focus: function(toggle, silent) {\n cmd_ready(function ready() {\n if (terminals.length() === 1) {\n if (toggle === false) {\n self.disable(silent);\n } else {\n self.enable(silent);\n }\n } else if (toggle === false) {\n self.next();\n } else {\n var front = terminals.front();\n if (front !== self) {\n // there should be only from terminal enabled but tests\n // sometime fail because there where more them one\n // where cursor have blink class\n terminals.forEach(function(terminal) {\n if (terminal !== self && terminal.enabled()) {\n terminal.disable(silent);\n }\n });\n if (!silent) {\n try {\n trigger_terminal_change(self);\n } catch (e) {\n display_exception(e, 'onTerminalChange');\n }\n }\n }\n terminals.set(self);\n self.enable(silent);\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Disable/Enable terminal that can be enabled by click\n // -------------------------------------------------------------\n freeze: function(freeze) {\n when_ready(function ready() {\n if (freeze) {\n self.disable();\n frozen = true;\n } else {\n frozen = false;\n self.enable();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: check if terminal is frozen\n // -------------------------------------------------------------\n frozen: function() {\n return frozen;\n },\n // -------------------------------------------------------------\n // :: Enable the terminal\n // -------------------------------------------------------------\n enable: function(silent) {\n if (!enabled && !frozen) {\n if (num_chars === undefined) {\n // enabling first time\n self.resize();\n }\n cmd_ready(function ready() {\n var ret;\n if (!silent && !enabled) {\n fire_event('onFocus');\n }\n if (!silent && ret === undefined || silent) {\n enabled = true;\n if (!self.paused()) {\n command_line.enable(true);\n }\n }\n });\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: function clear formatting cache if you don't longer need it\n // :: cache is used if option useCache is set to true\n // -------------------------------------------------------------\n clear_cache: 'Map' in root ? function() {\n buffer.clear_cache();\n line_cache.clear();\n return self;\n } : function() {\n return self;\n },\n // -------------------------------------------------------------\n // :: Disable the terminal\n // -------------------------------------------------------------\n disable: function(silent) {\n cmd_ready(function ready() {\n var ret;\n if (!silent && enabled) {\n ret = fire_event('onBlur');\n }\n if (!silent && ret === undefined || silent) {\n enabled = false;\n command_line.disable();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: return true if the terminal is enabled\n // -------------------------------------------------------------\n enabled: function() {\n return enabled;\n },\n // -------------------------------------------------------------\n // :: Return the terminal signature depending on the size of the terminal\n // -------------------------------------------------------------\n signature: function() {\n var cols = self.cols();\n for (var i = signatures.length; i--;) {\n var lengths = signatures[i].map(function(line) {\n return line.length;\n });\n if (Math.max.apply(null, lengths) <= cols) {\n return signatures[i].join('\\n') + '\\n';\n }\n }\n return '';\n },\n // -------------------------------------------------------------\n // :: Return the version number\n // -------------------------------------------------------------\n version: function() {\n return $.terminal.version;\n },\n // -------------------------------------------------------------\n // :: Return actual command line object (jquery object with cmd\n // :: methods)\n // -------------------------------------------------------------\n cmd: function() {\n return command_line;\n },\n // -------------------------------------------------------------\n // :: Return the current command entered by terminal\n // -------------------------------------------------------------\n get_command: function() {\n return command_line.get();\n },\n // -------------------------------------------------------------\n // :: better API than echo_command that supports animation\n // -------------------------------------------------------------\n enter: with_typing('enter', echo_command),\n // -------------------------------------------------------------\n // :: Change the command line to the new one\n // -------------------------------------------------------------\n set_command: function(command, silent) {\n when_ready(function ready() {\n // TODO: refactor to use options - breaking change\n if (typeof command !== 'string') {\n command = JSON.stringify(command);\n }\n command_line.set(command, undefined, silent);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Change position of the command line\n // -------------------------------------------------------------\n set_position: function(position, relative) {\n when_ready(function ready() {\n command_line.position(position, relative);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return position of the command line\n // -------------------------------------------------------------\n get_position: function() {\n return command_line.position();\n },\n // -------------------------------------------------------------\n // :: Insert text into the command line after the cursor\n // -------------------------------------------------------------\n insert: function(string, stay) {\n if (typeof string === 'string') {\n when_ready(function ready() {\n var bottom = self.is_bottom();\n command_line.insert(string, stay);\n if (settings.scrollOnEcho || bottom) {\n self.scroll_to_bottom();\n }\n });\n return self;\n } else {\n throw new Error(sprintf(strings().notAString, 'insert'));\n }\n },\n // -------------------------------------------------------------\n // :: Set the prompt of the command line\n // -------------------------------------------------------------\n set_prompt: with_typing('prompt', function(prompt) {\n if (is_function(prompt)) {\n command_line.prompt(function(callback) {\n return prompt.call(self, callback, self);\n });\n } else {\n command_line.prompt(prompt);\n }\n }, function(prompt) {\n interpreters.top().prompt = prompt;\n }),\n // -------------------------------------------------------------\n // :: Return the prompt used by the terminal\n // -------------------------------------------------------------\n get_prompt: function() {\n return interpreters.top().prompt;\n // command_line.prompt(); - can be a wrapper\n //return command_line.prompt();\n },\n // -------------------------------------------------------------\n // :: Enable or Disable mask depedning on the passed argument\n // :: the mask can also be character (in fact it will work with\n // :: strings longer then one)\n // -------------------------------------------------------------\n set_mask: function(mask) {\n when_ready(function ready() {\n command_line.mask(mask === true ? settings.maskChar : mask);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return the ouput of the terminal as text\n // :: the output may contain user terminal formatting\n // -------------------------------------------------------------\n get_output: function(raw) {\n if (raw) {\n return lines.data();\n } else {\n return lines.get_snapshot();\n }\n },\n // -------------------------------------------------------------\n // :: Recalculate and redraw everything\n // -------------------------------------------------------------\n resize: function(width, height) {\n if (!self.is(':visible')) {\n // delay resize if terminal not visible\n self.stopTime('resize');\n self.oneTime(500, 'resize', function() {\n self.resize(width, height);\n });\n } else {\n if (width && height) {\n self.width(width);\n self.height(height);\n }\n width = self.width();\n height = self.height();\n if (typeof settings.numChars !== 'undefined' ||\n typeof settings.numRows !== 'undefined') {\n command_line.resize(settings.numChars);\n self.refresh();\n fire_event('onResize');\n return;\n }\n var new_num_chars = get_num_chars(self, char_size);\n var new_num_rows = get_num_rows(self, char_size);\n // only if number of chars changed\n if (new_num_chars !== num_chars ||\n new_num_rows !== num_rows) {\n self.clear_cache();\n num_chars = new_num_chars;\n num_rows = new_num_rows;\n command_line.resize(num_chars);\n self.refresh();\n fire_event('onResize');\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: redraw the terminal and invalidate cache\n // -------------------------------------------------------------\n refresh: function() {\n if (char_size.width !== 0) {\n self[0].style.setProperty('--char-width', char_size.width);\n }\n self.clear_cache();\n if (command) {\n command_line.resize();\n }\n redraw({\n scroll: false,\n update: true\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Flush the output to the terminal\n // -------------------------------------------------------------\n flush: function(options) {\n options = $.extend({}, {\n update: false,\n scroll: true\n }, options || {});\n when_ready(function ready() {\n try {\n if (buffer.is_empty()) {\n return self;\n }\n var bottom = self.is_bottom();\n var scroll = (settings.scrollOnEcho && options.scroll) || bottom;\n var wrapper;\n // print all lines\n var first = true;\n var appending_to_partial = false;\n var partial = $();\n var snapshot;\n if (!options.update) {\n partial = self.find('.partial');\n snapshot = lines.get_partial();\n }\n // TODO: refactor buffer.flush(), there is way\n // to many levels of abstractions in one place\n buffer.flush(function(data) {\n if (!data) {\n if (!partial.length) {\n wrapper = $('');\n snapshot = [];\n } else if (first) {\n first = false;\n appending_to_partial = true;\n wrapper = partial;\n }\n } else if (is_function(data.finalize)) {\n if (scroll) {\n wrapper.find('img').on('load', function() {\n self.scroll_to_bottom();\n });\n }\n // this is finalize function from echo\n if (options.update) {\n lines.update_snapshot(data.index, snapshot);\n var selector = '> div[data-index=' + data.index + ']';\n var node = output.find(selector);\n if (node.html() !== wrapper.html()) {\n node.replaceWith(wrapper);\n }\n } else {\n wrapper.appendTo(output);\n if (!partial.length) {\n lines.make_snapshot(snapshot);\n }\n }\n wrapper.attr('data-index', data.index);\n appending_to_partial = !data.newline;\n wrapper.toggleClass('partial', appending_to_partial);\n if (appending_to_partial) {\n partial = wrapper;\n }\n data.finalize(wrapper);\n } else {\n var line = data.line;\n var div;\n if (typeof data.raw === 'string') {\n if (appending_to_partial) {\n snapshot[snapshot.length - 1] += data.raw;\n } else {\n snapshot.push(data.raw);\n }\n }\n if (appending_to_partial) {\n div = wrapper.children().last().append(line);\n appending_to_partial = false;\n } else {\n div = $('').html(line);\n if (data.newline) {\n div.addClass('cmd-end-line');\n }\n wrapper.append(div);\n }\n // width = '100%' does some weird extra magic\n // that makes the height correct. Any other\n // value doesn't work.\n div.css('width', '100%');\n }\n });\n var cmd_prompt = self.find('.cmd-prompt');\n var cmd_outer = self.find('.cmd');\n partial = self.find('.partial');\n var last_row;\n if (partial.length === 0) {\n cmd_prompt.css('margin-left', 0);\n cmd_outer.css('top', 0);\n command_line.__set_prompt_margin(0);\n last_row = self.find('.terminal-output div:last-child' +\n ' div:last-child');\n // check if the div is parital fix #695\n if (last_row.css('display') === 'inline-block') {\n last_row.css({\n width: '100%',\n display: ''\n });\n }\n } else {\n last_row = partial.children().last();\n // Remove width='100%' for two reasons:\n // 1. so we can measure the width right here\n // 2. so that the background of this last line of output\n // doesn't occlude the first line of input to the right\n last_row.css({\n width: '',\n display: 'inline-block'\n });\n var last_row_rect = last_row[0].getBoundingClientRect();\n var partial_width = last_row_rect.width;\n // Shift command prompt up one line and to the right\n // enough so that it appears directly next to the\n // partially constructed output line\n cmd_prompt.css('margin-left', partial_width);\n cmd_outer.css('top', -last_row_rect.height);\n // Measure length of partial line in characters\n var char_width = self.geometry().char.width;\n var prompt_margin = Math.round(partial_width / char_width);\n command_line.__set_prompt_margin(prompt_margin);\n }\n limit_lines();\n fire_event('onFlush');\n var cmd_cursor = self.find('.cmd-cursor');\n var offset = self.find('.cmd').offset();\n var self_offset = self.offset();\n setTimeout(function() {\n css(self[0], {\n '--terminal-height': self.height(),\n '--terminal-x': offset.left - self_offset.left,\n '--terminal-y': offset.top - self_offset.top,\n '--terminal-scroll': self.prop('scrollTop')\n });\n // Firefox won't reflow the cursor automatically, so\n // hide it briefly then reshow it\n cmd_cursor.hide();\n setTimeout(function() {\n cmd_cursor.show();\n }, 0);\n }, 0);\n if (scroll) {\n self.scroll_to_bottom();\n }\n } catch (e1) {\n if (is_function(settings.exceptionHandler)) {\n try {\n settings.exceptionHandler.call(\n self,\n e1,\n 'TERMINAL (Flush)'\n );\n } catch (e2) {\n settings.exceptionHandler = $.noop;\n alert_exception('[exceptionHandler]', e2);\n }\n } else {\n alert_exception('[Flush]', e1);\n }\n } finally {\n buffer.clear();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Update the output line - line number can be negative\n // -------------------------------------------------------------\n update: function(line, value, options) {\n when_ready(function ready() {\n if (line < 0) {\n line = lines.length() + line; // yes +\n }\n if (!lines.valid_index(line)) {\n self.error('Invalid line number ' + line);\n } else if (value === null) {\n lines.update(line, null);\n output.find('[data-index=' + line + ']').remove();\n } else {\n value = preprocess_value(value, {\n update: true,\n line: line\n });\n if (value === false) {\n return self;\n }\n unpromise(value, function(value) {\n var ret = prepare_render(value, options);\n if (ret) {\n value = ret[0];\n options = ret[1];\n }\n options = lines.update(line, value, options);\n var next = process_line({\n value: value,\n index: line,\n options: options\n });\n // process_line can return a promise\n // value is function that resolve to promise\n unpromise(next, function() {\n // trigger flush even if next is undefined\n self.flush({\n scroll: false,\n update: true\n });\n });\n });\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: convenience method for removing selected line\n // -------------------------------------------------------------\n remove_line: function(line) {\n return self.update(line, null);\n },\n // -------------------------------------------------------------\n // :: return index of last line in case when you need to update\n // :: after something is echo on the terminal\n // -------------------------------------------------------------\n last_index: function() {\n return lines.length() - 1;\n },\n // -------------------------------------------------------------\n // :: Print data to the terminal output. It can have options\n // :: * flush - indicate that arg should be send to DOM\n // :: * raw - indicate if it should handle input as html\n // :: * finalize - function call with container div\n // :: * keepWords - inform how to wrap text\n // :: * formatters - inform function if it should use formatters\n // :: on input string - good to prevent XSS when you want\n // :: advanced server side controling of terminal\n // :: you can echo: promise, function, strings array or string\n // -------------------------------------------------------------\n echo: function(arg, options) {\n var arg_defined = arguments.length > 0;\n var d = new $.Deferred();\n function echo(arg) {\n try {\n var locals = $.extend({\n flush: true,\n exec: true,\n raw: settings.raw,\n finalize: $.noop,\n unmount: $.noop,\n delay: settings.execAnimationDelay,\n ansi: false,\n typing: false,\n keepWords: false,\n invokeMethods: settings.invokeMethods,\n onClear: null,\n formatters: true,\n allowedAttributes: settings.allowedAttributes,\n newline: true\n }, options || {});\n // finalize function is passed around and invoked\n // in terminal::flush after content is added to DOM\n (function(finalize) {\n locals.finalize = function(div) {\n if (locals.raw) {\n div.addClass('raw');\n }\n if (locals.ansi) {\n div.addClass('ansi');\n }\n try {\n if (is_function(finalize)) {\n finalize.call(self, div);\n }\n var $images = div.find('img');\n $images.each(function() {\n var self = $(this);\n var img = new Image();\n img.onerror = function() {\n self.replaceWith(use_broken_image);\n };\n img.src = this.src;\n });\n } catch (e) {\n display_exception(e, 'USER:echo(finalize)');\n finalize = null;\n }\n };\n })(locals.finalize);\n if (locals.flush) {\n // flush buffer if there was no flush after previous echo\n if (!buffer.empty()) {\n self.flush();\n }\n }\n if (fire_event('onBeforeEcho', [arg]) === false) {\n return;\n }\n if (locals.typing) {\n if (typeof arg !== 'string') {\n return d.reject('echo: Typing animation require string' +\n ' or promise that resolve to string');\n }\n if (typeof locals.delay !== 'number' || isNaN(locals.delay)) {\n return d.reject('echo: Invalid argument, delay need to' +\n ' be a number');\n }\n var p = self.typing('echo', locals.delay, arg, locals);\n p.then(function() {\n d.resolve();\n });\n return;\n }\n var value;\n if (typeof arg === 'function') {\n value = arg.bind(self);\n } else if (typeof arg === 'undefined') {\n if (arg_defined) {\n value = String(arg);\n } else {\n value = '';\n }\n } else {\n var ret = preprocess_value(arg, {});\n if (ret === false) {\n return self;\n }\n value = ret;\n }\n if (is_promise(value)) {\n echo_promise = true;\n }\n unpromise(value, function(value) {\n if (render(value, locals)) {\n return self;\n }\n var index = lines.length();\n var last_newline = lines.has_newline();\n if (!last_newline) {\n index--;\n }\n if (!locals.newline && value[value.length - 1] === '\\n') {\n // This adjusts the value, so that when it updates or\n // refresh the lines list it does the right thing.\n value = value.slice(0, -1);\n locals.newline = true;\n }\n var next = process_line({\n value: value,\n options: locals,\n index: index\n });\n // queue async functions in echo\n if (is_promise(next)) {\n echo_promise = true;\n }\n lines.push([value, locals]);\n unpromise(next, function() {\n // extended commands should be processed only\n // once in echo and not on redraw\n if (locals.flush) {\n self.flush();\n fire_event('onAfterEcho', [arg]);\n }\n echo_promise = false;\n var original = echo_delay;\n echo_delay = [];\n for (var i = 0; i < original.length; ++i) {\n self.echo.apply(self, original[i]);\n }\n });\n });\n } catch (e) {\n // if echo throw exception we can't use error to\n // display that exception\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL (echo)');\n } else {\n alert_exception('[Terminal.echo]', e);\n }\n }\n }\n if (echo_promise) {\n echo_delay.push([arg, options]);\n } else {\n echo(arg);\n }\n if (options && options.typing) {\n return d.promise();\n }\n return self;\n },\n // -------------------------------------------------------------\n typing: function(type, delay, string, options) {\n var d = new $.Deferred();\n var settings;\n var finish;\n if (typeof options === 'object') {\n finish = options.finalize || $.noop;\n settings = $.extend({}, options, {\n delay: delay,\n finalize: done\n });\n } else {\n finish = options || $.noop;\n settings = {\n delay: delay,\n finalize: done\n };\n }\n function done() {\n d.resolve();\n if (is_function(finish)) {\n finish.apply(self, arguments);\n }\n }\n when_ready(function ready() {\n if (['prompt', 'echo', 'enter'].indexOf(type) >= 0) {\n if (type === 'prompt') {\n typed_prompt(string, settings);\n } else if (type === 'echo') {\n typed_message(string, settings);\n } else if (type === 'enter') {\n with_prompt(self.get_prompt(), function(prompt) {\n typed_enter(prompt, string, settings);\n }, self);\n }\n } else {\n d.reject('Invalid type only `echo` and `prompt` are supported');\n }\n });\n return d.promise();\n },\n // -------------------------------------------------------------\n // :: echo red text\n // -------------------------------------------------------------\n error: function(message, options) {\n options = $.extend({}, options, {raw: false, formatters: false});\n function format(string) {\n if (typeof string !== 'string') {\n string = String(string);\n }\n // quick hack to fix trailing backslash\n var str = $.terminal.escape_brackets(string).\n replace(/\\\\$/, '\').\n replace(url_re, ']$1[[;;;terminal-error]');\n return '[[;;;terminal-error]' + str + ']';\n }\n if (typeof message === 'function') {\n return self.echo(function() {\n return format(message.call(self));\n }, options);\n }\n if (message && message.then) {\n message.then(function(string) {\n self.echo(format(string));\n }).catch(make_label_error('Echo Error'));\n return self;\n }\n return self.echo(format(message), options);\n },\n // -------------------------------------------------------------\n // :: Display Exception on terminal\n // -------------------------------------------------------------\n exception: function(e, label) {\n var message = exception_message(e);\n if (label) {\n message = '[' + label + ']: ' + message;\n }\n if (message) {\n self.error(message, {\n finalize: function(div) {\n div.addClass('terminal-exception terminal-message');\n },\n keepWords: true\n });\n }\n if (typeof e.fileName === 'string') {\n // display filename and line which throw exeption\n self.pause(settings.softPause);\n $.get(e.fileName, function(file) {\n var num = e.lineNumber - 1;\n var line = file.split('\\n')[num];\n if (line) {\n self.error('[' + e.lineNumber + ']: ' + line);\n }\n self.resume();\n }, 'text');\n }\n if (e.stack) {\n var stack = $.terminal.escape_brackets(e.stack);\n var output = stack.split(/\\n/g).map(function(trace) {\n // nested formatting will handle urls but that formatting\n // can be removed - this code was created before\n // that formatting existed (see commit ce01c3f5)\n return '[[;;;terminal-error]' +\n trace.replace(url_re, function(url) {\n return ']' + url + '[[;;;terminal-error]';\n }) + ']';\n }).join('\\n');\n self.echo(output, {\n finalize: function(div) {\n div.addClass('terminal-exception terminal-stack-trace');\n },\n formatters: false\n });\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Scroll Div that holds the terminal\n // -------------------------------------------------------------\n scroll: function(amount) {\n var pos;\n amount = Math.round(amount);\n if (self.prop) { // work with jQuery > 1.6\n if (amount > self.prop('scrollTop') && amount > 0) {\n self.prop('scrollTop', 0);\n }\n pos = self.prop('scrollTop');\n self.scrollTop(pos + amount);\n } else {\n if (amount > self.prop('scrollTop') && amount > 0) {\n self.prop('scrollTop', 0);\n }\n pos = self.prop('scrollTop');\n self.scrollTop(pos + amount);\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Exit all interpreters and logout. The function will throw\n // :: exception if there is no login provided\n // -------------------------------------------------------------\n logout: function(local) {\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'logout'));\n }\n when_ready(function ready() {\n if (local) {\n var login = logins.pop();\n self.set_token(undefined, true);\n self.login.apply(self, login);\n } else if (interpreters.size() === 1 && self.token()) {\n self.logout(true);\n } else {\n while (interpreters.size() > 1) {\n // pop will call global_logout that will call login\n // and size will be > 0; this is workaround the problem\n if (self.token()) {\n self.logout(true).pop().pop();\n } else {\n self.pop();\n }\n }\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Function returns the token returned by callback function\n // :: in login function. It does nothing (return undefined) if\n // :: there is no login\n // -------------------------------------------------------------\n token: function(local) {\n return storage.get(self.prefix_name(local) + '_token');\n },\n // -------------------------------------------------------------\n // :: Function sets the token to the supplied value. This function\n // :: works regardless of wherer settings.login is supplied\n // -------------------------------------------------------------\n set_token: function(token, local) {\n var name = self.prefix_name(local) + '_token';\n if (typeof token === 'undefined') {\n storage.remove(name);\n } else {\n storage.set(name, token);\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Function get the token either set by the login method or\n // :: by the set_token method.\n // -------------------------------------------------------------\n get_token: function(local) {\n return self.token(local);\n },\n // -------------------------------------------------------------\n // :: Function return Login name entered by the user\n // -------------------------------------------------------------\n login_name: function(local) {\n return storage.get(self.prefix_name(local) + '_login');\n },\n // -------------------------------------------------------------\n // :: Function returns the name of current interpreter\n // -------------------------------------------------------------\n name: function() {\n return interpreters.top().name;\n },\n // -------------------------------------------------------------\n // :: Function return prefix name for login/token\n // -------------------------------------------------------------\n prefix_name: function(local) {\n var name = (settings.name ? settings.name + '_' : '') +\n terminal_id;\n if (local && interpreters.size() > 1) {\n var local_name = interpreters.map(function(intrp) {\n return intrp.name || '';\n }).slice(1).join('_');\n if (local_name) {\n name += '_' + local_name;\n }\n }\n return name;\n },\n // -------------------------------------------------------------\n // :: wrapper for common use case\n // -------------------------------------------------------------\n read: function(message, success, cancel) {\n var options;\n if (typeof arguments[1] === 'object') {\n options = $.extend({\n typing: false,\n delay: settings.execAnimationDelay,\n success: $.noop,\n cancel: $.noop\n }, arguments[1]);\n } else {\n options = {\n typing: false,\n success: success || $.noop,\n cancel: cancel || $.noop\n };\n }\n if (options.typing) {\n var prompt = self.get_prompt();\n options.typing = false;\n return self.typing('prompt', options.delay, message).then(function() {\n return self.set_prompt(prompt).read(message, options);\n });\n }\n // return from read() should not pause terminal\n force_awake = true;\n var defer = jQuery.Deferred();\n var read = false;\n self.push(function(string) {\n read = true;\n defer.resolve(string);\n if (is_function(options.success)) {\n options.success(string);\n }\n self.pop();\n if (settings.history) {\n command_line.history().enable();\n }\n }, {\n name: 'read',\n history: false,\n prompt: message || '',\n onExit: function() {\n if (!read) {\n defer.reject();\n if (is_function(options.cancel)) {\n options.cancel();\n }\n }\n }\n });\n if (settings.history) {\n command_line.history().disable();\n }\n return defer.promise();\n },\n // -------------------------------------------------------------\n // :: Push a new interenter on the Stack\n // -------------------------------------------------------------\n push: function(interpreter, options) {\n cmd_ready(function ready() {\n options = options || {};\n var defaults = {\n infiniteLogin: false\n };\n var push_settings = $.extend({}, defaults, options);\n if (!push_settings.name && prev_command) {\n // name the interpreter from last command\n push_settings.name = prev_command.name;\n }\n if (push_settings.prompt === undefined) {\n push_settings.prompt = (push_settings.name || '>') + ' ';\n }\n // names.push(options.name);\n var top = interpreters.top();\n if (top) {\n top.mask = command_line.mask();\n }\n var was_paused = paused;\n function init() {\n fire_event('onPush', [top, interpreters.top()]);\n prepare_top_interpreter();\n }\n //self.pause();\n make_interpreter(interpreter, options.login, function(ret) {\n // result is object with interpreter and completion properties\n interpreters.push($.extend({}, ret, push_settings));\n if (push_settings.completion === true) {\n if ($.isArray(ret.completion)) {\n interpreters.top().completion = ret.completion;\n } else if (!ret.completion) {\n interpreters.top().completion = false;\n }\n }\n if (push_settings.login) {\n var error;\n var type = get_type(push_settings.login);\n if (type === 'function') {\n error = push_settings.infiniteLogin ? $.noop : self.pop;\n self.login(\n push_settings.login,\n push_settings.infiniteLogin,\n init,\n error\n );\n } else if (get_type(interpreter) === 'string' &&\n type === 'string' || type === 'boolean') {\n error = push_settings.infiniteLogin ? $.noop : self.pop;\n self.login(\n make_json_rpc_login(\n interpreter,\n push_settings.login\n ),\n push_settings.infiniteLogin,\n init,\n error\n );\n }\n } else {\n init();\n }\n if (!was_paused && self.enabled()) {\n self.resume();\n }\n });\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove the last interpreter from the Stack\n // -------------------------------------------------------------\n pop: function(string, silent) {\n if (string !== undefined) {\n echo_command(string);\n }\n var token = self.token(true);\n var top;\n if (interpreters.size() === 1) {\n top = interpreters.top();\n if (settings.login) {\n if (!silent) {\n fire_event('onPop', [top, null]);\n }\n global_logout();\n fire_event('onExit');\n } else {\n self.error(strings().canExitError);\n }\n } else {\n if (token) {\n clear_loging_storage();\n }\n var current = interpreters.pop();\n top = interpreters.top();\n prepare_top_interpreter();\n // restore mask\n self.set_mask(top.mask);\n if (!silent) {\n fire_event('onPop', [current, top]);\n }\n // we check in case if you don't pop from password interpreter\n if (in_login && self.get_prompt() !== strings().login + ': ') {\n in_login = false;\n }\n if (is_function(current.onExit)) {\n try {\n current.onExit.call(self, self);\n } catch (e) {\n current.onExit = $.noop;\n display_exception(e, 'onExit');\n }\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Change terminal option(s) at runtime\n // -------------------------------------------------------------\n option: function(object_or_name, value) {\n if (typeof value === 'undefined') {\n if (typeof object_or_name === 'string') {\n return settings[object_or_name];\n } else if (typeof object_or_name === 'object') {\n $.each(object_or_name, function(key, value) {\n settings[key] = value;\n });\n }\n } else {\n settings[object_or_name] = value;\n if (object_or_name.match(/^num(Chars|Rows)$/)) {\n redraw();\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: invoke keydown shorcut\n // -------------------------------------------------------------\n invoke_key: function(shortcut) {\n command_line.invoke_key(shortcut);\n return self;\n },\n // -------------------------------------------------------------\n // :: change terminal keymap at runtime\n // -------------------------------------------------------------\n keymap: function(keymap, fn) {\n if (arguments.length === 0) {\n return command_line.keymap();\n }\n if (typeof fn === 'undefined') {\n if (typeof keymap === 'string') {\n return command_line.keymap(keymap);\n } else if ($.isPlainObject(keymap)) {\n // argument is an object\n keymap = $.extend(\n {},\n terminal_init_keymap,\n $.omap(keymap || {}, function(key, fn) {\n if (!terminal_init_keymap[key]) {\n return fn.bind(self);\n }\n return function(e, original) {\n // new keymap function will get default as 2nd arg\n return fn.call(self, e, function() {\n return terminal_init_keymap[key](e, original);\n });\n };\n })\n );\n command_line.keymap(null).keymap(keymap);\n }\n } else if (typeof fn === 'function') {\n var key = keymap;\n if (!terminal_init_keymap[key]) {\n command_line.keymap(key, fn.bind(self));\n } else {\n command_line.keymap(key, function(e, original) {\n return fn.call(self, e, function() {\n return terminal_init_keymap[key](e, original);\n });\n });\n }\n }\n },\n // -------------------------------------------------------------\n // :: Return how deep you are in nested interpreters\n // -------------------------------------------------------------\n level: function() {\n return interpreters.size();\n },\n // -------------------------------------------------------------\n // :: Reinitialize the terminal\n // -------------------------------------------------------------\n reset: function() {\n when_ready(function ready() {\n self.clear();\n while (interpreters.size() > 1) {\n interpreters.pop();\n }\n initialize();\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove all local storage left by terminal, it will not\n // :: logout you until you refresh the browser\n // -------------------------------------------------------------\n purge: function() {\n when_ready(function ready() {\n var prefix = self.prefix_name() + '_';\n var names = storage.get(prefix + 'interpreters');\n if (names) {\n $.each(JSON.parse(names), function(_, name) {\n storage.remove(name + '_commands');\n storage.remove(name + '_token');\n storage.remove(name + '_login');\n });\n }\n command_line.purge();\n storage.remove(prefix + 'interpreters');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove all events and DOM nodes left by terminal, it will\n // :: not purge the terminal so you will have the same state\n // :: when you refresh the browser\n // -------------------------------------------------------------\n destroy: function() {\n when_ready(function ready() {\n command_line.destroy().remove();\n self.resizer('unbind');\n self.touch_scroll('unbind');\n font_resizer.resizer('unbind').remove();\n $(document).unbind('.terminal_' + self.id());\n $(window).unbind('.terminal_' + self.id());\n self.unbind('click wheel mousewheel mousedown mouseup');\n self.removeData('terminal').removeClass('terminal').\n unbind('.terminal');\n if (settings.width) {\n self.css('width', '');\n }\n if (settings.height) {\n self.css('height', '');\n }\n $(window).off('blur', blur_terminal).\n off('focus', focus_terminal);\n self.find('.terminal-fill, .terminal-font').remove();\n self.stopTime();\n terminals.remove(terminal_id);\n if (visibility_observer) {\n if (visibility_observer.unobserve) {\n visibility_observer.unobserve(self[0]);\n } else {\n clearInterval(visibility_observer);\n }\n }\n var scroll_marker = self.find('.terminal-scroll-marker');\n if (is_bottom_observer) {\n is_bottom_observer.unobserve(scroll_marker[0]);\n }\n scroll_marker.remove();\n if (mutation_observer) {\n mutation_observer.disconnect();\n }\n if (!terminals.length()) {\n $(window).off('hashchange');\n }\n if (is_mobile) {\n self.off([\n 'touchstart.terminal',\n 'touchmove.terminal',\n 'touchend.terminal'\n ].join(' '));\n }\n output.remove();\n wrapper.remove();\n if (body_terminal) {\n var $body = $(body_terminal);\n if ($body.attr('class') === 'full-screen-terminal') {\n $body.removeAttr('class');\n } else {\n $body.removeClass('full-screen-terminal');\n }\n self.remove();\n }\n defunct = true;\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: ref: https://stackoverflow.com/a/18927969/387194\n // -------------------------------------------------------------\n scroll_to: function(elem) {\n var scroll = self.scrollTop() - self.offset().top + $(elem).offset().top;\n self.scrollTop(scroll);\n return self;\n },\n // -------------------------------------------------------------\n scroll_to_bottom: function() {\n var scrollHeight;\n if (self.prop) {\n scrollHeight = self.prop('scrollHeight');\n } else {\n scrollHeight = self.attr('scrollHeight');\n }\n self.scrollTop(scrollHeight);\n return self;\n },\n // -------------------------------------------------------------\n // :: return true if terminal div or body is at the bottom\n // :: is use scrollBottomOffset option as margin for the check\n // -------------------------------------------------------------\n is_bottom: function() {\n if (settings.scrollBottomOffset === -1) {\n return false;\n } else if (typeof is_bottom_detected === 'boolean') {\n return is_bottom_detected;\n } else {\n var scroll_height, scroll_top, height;\n scroll_height = self[0].scrollHeight;\n scroll_top = self[0].scrollTop;\n height = self[0].offsetHeight;\n var limit = scroll_height - settings.scrollBottomOffset;\n return scroll_top + height > limit;\n }\n },\n // -------------------------------------------------------------\n // :: create terminal object clone, used by pipe\n // -------------------------------------------------------------\n duplicate: function() {\n var copy = $(self);\n return $.extend(copy, public_api);\n },\n // -------------------------------------------------------------\n // :: return output flush buffer\n // -------------------------------------------------------------\n get_output_buffer: function(options) {\n var settings = $.extend({\n html: false\n }, options);\n var result = [];\n var append = false;\n buffer.forEach(function(data) {\n if (data) {\n if (is_function(data.finalize)) {\n append = !data.newline;\n } else {\n var output;\n if (settings.html) {\n output = data.line;\n } else {\n output = data.raw;\n }\n if (append) {\n var last = result.length - 1;\n result[last] += output;\n } else {\n result.push(output);\n }\n }\n }\n });\n if (settings.html) {\n return result.map(function(line) {\n return '
' + line + '
';\n }).join('\\n');\n }\n return result.join('\\n');\n },\n // -------------------------------------------------------------\n // :: clear flush buffer\n // -------------------------------------------------------------\n clear_buffer: function() {\n buffer.clear();\n return self;\n }\n }, function(name, fun) {\n // wrap all functions and display execptions\n return function() {\n if (defunct) {\n if (!settings.exceptionHandler) {\n throw new $.terminal.Exception(strings().defunctTerminal);\n }\n }\n try {\n return fun.apply(self, [].slice.apply(arguments));\n } catch (e) {\n // exec catch by command (resume call exec)\n if (name !== 'exec' && name !== 'resume') {\n display_exception(e, e.type || 'TERMINAL', true);\n }\n if (!settings.exceptionHandler) {\n throw e;\n }\n }\n };\n });\n $.extend(self, public_api);\n // -----------------------------------------------------------------\n // :: INIT CODE\n // -----------------------------------------------------------------\n if (self.length === 0) {\n var msg = sprintf(strings().invalidSelector);\n throw new $.terminal.Exception(msg);\n }\n self.data('terminal', self);\n // synchronize the echo calls (used for async functions) that need\n // to be called in order\n var echo_delay = [];\n var echo_promise = false;\n // var names = []; // stack if interpreter names\n var prev_command; // used for name on the terminal if not defined\n var prev_exec_cmd;\n var tab_count = 0; // for tab completion\n var output; // .terminal-output jquery object\n var terminal_id = terminals.length();\n var force_awake = false; // flag used to don't pause when user return read() call\n var num_chars; // numer of chars in line\n var num_rows; // number of lines that fit without scrollbar\n var command; // for tab completion\n var logins = new Stack(); // stack of logins\n var command_queue = new DelayQueue();\n var animating = false; // true on typing animation\n var init_queue = new DelayQueue();\n var when_ready = ready(init_queue);\n var cmd_ready = ready(command_queue);\n var is_bottom_detected;\n var is_bottom_observer;\n var in_login = false;// some Methods should not be called when login\n // TODO: Try to use mutex like counter for pause/resume\n var onPause = $.noop;// used to indicate that user call pause onInit\n var old_width, old_height;\n var delayed_commands = []; // used when exec commands while paused\n var settings = $.extend(\n {},\n $.terminal.defaults,\n {\n name: self.selector,\n exit: !!(options && options.login || !options)\n },\n options || {}\n );\n if (typeof settings.width === 'number') {\n self.width(settings.width);\n }\n if (typeof settings.height === 'number') {\n self.height(settings.height);\n }\n var char_size = get_char_size(self);\n // this is needed when terminal have selector with --size that is not\n // bare .terminal so fake terminal will not get the proper size #602\n var need_char_size_recalculate = !terminal_ready(self);\n // so it's the same as in TypeScript definition for options\n delete settings.formatters;\n // used to throw error when calling methods on destroyed terminal\n var defunct = false;\n // ---------------------------------------------------------------------\n // :: FormatBuffer is used to to buffer the lines that echoed\n // :: it have append function that have 2 options raw and finalize\n // :: raw - will not encode the string and finalize if a function that\n // :: will have div container of the line as first argument\n // :: actuall echo to the terminal happen when calling flush\n // ---------------------------------------------------------------------\n var buffer = new FormatBuffer(function(options) {\n return {\n linksNoReferrer: settings.linksNoReferrer,\n linksNoFollow: settings.linksNoFollow,\n anyLinks: settings.anyLinks,\n charWidth: char_size.width,\n useCache: settings.useCache,\n escape: false,\n allowedAttributes: options.allowedAttributes || []\n };\n });\n var lines = new OutputLines(function() {\n return settings;\n });\n var storage = new StorageHelper(settings.memory);\n var enabled = settings.enabled;\n var frozen = false;\n var paused = false;\n var autologin = true; // set to false if onBeforeLogin return false\n var interpreters;\n var command_line;\n var old_enabled;\n var visibility_observer;\n var mutation_observer;\n // backward compatibility\n if (settings.ignoreSystemDescribe === true) {\n settings.describe = false;\n }\n // register ajaxSend for cancel requests on CTRL+D\n $(document).bind('ajaxSend.terminal_' + self.id(), function(e, xhr) {\n requests.push(xhr);\n });\n var wrapper = $('
').appendTo(self);\n $(broken_image).hide().appendTo(wrapper);\n var font_resizer = $('
').appendTo(self);\n var fill = $('
').appendTo(self);\n output = $('
').addClass('terminal-output').attr('role', 'log')\n .appendTo(wrapper);\n self.addClass('terminal');\n // before login event\n if (settings.login && fire_event('onBeforeLogin') === false) {\n autologin = false;\n }\n // create json-rpc authentication function\n var base_interpreter;\n if (typeof init_interpreter === 'string') {\n base_interpreter = init_interpreter;\n } else if (is_array(init_interpreter)) {\n // first JSON-RPC\n for (var i = 0, len = init_interpreter.length; i < len; ++i) {\n if (typeof init_interpreter[i] === 'string') {\n base_interpreter = init_interpreter[i];\n break;\n }\n }\n }\n var global_login_fn;\n if (is_function(settings.login)) {\n global_login_fn = settings.login;\n } else if (base_interpreter &&\n (typeof settings.login === 'string' || settings.login === true)) {\n global_login_fn = make_json_rpc_login(base_interpreter, settings.login);\n }\n terminals.append(self);\n function focus_terminal() {\n if (old_enabled) {\n self.focus();\n self.scroll_to_bottom();\n }\n }\n // -------------------------------------------------------------------------------\n function blur_terminal() {\n old_enabled = enabled;\n self.disable().find('.cmd textarea').trigger('blur', [true]);\n }\n // -------------------------------------------------------------------------------\n function stringify_value(value) {\n if (is_function(value)) {\n value = value();\n }\n if (value && is_function(value.then)) {\n return value.then(stringify_value);\n }\n if (get_type(value) !== 'string') {\n if (is_function(settings.parseObject)) {\n var ret = settings.parseObject(value);\n if (get_type(ret) === 'string') {\n value = ret;\n }\n } else if (is_array(value)) {\n value = $.terminal.columns(value, self.cols(), settings.tabs);\n } else {\n value = String(value);\n }\n }\n return value;\n }\n // -------------------------------------------------------------------------------\n function context_callback_proxy(fn) {\n if (fn.proxy) {\n return fn;\n }\n var wrapper = function(callback) {\n return fn.call(self, callback, self);\n };\n wrapper.proxy = true;\n return wrapper;\n }\n // -------------------------------------------------------------------------------\n // paste event is not testable in node\n // istanbul ignore next\n function paste_event(e) {\n e = e.originalEvent;\n // we don't care about browser that don't support clipboard data\n // those browser simple will not have this feature normal paste\n // is cross-browser and it's handled by cmd plugin\n function is_type(item, type) {\n return item.type.indexOf(type) !== -1;\n }\n function echo_image(image) {\n self.echo('', {raw: true});\n }\n function data_uri(blob) {\n var URL = window.URL || window.webkitURL;\n return URL.createObjectURL(blob);\n }\n function echo(object, ignoreEvents) {\n if (!ignoreEvents) {\n var event = {\n target: self\n };\n if (typeof object === 'string') {\n event.text = object;\n } else if (object instanceof Blob) {\n event.image = data_uri(object);\n }\n var ret = fire_event('onPaste', [event]);\n if (ret) {\n if (is_function(ret.then || ret.done)) {\n return (ret.then || ret.done).call(ret, function(ret) {\n echo(ret, true);\n });\n } else {\n echo(ret, true);\n }\n } else if (ret !== false) {\n echo(event.image || event.text, true);\n }\n } else if (object instanceof Blob) {\n echo_image(data_uri(object));\n } else if (typeof object === 'string') {\n if (object.match(/^(data:|blob:)/)) {\n echo_image(object);\n } else {\n self.insert(object);\n }\n }\n }\n if (e.clipboardData) {\n if (self.enabled()) {\n var items = e.clipboardData.items;\n if (items) {\n for (var i = 0; i < items.length; i++) {\n if (is_type(items[i], 'image') && settings.pasteImage) {\n var blob = items[i].getAsFile();\n echo(blob);\n } else if (is_type(items[i], 'text/plain')) {\n items[i].getAsString(function(text) {\n echo(text.replace(/\\r/g, ''));\n });\n }\n }\n } else if (e.clipboardData.getData) {\n var text = e.clipboardData.getData('text/plain');\n echo(text.replace(/\\r/g, ''));\n }\n return false;\n }\n }\n }\n $(document).on('paste.terminal_' + self.id(), paste_event);\n var terminal_init_keymap = $.extend(\n {},\n keymap,\n $.omap(settings.keymap || {}, function(key, fn) {\n if (!keymap[key]) {\n return fn.bind(self);\n }\n return function(e, original) {\n // new keymap function will get default as 2nd argument\n return fn.call(self, e, function() {\n return keymap[key](e, original);\n });\n };\n })\n );\n make_interpreter(init_interpreter, settings.login, function(interpreter) {\n if (settings.completion && typeof settings.completion !== 'boolean' ||\n !settings.completion) {\n // overwrite interpreter completion by global setting #224\n // we use string to indicate that it need to be taken from settings\n // so we are able to change it using option API method\n interpreter.completion = 'settings';\n }\n var prompt = settings.prompt;\n if (is_function(prompt)) {\n prompt = context_callback_proxy(prompt);\n }\n interpreters = new Stack($.extend({}, settings.extra, {\n name: settings.name,\n prompt: prompt,\n keypress: settings.keypress,\n keydown: settings.keydown,\n resize: settings.onResize,\n greetings: settings.greetings,\n mousewheel: settings.mousewheel,\n history: settings.history,\n keymap: terminal_init_keymap\n }, interpreter));\n // CREATE COMMAND LINE\n command_line = $('').appendTo(wrapper).cmd({\n tabindex: settings.tabindex,\n mobileDelete: settings.mobileDelete,\n mobileIngoreAutoSpace: settings.mobileIngoreAutoSpace,\n prompt: global_login_fn ? false : prompt,\n history: settings.memory ? 'memory' : settings.history,\n historyFilter: settings.historyFilter,\n historySize: settings.historySize,\n caseSensitiveSearch: settings.caseSensitiveSearch,\n onPaste: settings.onPaste,\n width: '100%',\n enabled: false,\n charWidth: char_size.width,\n keydown: key_down,\n keymap: terminal_init_keymap,\n clickTimeout: settings.clickTimeout,\n holdTimeout: settings.holdTimeout,\n holdRepeatTimeout: settings.holdRepeatTimeout,\n repeatTimeoutKeys: settings.repeatTimeoutKeys,\n allowedAttributes: settings.allowedAttributes,\n keypress: key_press,\n tabs: settings.tabs,\n onPositionChange: function() {\n var args = [].slice.call(arguments);\n make_cursor_visible();\n fire_event('onPositionChange', args);\n },\n onCommandChange: function(command) {\n // resize is not triggered when insert called just after init\n // and scrollbar appear\n if (old_width !== fill.width()) {\n // resizer handler will update old_width\n self.resizer();\n }\n fire_event('onCommandChange', [command]);\n make_cursor_visible();\n },\n commands: commands\n });\n function disable(e) {\n if (is_mobile) {\n return;\n }\n e = e.originalEvent;\n if (e) {\n // e.terget is body when click outside of context menu to close it\n // even if you click on terminal\n var node = document.elementFromPoint(e.clientX, e.clientY);\n if (!$(node).closest('.terminal').length && self.enabled()) {\n // we only need to disable when click outside of terminal\n // click on other terminal is handled by focus event\n self.disable();\n }\n }\n }\n self.oneTime(100, function() {\n $(document).bind('click.terminal_' + self.id(), disable).\n bind('contextmenu.terminal_' + self.id(), disable);\n });\n var $win = $(window);\n // cordova application, if keyboard was open and we resume, it will be\n // closed so we need to disable terminal so you can enable it with tap\n document.addEventListener(\"resume\", function() {\n self.disable();\n });\n // istanbul ignore next\n if (is_mobile) {\n (function() {\n self.addClass('terminal-mobile');\n var start;\n var move;\n var enabled;\n var SENSITIVITY = 3;\n var clip = command_line.clip();\n var HOLD_TIME = 200;\n var timer;\n clip.$node.off('touchstart.cmd');\n self.on('touchstart.terminal', function(e) {\n e = e.originalEvent;\n window.touch_event = e;\n if (e.target.tagName.toLowerCase() === 'a') {\n return;\n }\n if (!frozen && e.touches.length === 1) {\n enabled = self.enabled();\n var point = e.touches[0];\n start = {\n x: point.clientX,\n y: point.clientY\n };\n timer = setTimeout(function() {\n start = null;\n }, HOLD_TIME);\n }\n }).on('touchmove.terminal', function(e) {\n if (e.touches.length === 1 && start) {\n var point = e.touches[0];\n var diff_x = Math.abs(point.clientX - start.x);\n var diff_y = Math.abs(point.clientY - start.y);\n if (diff_x > SENSITIVITY || diff_y > SENSITIVITY) {\n move = true;\n }\n }\n }).on('touchend.terminal', function() {\n if (start) {\n clearTimeout(timer);\n if (!move) {\n if (!enabled) {\n clip.focus();\n self.focus();\n } else {\n clip.blur();\n self.disable();\n }\n }\n }\n move = false;\n start = null;\n });\n })();\n } else {\n // work weird on mobile\n $win.on('focus.terminal_' + self.id(), focus_terminal).\n on('blur.terminal_' + self.id(), blur_terminal);\n // context is used to check if terminal should not scroll to bottom\n // after right click on e.g. img, canvas, a and then click to hide\n // the menu. The problem is that right click on those elements don't\n // move the textarea to show proper context menu like save as on images\n // or open on links. See #644 bug\n var was_ctx_event;\n // detect mouse drag\n (function() {\n var count = 0;\n var $target;\n var name = 'click_' + self.id();\n var textarea = self.find('.cmd textarea');\n function click() {\n if ($target.is('.terminal') ||\n $target.is('.terminal-wrapper')) {\n var len = self.get_command().length;\n self.set_position(len);\n } else if ($target.closest('.cmd-prompt').length) {\n self.set_position(0);\n }\n if (!textarea.is(':focus')) {\n textarea.focus();\n }\n reset();\n }\n function reset() {\n count = 0;\n $target = null;\n }\n var ignore_elements = '.terminal-output textarea,' +\n '.terminal-output input';\n self.mousedown(function(e) {\n if (!scrollbar_event(e, fill)) {\n $target = $(e.target);\n }\n }).mouseup(function() {\n if (was_ctx_event) {\n was_ctx_event = false;\n return;\n }\n if ($target && $target.closest(ignore_elements).length) {\n if (enabled) {\n self.disable();\n }\n } else if (get_selected_html() === '' && $target) {\n if (++count === 1) {\n if (!frozen) {\n if (!enabled) {\n self.focus();\n self.scroll_to_bottom();\n } else {\n var timeout = settings.clickTimeout;\n self.oneTime(timeout, name, click);\n return;\n }\n }\n } else {\n self.stopTime(name);\n }\n }\n reset();\n }).dblclick(function() {\n reset();\n self.stopTime(name);\n });\n })();\n (function() {\n var $clip = command_line.clip().$node;\n function is_context_event(e) {\n return e.type === 'mousedown' && e.buttons === 2 ||\n e.type === 'contextmenu';\n }\n var event_name;\n if ('oncontextmenu' in window) {\n event_name = 'contextmenu.terminal';\n } else {\n event_name = 'mousedown.terminal';\n }\n self.on(event_name, function(e) {\n was_ctx_event = get_selected_html() === '' && is_context_event(e);\n if (was_ctx_event) {\n var $target = $(e.target);\n if ($target.is('img,value,audio,object,canvas,a')) {\n return;\n }\n if (!self.enabled()) {\n self.enable();\n }\n var cmd_offset = command_line.offset();\n var cmd_rect = command_line[0].getBoundingClientRect();\n var self_offset = self.offset();\n var top_limit = self_offset.top - cmd_offset.top;\n var top = Math.max(e.pageY - cmd_offset.top - 20, top_limit);\n var left = e.pageX - cmd_offset.left - 20;\n var height = 4 * 14;\n var width = 5 * 14;\n var rect = self[0].getBoundingClientRect();\n // we need width without scrollbar\n var content_width = fill.outerWidth();\n // fix jumping when click near bottom or left edge #592\n var diff_h = (top + cmd_rect.top + height);\n diff_h = diff_h - rect.height - rect.top;\n var diff_w = (left + cmd_rect.left + width);\n // in Chrome scrollbar is added even when width\n // of textarea is smaller, adding 1px solved the issue\n diff_w = diff_w - content_width - rect.left;\n if (diff_h > 0) {\n height -= Math.ceil(diff_h);\n }\n if (diff_w > 0) {\n width -= Math.ceil(diff_w);\n }\n $clip.attr('style', [\n 'left:' + left + 'px !important',\n 'top:' + top + 'px !important',\n 'width:' + width + 'px !important',\n 'height:' + height + 'px !important'\n ].join(';'));\n if (!$clip.is(':focus')) {\n $clip.focus();\n }\n self.stopTime('textarea');\n self.oneTime(100, 'textarea', function() {\n var props = {\n left: '',\n top: '',\n width: '',\n height: ''\n };\n if (!is_css_variables_supported) {\n var in_line = self.find('.cmd .cmd-cursor-line')\n .prevUntil('.cmd-prompt').length;\n props.top = in_line * 14 + 'px';\n }\n $clip.css(props);\n });\n self.stopTime('selection');\n self.everyTime(20, 'selection', function() {\n if ($clip[0].selection !== $clip[0].value) {\n if (get_textarea_selection($clip[0])) {\n clear_textarea_selection($clip[0]);\n select(\n self.find('.terminal-output')[0],\n self.find('.cmd div:last-of-type')[0]\n );\n self.stopTime('selection');\n }\n }\n });\n }\n });\n })();\n self.on('scroll', function() {\n var $textarea = self.find('textarea');\n var rect = self[0].getBoundingClientRect();\n var height = self[0].scrollHeight;\n var scrollTop = self.scrollTop();\n var diff = height - (scrollTop + rect.height);\n // if scrolled to bottom top need to be aligned with cursor line\n // done by CSS file using css variables\n if (diff === 0) {\n $textarea.css('top', '');\n } else {\n $textarea.css('top', -diff);\n }\n });\n }\n self.on('click', 'a', function(e) {\n var $this = $(this);\n if ($this.closest('.terminal-exception').length) {\n var href = $this.attr('href');\n if (href.match(/:[0-9]+$/)) { // display line if specified\n e.preventDefault();\n print_line(href, self.cols());\n }\n }\n // refocus because links have tabindex in case where user want\n // tab change urls, we can ignore this function on click\n if (enabled) {\n self.find('.cmd textarea').focus();\n }\n });\n function calculate_char_size() {\n var width = char_size.width;\n char_size = get_char_size(self);\n if (width !== char_size.width) {\n command_line.option('charWidth', char_size.width).refresh();\n }\n }\n resize();\n function resize() {\n if (self.is(':visible')) {\n var width = fill.width();\n var height = fill.height();\n if (need_char_size_recalculate) {\n need_char_size_recalculate = !terminal_ready(self);\n calculate_char_size();\n }\n // prevent too many calculations in IE\n if (old_height !== height || old_width !== width) {\n self.resize();\n }\n old_height = height;\n old_width = width;\n }\n }\n function create_resizers() {\n var options = {\n prefix: 'terminal-'\n };\n self.resizer('unbind').resizer(resize, options);\n font_resizer.resizer('unbind').resizer(function() {\n calculate_char_size();\n self.resize();\n }, options);\n }\n function bottom_detect(intersections) {\n is_bottom_detected = intersections[0].intersectionRatio === 1;\n }\n function create_bottom_detect() {\n if (window.IntersectionObserver) {\n var top = $('
');\n if (html.match(/<\\/div>/)) {\n // match multiple echo output\n stdout = $html.find('div[data-index]').map(function() {\n return process_div(this);\n }).get().join('\\n');\n // match inside single echo output\n if (!stdout && html.match(/style=\"width: 100%;?\"/)) {\n stdout = process_div($html);\n }\n text = stdout;\n }\n var $prompt = $html.find('.cmd-prompt');\n if ($prompt.length) {\n if (text.length) {\n text += '\\n';\n }\n text += $prompt.text();\n }\n var $cmd_lines = $html.find('[role=\"presentation\"]');\n if ($cmd_lines.length) {\n text += $cmd_lines.map(process_selected_line).get().join('');\n }\n if (!text.length && html) {\n text = $html.text();\n }\n return text.replace(/\\xA0/g, ' '); // fix space\n }\n // ---------------------------------------------------------------------\n // :: copy given DOM element text to clipboard\n // ---------------------------------------------------------------------\n var support_copy = (function() {\n if (typeof document === 'undefined') {\n return false;\n }\n if (!is_function(document.queryCommandSupported)) {\n return false;\n }\n return document.queryCommandSupported('copy');\n })();\n // ---------------------------------------------------------------------\n var text_to_clipboard;\n if (support_copy) {\n text_to_clipboard = function text_to_clipboard($textarea, text) {\n var val = $textarea.val();\n var had_focus = $textarea.is(':focus');\n var pos = $textarea.caret();\n if (window.navigator && window.navigator.clipboard) {\n navigator.clipboard.writeText(text);\n } else if (had_focus) {\n $textarea.val(text).focus();\n $textarea[0].select();\n document.execCommand('copy');\n $textarea.val(val);\n $textarea.caret(pos);\n } else {\n var $text = $('
').css({\n position: 'fixed',\n top: 0,\n left: 0\n }).appendTo('body');\n $text.val(text).focus();\n $text[0].select();\n document.execCommand('copy');\n $text.blur();\n $text.remove();\n }\n return true;\n };\n } else {\n text_to_clipboard = $.noop;\n }\n // ---------------------------------------------------------------------\n var get_textarea_selection = (function() {\n function noop() {\n return '';\n }\n if (typeof document === 'undefined') {\n return noop;\n }\n var textarea = document.createElement('textarea');\n var selectionStart = 'selectionStart' in textarea;\n textarea = null;\n if (selectionStart) {\n return function(textarea) {\n var length = textarea.selectionEnd - textarea.selectionStart;\n return textarea.value.substr(textarea.selectionStart, length);\n };\n } else if (document.selection) {\n return function() {\n var range = document.selection.createRange();\n return range.text();\n };\n } else {\n return noop;\n }\n })();\n // ---------------------------------------------------------------------\n function clear_textarea_selection(textarea) {\n textarea.selectionStart = textarea.selectionEnd = 0;\n }\n // ---------------------------------------------------------------------\n // :: return string that are common in all elements of the array\n // ---------------------------------------------------------------------\n function common_string(string, array, matchCase) {\n if (!array.length) {\n return '';\n }\n var type = string_case(string);\n var result = [];\n for (var j = string.length; j < array[0].length; ++j) {\n var push = false;\n var candidate = array[0].charAt(j),\n candidateLower = candidate.toLowerCase();\n for (var i = 1; i < array.length; ++i) {\n push = true;\n var current = array[i].charAt(j),\n currentLower = current.toLowerCase();\n if (candidate !== current) {\n if (matchCase || type === 'mixed') {\n push = false;\n break;\n } else if (candidateLower === currentLower) {\n if (type === 'lower') {\n candidate = candidate.toLowerCase();\n } else if (type === 'upper') {\n candidate = candidate.toUpperCase();\n } else {\n push = false;\n break;\n }\n } else {\n push = false;\n break;\n }\n }\n }\n if (push) {\n result.push(candidate);\n } else {\n break;\n }\n }\n return string + result.join('');\n }\n // ---------------------------------------------------------------------\n function trigger_terminal_change(next) {\n terminals.forEach(function(term) {\n term.settings().onTerminalChange.call(term, next);\n });\n }\n // ---------------------------------------------------------------------\n var select = (function() {\n if (root.getSelection) {\n var selection = root.getSelection();\n if (selection.setBaseAndExtent) {\n return function(start, end) {\n var selection = root.getSelection();\n selection.setBaseAndExtent(start, 0, end, 1);\n };\n } else {\n return function(start, end) {\n var selection = root.getSelection();\n var range = document.createRange();\n range.setStart(start, 0);\n range.setEnd(end, end.childNodes.length);\n selection.removeAllRanges();\n selection.addRange(range);\n };\n }\n } else {\n return $.noop;\n }\n })();\n // -------------------------------------------------------------------------\n function process_command(original, fn) {\n var string = original.trim();\n var array = string.match(command_re) || [];\n if (array.length) {\n var name = array.shift();\n var args = $.map(array, function(arg) {\n if (arg.match(/^[\"']/)) {\n arg = arg.replace(/\\n/g, '\\\\u0000\\\\u0000\\\\u0000\\\\u0000');\n arg = fn(arg);\n return arg.replace(/\\x00\\x00\\x00\\x00/g, '\\n');\n }\n return fn(arg);\n });\n var quotes = $.map(array, function(arg) {\n var m = arg.match(/^(['\"`]).*\\1$/);\n return m && m[1] || '';\n });\n var rest = string.slice(name.length).trim();\n return {\n command: original,\n name: name,\n args: args,\n args_quotes: quotes,\n rest: rest\n };\n } else {\n return {\n command: original,\n name: '',\n args: [],\n args_quotes: [],\n rest: ''\n };\n }\n }\n // -------------------------------------------------------------------------\n $.terminal = {\n version: '2.31.0',\n date: 'Thu, 30 Dec 2021 10:53:02 +0000',\n // colors from https://www.w3.org/wiki/CSS/Properties/color/keywords\n color_names: [\n 'transparent', 'currentcolor', 'black', 'silver', 'gray', 'white',\n 'maroon', 'red', 'purple', 'fuchsia', 'green', 'lime', 'olive',\n 'yellow', 'navy', 'blue', 'teal', 'aqua', 'aliceblue',\n 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque',\n 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown',\n 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral',\n 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue',\n 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey',\n 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange',\n 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen',\n 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise',\n 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',\n 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',\n 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green',\n 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo',\n 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen',\n 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',\n 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey',\n 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue',\n 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',\n 'lime', 'limegreen', 'linen', 'magenta', 'maroon',\n 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',\n 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',\n 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream',\n 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive',\n 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod',\n 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',\n 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',\n 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown',\n 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue',\n 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan',\n 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat',\n 'white', 'whitesmoke', 'yellow', 'yellowgreen', 'rebeccapurple'],\n // for unit tests\n Cycle: Cycle,\n History: History,\n Stack: Stack,\n // ---------------------------------------------------------------------\n // :: Validate html color (it can be name or hex)\n // ---------------------------------------------------------------------\n valid_color: function valid_color(color) {\n if (color.match(color_re)) {\n return true;\n } else {\n return $.inArray(color.toLowerCase(), $.terminal.color_names) !== -1;\n }\n },\n // ---------------------------------------------------------------------\n // :: function check if given string contain invalid strings\n // ---------------------------------------------------------------------\n unclosed_strings: function unclosed_strings(string) {\n return !!string.match(unclosed_strings_re);\n },\n // ---------------------------------------------------------------------\n // :: Escape all special regex characters, so it can be use as regex to\n // :: match exact string that contain those characters\n // ---------------------------------------------------------------------\n escape_regex: function escape_regex(str) {\n if (typeof str === 'string') {\n var special = /([-\\\\^$[\\]()+{}?*.|])/g;\n return str.replace(special, '\\\\$1');\n }\n },\n // ---------------------------------------------------------------------\n // :: test if string contain formatting\n // ---------------------------------------------------------------------\n have_formatting: function have_formatting(str) {\n return typeof str === 'string' && !!str.match(format_exist_re);\n },\n is_formatting: function is_formatting(str) {\n return typeof str === 'string' && !!str.match(format_full_re);\n },\n // ---------------------------------------------------------------------\n is_extended_command: function is_extended_command(str) {\n return typeof str === 'string' &&\n str.match(format_exec_re) &&\n !$.terminal.is_formatting(str);\n },\n // ---------------------------------------------------------------------\n each_extended_command: function(string, fn) {\n var parts = string.split(format_exec_split_re);\n return $.map(parts, function(string) {\n if ($.terminal.is_extended_command(string)) {\n var command = string.replace(/^\\[\\[|\\]\\]$/g, '');\n return fn(command) || '';\n }\n return string;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: return array of formatting and text between them\n // ---------------------------------------------------------------------\n format_split: function format_split(str) {\n return str.split(format_split_re).filter(Boolean);\n },\n // ---------------------------------------------------------------------\n // :: replace that return position after replace for working with\n // :: replacement that change length of the string\n // :: source https://stackoverflow.com/a/46756077/387194\n // ---------------------------------------------------------------------\n tracking_replace: function tracking_replace(string, rex, replacement, position) {\n if (!(rex instanceof RegExp)) {\n throw new Error('tracking_replace: Second argument need to be RegExp');\n }\n function substring(string, start, end) {\n return string.slice(start, end);\n }\n function length(string) {\n return $.terminal.strip(string).length;\n }\n var new_string = \"\";\n var match;\n var index = 0;\n var rep_string;\n var new_position = position;\n var start;\n rex.lastIndex = 0; // Just to be sure\n while ((match = rex.exec(string))) {\n // if regex don't have g flag lastIndex will not work\n if (rex.global) {\n // Add any of the original string we just skipped\n var last_index = length(substring(string, 0, rex.lastIndex));\n start = last_index - length(match[0]);\n } else {\n start = match.index;\n last_index = start + length(match[0]);\n }\n if (index < start) {\n new_string += substring(string, index, start);\n }\n index = last_index;\n // Build the replacement string. This just handles $$ and $n,\n // you may want to add handling for $`, $', and $&.\n if (typeof replacement === 'function') {\n rep_string = replacement.apply(null, match);\n } else {\n rep_string = replacement.replace(/\\$(\\$|\\d)/g, function(m, c0) {\n if (c0 === \"$\") {\n return \"$\";\n }\n return match[c0];\n });\n }\n // Add on the replacement\n new_string += rep_string;\n // If the position is affected...\n if (start < position) {\n // ... update it:\n var rep_len = length(rep_string);\n rep_len += count_selfclosing_formatting(rep_string);\n if (last_index < position) {\n // It's after the replacement, move it\n new_position = Math.max(\n 0,\n new_position +\n rep_len -\n length(match[0])\n );\n } else {\n // It's *in* the replacement, put it just after\n new_position += rep_len - (position - start);\n }\n }\n // If the regular expression doesn't have the g flag, break here so\n // we do just one replacement (and so we don't have an endless loop!)\n if (!rex.global) {\n break;\n }\n }\n // Add on any trailing text in the string\n if (index < length(string)) {\n new_string += substring(string, index);\n }\n // Return the string and the updated position\n if (string === new_string) {\n return [string, position];\n }\n return [new_string, new_position];\n },\n // ---------------------------------------------------------------------\n // :: helper function used by substring and split_equal it loop over\n // :: string and execute callback with text count and other data\n // ---------------------------------------------------------------------\n iterate_formatting: function iterate_formatting(string, callback) {\n function is_space(i) {\n return string.slice(i - 6, i) === ' ' ||\n string.slice(i - 1, i).match(/\\s/);\n }\n // ----------------------------------------------------------------\n function match_entity(index) {\n return string.slice(index).match(entity_re);\n }\n // ----------------------------------------------------------------\n function is_open_formatting(i) {\n return string[i] === '[' && string[i + 1] === '[';\n }\n // ----------------------------------------------------------------\n function is_escape_bracket(i) {\n return string[i - 1] !== '\\\\' && string[i] === '\\\\' &&\n string[i + 1] === ']';\n }\n // ----------------------------------------------------------------\n function is_text(i) {\n return not_formatting && (string[i] !== ']' || !have_formatting)\n && !opening;\n }\n // ----------------------------------------------------------------\n // :: function will skip to next character in main loop\n // :: TODO: improve performance of emoji regex and check whole\n // :: string it's complex string if not use simple function\n // ----------------------------------------------------------------\n var get_next_character = make_next_char_fun(string);\n function next_iteration() {\n var char = get_next_character(substring);\n if (char.length > 1 && $.terminal.length(substring) > 1) {\n return char.length - 1;\n }\n return 0;\n }\n // ----------------------------------------------------------------\n function is_next_space() {\n return (is_space(i) && (not_formatting || opening)) &&\n (space === -1 && prev_space !== i || space !== -1);\n }\n // ----------------------------------------------------------------\n // :: last iteration or one before closing formatting\n // ----------------------------------------------------------------\n var last = false;\n function is_last() {\n if (i === string.length - 1 && !last) {\n last = true;\n } else {\n last = formatting && !!substring.match(/^.]$/);\n }\n return last;\n }\n // ----------------------------------------------------------------\n var have_formatting = $.terminal.have_formatting(string);\n var formatting = '';\n var in_text = false;\n var count = 0;\n var match;\n var space = -1;\n var space_count = -1;\n var prev_space;\n var length = 0;\n var offset = 0;\n var re_ent = /(&[^;]+);$/;\n for (var i = 0; i < string.length; i++) {\n var substring = string.slice(i);\n match = substring.match(format_start_re);\n if (match) {\n formatting = match[1];\n in_text = false;\n } else if (formatting) {\n if (string[i] === ']') {\n if (in_text) {\n formatting = '';\n in_text = false;\n } else {\n in_text = true;\n }\n }\n } else {\n in_text = true;\n }\n var not_formatting = (formatting && in_text) || !formatting;\n var opening = is_open_formatting(i);\n if (is_next_space()) {\n space = i;\n space_count = count;\n }\n var braket = string[i].match(/[[\\]]/);\n offset = 0;\n if (not_formatting) {\n // treat entity as one character\n if (string[i] === '&') {\n match = match_entity(i);\n if (match) {\n i += match[1].length - 2; // 2 because continue adds 1 to i\n continue;\n }\n ++count;\n ++length;\n } else if (is_escape_bracket(i)) {\n // escape \\] and \\\\ counts as one character\n ++count;\n ++length;\n offset = 1;\n i += 1;\n } else if (!braket || !have_formatting) {\n ++count;\n ++length;\n }\n }\n if (is_text(i)) {\n if (strlen(string[i]) === 2) {\n length++;\n }\n var char = get_next_character(substring);\n var size = char.length;\n // begining of enity that we've skipped, we are at the end\n if (char === ';') {\n match = string.slice(0, i + 1).match(re_ent);\n if (match) {\n offset = match[1].length;\n size = offset + 1;\n }\n }\n var data = {\n last: is_last(),\n count: count,\n index: i - offset,\n formatting: formatting,\n length: length,\n text: in_text,\n size: size,\n space: space,\n space_count: space_count\n };\n var ret = callback(data);\n if (ret === false) {\n break;\n } else if (ret) {\n if (ret.count !== undefined) {\n count = ret.count;\n }\n if (ret.length !== undefined) {\n length = ret.length;\n }\n if (ret.space !== undefined) {\n prev_space = space;\n space = ret.space;\n }\n if (ret.index !== undefined) {\n i = ret.index;\n continue;\n }\n }\n } else if (i === string.length - 1 && !last) {\n // last iteration, if formatting have last bracket,\n // from formatting, then last iteration\n // was already called (in if) #550\n callback({\n last: true,\n count: count + 1,\n index: i,\n formatting: formatting,\n length: 0,\n text: in_text,\n space: space\n });\n }\n // handle emoji, suroggate pairs and combine characters\n if (in_text) {\n i += next_iteration();\n }\n }\n },\n // ---------------------------------------------------------------------\n // :: function return string splitted into single characters\n // :: each character is wrapped into formatting from input string\n // :: or empty formatting so it will create span when using with ::format\n // ---------------------------------------------------------------------\n partition: function partition(string) {\n if (!$.terminal.have_formatting(string)) {\n var chars = $.terminal.split_characters(string);\n return chars.map(wrap);\n }\n var result = [];\n function wrap(string) {\n if (string.match(/\\\\$/)) {\n string += '\\\\';\n }\n return '[[;;]' + string + ']';\n }\n function formatting(string) {\n if ($.terminal.is_formatting(string)) {\n if (string.match(/\\\\]$/)) {\n string = string.replace(/\\\\]/g, '\\\\\\\\]');\n }\n } else {\n string = wrap(string);\n }\n return string;\n }\n $.terminal.iterate_formatting(string, function(data) {\n if (data.text) {\n var text = [];\n if (data.formatting) {\n text.push(data.formatting);\n }\n text.push(string.substring(data.index, data.index + data.size));\n if (data.formatting) {\n text.push(']');\n }\n result.push(formatting(text.join('')));\n }\n });\n return result;\n },\n // ---------------------------------------------------------------------\n // :: formatting aware substring function\n // ---------------------------------------------------------------------\n substring: function substring(string, start_index, end_index) {\n var chars = $.terminal.split_characters(string);\n if (!chars.slice(start_index, end_index).length) {\n return '';\n }\n if (!$.terminal.have_formatting(string)) {\n return chars.slice(start_index, end_index).join('');\n }\n var start = 0;\n var end;\n var start_formatting = '';\n var end_formatting = '';\n var prev_index;\n var offset = 1;\n $.terminal.iterate_formatting(string, function(data) {\n if (start_index && data.count === start_index + 1) {\n start = data.index;\n if (data.formatting) {\n start_formatting = data.formatting;\n }\n }\n if (end_index && data.count === end_index) {\n end_formatting = data.formatting;\n prev_index = data.index;\n offset = data.size;\n }\n if (data.count === end_index + 1) {\n end = data.index;\n if (data.formatting) {\n end = prev_index + offset;\n }\n }\n });\n if (start_index && !start) {\n return '';\n }\n if (end === undefined) {\n end = string.length;\n }\n string = start_formatting + string.slice(start, end);\n if (end_formatting) {\n string = string.replace(/(\\[\\[^\\]]+)?\\]$/, '');\n string += ']';\n }\n return string;\n },\n // ---------------------------------------------------------------------\n // :: add format text as 5th paramter to formatting it's used for\n // :: data attribute in format function - and fix unclosed &\n // ---------------------------------------------------------------------\n normalize: function normalize(string) {\n string = string.replace(format_re, function(_, format, text) {\n if (format.match(self_closing_re) && text === '') {\n return '[[' + format + '] ]';\n }\n if (text === '') {\n return '';\n }\n function safe(string) {\n return string.replace(/\\\\\\]/g, ']').replace(/\\n/g, '\\\\n')\n .replace(/ /g, ' ');\n }\n format = safe(format);\n var semicolons = format.match(/;/g).length;\n // missing semicolons\n if (semicolons >= 4) {\n var args = format.split(/;/);\n var start = args.slice(0, 4).join(';');\n var arg = args.slice(4).join(';');\n return '[[' + start + ';' + (arg || text) + ']' + text + ']';\n } else if (semicolons === 2) {\n semicolons = ';;';\n } else if (semicolons === 3) {\n semicolons = ';';\n }\n // return '[[' + format + ']' + text + ']';\n // closing braket will break formatting so we need to escape\n // those using html entity equvalent\n // space is hack for images that break iterate_formatting\n format += semicolons + safe(text);\n return '[[' + format + ']' + text + ']';\n });\n return $.terminal.amp(string);\n },\n // ---------------------------------------------------------------------\n // :: split text into lines with equal length so each line can be\n // :: rendered separately (text formatting can be longer then a line).\n // ---------------------------------------------------------------------\n split_equal: function split_equal(str, length, keep_words) {\n var prev_format = '';\n var result = [];\n var array = $.terminal.normalize(str).split(/\\n/g);\n for (var i = 0, len = array.length; i < len; ++i) {\n if (array[i] === '') {\n result.push('');\n continue;\n }\n var line = array[i];\n var get_next_character = make_next_char_fun(line);\n var first_index = 0;\n var output;\n var line_length = line.length;\n var last_bracket = !!line.match(/\\[\\[[^\\]]+\\](?:[^\\][]|\\\\\\])+\\]$/);\n $.terminal.iterate_formatting(line, function(data) {\n var chr, substring;\n if (data.length >= length || data.last ||\n (data.length === length - 1 &&\n strlen(line[data.index + 1]) === 2)) {\n var can_break = false;\n // TODO: this need work\n if (keep_words && data.space !== -1) {\n // replace html entities with characters\n var stripped = text(line).substring(data.space_count);\n // real length, not counting formatting\n stripped = stripped.slice(0, length).trim();\n var text_len = strlen(stripped);\n if (stripped.match(/\\s/) || text_len < length) {\n can_break = true;\n }\n }\n // if words is true we split at last space and make next loop\n // continue where the space where located\n var after_index = data.index + data.size;\n if (last_bracket) {\n after_index += 1;\n }\n var new_index;\n if (keep_words && data.space !== -1 &&\n after_index !== line_length && can_break) {\n output = line.slice(first_index, data.space);\n new_index = data.space - 1;\n } else {\n substring = line.slice(data.index);\n chr = get_next_character(substring);\n output = line.slice(first_index, data.index) + chr;\n if (data.last && last_bracket && chr !== ']') {\n output += ']';\n }\n new_index = data.index + chr.length - 1;\n }\n if (keep_words) {\n output = output.replace(/^( |\\s)+|( |\\s)+$/g, '');\n }\n first_index = (new_index || data.index) + 1;\n if (prev_format) {\n var closed_formatting = output.match(/^[^\\]]*\\]/);\n output = prev_format + output;\n if (closed_formatting) {\n prev_format = '';\n }\n }\n var matched = output.match(format_re);\n if (matched) {\n var last = matched[matched.length - 1];\n if (last[last.length - 1] !== ']') {\n prev_format = last.match(format_begin_re)[1];\n output += ']';\n } else if (output.match(format_end_re)) {\n output = output.replace(format_end_re, '');\n prev_format = last.match(format_begin_re)[1];\n }\n }\n result.push(output);\n // modify loop by returing new data\n return {index: new_index, length: 0, space: -1};\n }\n });\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: Escape & that's not part of entity\n // ---------------------------------------------------------------------\n amp: function amp(str) {\n return str.replace(/&(?!#[0-9]+;|#x[0-9a-f]+;|[a-z]+;)/gi, '&');\n },\n // ---------------------------------------------------------------------\n // :: Encode formating as html for insertion into DOM\n // ---------------------------------------------------------------------\n encode: function encode(str, options) {\n var settings = $.extend({\n tabs: 4,\n before: ''\n }, options);\n return $.terminal.amp(str).replace(//g, '>')\n .replace(/ /g, ' ').split('\\n').map(function(line) {\n var splitted = line.split(/((?:\\[\\[[^\\]]+\\])?\\t(?:\\])?)/);\n splitted = splitted.filter(Boolean);\n return splitted.map(function(str, i) {\n if (str.match(/\\t/)) {\n return str.replace(/\\t([^\\t]*)$/, function(_, end) {\n if (i !== 0 && splitted[i - 1].match(/\\t\\]?$/)) {\n var sp = new Array(settings.tabs + 1).join(' ');\n return sp + end;\n } else {\n var before = splitted.slice(i - 1, i).join('');\n if (settings.before && i <= 1) {\n before = settings.before + before;\n }\n var len = $.terminal.length(before);\n var chars = settings.tabs - (len % settings.tabs);\n if (chars === 0) {\n chars = 4;\n }\n return new Array(chars + 1).join(' ') + end;\n }\n });\n }\n return str;\n }).join('');\n }).join('\\n');\n },\n // -----------------------------------------------------------------------\n // :: Default formatter that allow for nested formatting, example:\n // :: [[;;#000]hello [[;#f00;]red] world]\n // -----------------------------------------------------------------------\n nested_formatting: function nested_formatting(string) {\n if (!$.terminal.have_formatting(string)) {\n return string;\n }\n var stack = [];\n var re = /((?:\\[\\[(?:[^\\][]|\\\\\\])+\\])?(?:[^\\][]|\\\\\\])*\\]?)/;\n var format_re = /\\[\\[([^\\][]+)\\][\\s\\S]*/;\n var format_split_re = /^\\[\\[([^;]*);([^;]*);([^\\]]*)\\]/;\n var class_i = 3; // index of the class in formatting\n var attrs_i = 5; // index of attributes in formatting\n // ---------------------------------------------------------------------------\n function unique(value, index, self) {\n return self.indexOf(value) === index;\n }\n // ---------------------------------------------------------------------------\n function update_style(new_style, old_style) {\n new_style = parse_style(new_style);\n if (!old_style) {\n return new_style;\n }\n return $.extend(old_style, new_style);\n }\n // ---------------------------------------------------------------------------\n function parse_style(string) {\n var style = {};\n string.split(/\\s*;\\s*/).forEach(function(string) {\n var parts = string.split(':').map(function(string) {\n return string.trim();\n });\n var prop = parts[0];\n var value = parts[1];\n style[prop] = value;\n });\n return style;\n }\n // ---------------------------------------------------------------------------\n function stringify_formatting(input) {\n var result = input.slice();\n if (input[attrs_i]) {\n result[attrs_i] = stringify_attrs(input[attrs_i]);\n }\n if (input[class_i]) {\n result[class_i] = stringify_class(input[class_i]);\n }\n result[0] = stringify_styles(input[0]);\n return result.join(';');\n }\n // ---------------------------------------------------------------------------\n function stringify_styles(input) {\n var ignore = input.filter(function(s) {\n return s[0] === '-';\n }).map(function(s) {\n return s[1];\n });\n return input.filter(function(s) {\n return ignore.indexOf(s) === -1 && ignore.indexOf(s[1]) === -1;\n }).join('');\n }\n // ---------------------------------------------------------------------------\n function stringify_attrs(attrs) {\n return JSON.stringify(attrs, function(key, value) {\n if (key === 'style') {\n return stringify_style(value);\n }\n return value;\n });\n }\n // ---------------------------------------------------------------------------\n function stringify_class(klass) {\n return klass.filter(unique).join(' ');\n }\n // ---------------------------------------------------------------------------\n function stringify_style(style) {\n return Object.keys(style).map(function(prop) {\n return prop + ':' + style[prop];\n }).join(';');\n }\n // ---------------------------------------------------------------------------\n function get_inherit_style(stack) {\n function update_attrs(value) {\n if (!output[attrs_i]) {\n output[attrs_i] = {};\n }\n try {\n var new_attrs = JSON.parse(value);\n if (new_attrs.style) {\n var new_style = new_attrs.style;\n var old_style = output[attrs_i].style;\n new_attrs.style = update_style(new_style, old_style);\n output[attrs_i] = $.extend(\n new_attrs,\n output[attrs_i],\n {\n style: update_style(new_style, old_style)\n }\n );\n } else {\n output[attrs_i] = $.extend(\n new_attrs,\n output[attrs_i]\n );\n }\n } catch (e) {\n warn('Invalid JSON ' + value);\n }\n }\n var output = [[], '', ''];\n if (!stack.length) {\n return output;\n }\n for (var i = stack.length; i--;) {\n var formatting = stack[i].split(';');\n if (formatting.length > 5) {\n var last = formatting.slice(5).join(';');\n formatting = formatting.slice(0, 5).concat(last);\n }\n var style = formatting[0].split(/(-?[@!gbiuso])/g).filter(Boolean);\n style.forEach(function(s) {\n if (output[0].indexOf(s) === -1) {\n output[0].push(s);\n }\n });\n for (var j = 1; j < formatting.length; ++j) {\n var value = formatting[j].trim();\n if (value) {\n if (j === class_i) {\n if (!output[class_i]) {\n output[class_i] = [];\n }\n var classes = value.split(/\\s+/);\n output[class_i] = output[class_i].concat(classes);\n } else if (j === attrs_i) {\n update_attrs(value);\n } else if (!output[j]) {\n output[j] = value;\n }\n }\n }\n }\n return stringify_formatting(output);\n }\n return string.split(re).filter(Boolean).map(function(string) {\n var style;\n if (string.match(/^\\[\\[/) && !$.terminal.is_extended_command(string)) {\n var formatting = string.replace(format_re, '$1');\n var is_formatting = $.terminal.is_formatting(string);\n string = string.replace(format_split_re, '');\n stack.push(formatting);\n if ($.terminal.nested_formatting.__inherit__) {\n style = get_inherit_style(stack);\n } else {\n style = formatting;\n }\n if (!is_formatting) {\n string += ']';\n } else {\n stack.pop();\n }\n string = '[[' + style + ']' + string;\n } else {\n var pop = false;\n if (string.match(/\\]/)) {\n pop = true;\n }\n if (stack.length) {\n if ($.terminal.nested_formatting.__inherit__) {\n style = get_inherit_style(stack);\n } else {\n style = stack[stack.length - 1];\n }\n string = '[[' + style + ']' + string;\n }\n if (pop) {\n stack.pop();\n } else if (stack.length) {\n string += ']';\n }\n }\n return string;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: safe function that will render text as it is\n // ---------------------------------------------------------------------\n escape_formatting: function escape_formatting(string) {\n return $.terminal.escape_brackets(string);\n },\n // ---------------------------------------------------------------------\n // :: apply custom formatters only to text\n // ---------------------------------------------------------------------\n apply_formatters: function apply_formatters(string, settings) {\n if (string === \"\") {\n if (settings && typeof settings.position === 'number') {\n return [\"\", settings.position];\n } else {\n return \"\";\n }\n }\n function test_lengths(formatter, index, ret, string) {\n if (!formatter.__no_warn__ &&\n $.terminal.length(ret) !== $.terminal.length(string)) {\n warn('Your formatter[' + index + '] change length of the string, ' +\n 'you should use [regex, replacement] formatter or function ' +\n ' that return [replacement, position] instead');\n }\n }\n function should_format(options) {\n if (!settings || !options) {\n return true;\n }\n var props = ['echo', 'command', 'prompt'];\n var have_any = props.some(function(name) {\n return options[name] === true;\n });\n if (!have_any) {\n return true;\n }\n for (var i = props.length; i--;) {\n var prop = props[i];\n if (options[prop] === true && settings[prop] === true) {\n return true;\n }\n }\n return false;\n }\n settings = settings || {};\n var formatters = settings.formatters || $.terminal.defaults.formatters;\n var i = 0;\n function apply_function_formatter(formatter, input) {\n var options = $.extend({}, settings, {\n position: input[1]\n });\n var ret = formatter(input[0], options);\n if (typeof ret === 'string') {\n test_lengths(formatter, i - 1, ret, input[0]);\n if (typeof ret === 'string') {\n return [ret, options.position];\n }\n return input;\n } else if (is_array(ret) && ret.length === 2) {\n return ret;\n } else {\n return input;\n }\n }\n var input;\n if (typeof settings.position === 'number') {\n input = [string, settings.position];\n } else {\n input = [string, 0];\n }\n try {\n var result = formatters.reduce(function(input, formatter) {\n i++;\n // __meta__ is for safe formatter that can handle formatters\n // inside formatters. for other usage we use format_split so one\n // formatter don't mess with formatter that was previous\n // on the list\n if (typeof formatter === 'function' && formatter.__meta__) {\n return apply_function_formatter(formatter, input);\n } else {\n var length = 0;\n var found_position = false;\n var splitted = $.terminal.format_split(input[0]);\n var partials = splitted.map(function(string) {\n var position;\n var this_len = text(string).length;\n // first position that match is used for this partial\n if (input[1] < length + this_len && !found_position) {\n position = input[1] - length;\n found_position = true;\n } else if (found_position) {\n // -1 indicate that we will not track position because it\n // was in one of the previous parial strings\n position = -1;\n } else {\n // initial position for replacers\n position = input[1];\n }\n // length is used to correct position after replace\n var length_before = length;\n var result;\n length += this_len;\n if ($.terminal.is_formatting(string)) {\n if (found_position) {\n return [string, position];\n }\n return [string, -1];\n } else {\n if (is_array(formatter)) {\n var options = formatter[2] || {};\n result = [string, position < 0 ? 0 : position];\n if (result[0].match(formatter[0]) &&\n should_format(formatter[2])) {\n if (options.loop) {\n while (result[0].match(formatter[0])) {\n result = $.terminal.tracking_replace(\n result[0],\n formatter[0],\n formatter[1],\n result[1]\n );\n }\n } else {\n result = $.terminal.tracking_replace(\n result[0],\n formatter[0],\n formatter[1],\n result[1]\n );\n }\n }\n if (position < 0) {\n return [result[0], -1];\n }\n } else if (typeof formatter === 'function') {\n result = apply_function_formatter(formatter, [\n string, position\n ]);\n }\n if (typeof result !== 'undefined') {\n // correct position becuase it's relative\n // to partial and we need global for whole string\n if (result[1] !== -1) {\n result[1] += length_before;\n }\n var after_len = text(result[0]).length;\n if (after_len !== this_len) {\n }\n return result;\n }\n return [string, -1];\n }\n });\n var position_partial = partials.filter(function(partial) {\n return partial[1] !== -1;\n })[0];\n var string = partials.map(function(partial) {\n return partial[0];\n }).join('');\n var position;\n if (typeof position_partial === 'undefined') {\n position = input[1];\n } else {\n position = position_partial[1];\n }\n // to make sure that output position is not outside the string\n var max = text(string).length;\n max += count_selfclosing_formatting(string);\n if (position > max) {\n position = max;\n }\n if (string === input[0]) {\n return input;\n }\n var before = $.terminal.strip(input[0]);\n var after = $.terminal.strip(string);\n if (before === after) {\n return [string, input[1]];\n }\n return [string, position];\n }\n }, input);\n if (typeof settings.position === 'number') {\n var codepoint_len = $.terminal.strip(result[0]).length;\n if ($.terminal.length(result[0]) < codepoint_len) {\n var position = result[1];\n position = normalize_position(result[0], position);\n var max = $.terminal.length(result[0]);\n if (position > max) {\n position = max;\n }\n result[1] = position;\n }\n return result;\n } else {\n return result[0];\n }\n } catch (e) {\n var msg = 'Error in formatter [' + (i - 1) + ']';\n formatters.splice(i - 1);\n throw new $.terminal.Exception('formatting', msg, e.stack);\n }\n },\n // ---------------------------------------------------------------------\n // :: Replace terminal formatting with html\n // ---------------------------------------------------------------------\n format: function format(str, options) {\n var settings = $.extend({}, {\n linksNoReferrer: false,\n linksNoFollow: false,\n allowedAttributes: [],\n charWidth: undefined,\n escape: true,\n anyLinks: false\n }, options || {});\n // -----------------------------------------------------------------\n function filter_attr_names(names) {\n if (names.length && settings.allowedAttributes.length) {\n return names.filter(function(name) {\n if (name === 'data-text') {\n return false;\n }\n var allowed = false;\n var filters = settings.allowedAttributes;\n for (var i = 0; i < filters.length; ++i) {\n if (filters[i] instanceof RegExp) {\n if (filters[i].test(name)) {\n allowed = true;\n break;\n }\n } else if (filters[i] === name) {\n allowed = true;\n break;\n }\n }\n return allowed;\n });\n }\n return [];\n }\n // -----------------------------------------------------------------\n function clean_data(data, text) {\n if (data === '') {\n return text;\n } else {\n return data.replace(/]/g, ']')\n .replace(/>/g, '>')\n .replace(/');\n if (str.match(/^\\s*\\{[^}]*\\}\\s*$/)) {\n attrs = JSON.parse(str);\n data_text = splitted[0];\n }\n } catch (e) {\n }\n }\n if (text === '' && !style.match(/@/)) {\n return ''; //'';\n }\n text = safe(text);\n text = text.replace(/\\\\\\]/g, ']');\n if (settings.escape) {\n // inside formatting we need to unescape escaped slashes\n // but this escape is not needed when echo - don't know why\n text = text.replace(/\\\\\\\\/g, '\\\\');\n }\n var style_str = '';\n if (style.indexOf('b') !== -1) {\n style_str += 'font-weight:bold;';\n }\n var text_decoration = [];\n if (style.indexOf('u') !== -1) {\n text_decoration.push('underline');\n }\n if (style.indexOf('s') !== -1) {\n text_decoration.push('line-through');\n }\n if (style.indexOf('o') !== -1) {\n text_decoration.push('overline');\n }\n if (text_decoration.length) {\n style_str += 'text-decoration:' +\n text_decoration.join(' ') + ';';\n }\n if (style.indexOf('i') !== -1) {\n style_str += 'font-style:italic;';\n }\n if ($.terminal.valid_color(color)) {\n style_str += [\n 'color:' + color,\n '--color:' + color,\n '--original-color:' + color\n ].join(';') + ';';\n if (style.indexOf('!') !== -1) {\n style_str += '--link-color:' + color + ';';\n }\n if (style.indexOf('g') !== -1) {\n style_str += 'text-shadow:0 0 5px ' + color + ';';\n }\n }\n if ($.terminal.valid_color(background)) {\n style_str += [\n 'background-color:' + background,\n '--background:' + background\n ].join(';') + ';';\n }\n var data = clean_data(data_text, text);\n var extra = extra_css(text, settings);\n if (extra) {\n text = wide_characters(text, settings);\n style_str += extra;\n }\n var result;\n if (style.indexOf('!') !== -1) {\n result = pre_process_link(data);\n } else if (style.indexOf('@') !== -1) {\n result = pre_process_image(data);\n } else {\n result = '' + text + '';\n } else if (style.indexOf('@') !== -1) {\n result += ' data-text/>';\n } else {\n result += ' data-text=\"' + data + '\">' +\n '' + text + '';\n }\n return result;\n }\n if (typeof str === 'string') {\n // support for formating foo[[u;;]bar]baz[[b;#fff;]quux]zzz\n var splitted = $.terminal.format_split(str);\n str = $.map(splitted, function(text) {\n if (text === '') {\n return text;\n } else if ($.terminal.is_formatting(text)) {\n // fix inside formatting because encode is called\n // before format\n text = text.replace(/\\[\\[[^\\]]+\\]/, function(text) {\n return text.replace(/ /g, ' ');\n });\n return text.replace(format_parts_re, format);\n } else {\n text = safe(text);\n text = text.replace(/\\\\\\]/, ']');\n var data = clean_data(text);\n var extra = extra_css(text, settings);\n var prefix;\n if (extra.length) {\n text = wide_characters(text, settings);\n prefix = '' + text + '';\n }\n }).join('');\n return str.replace(/ <\\/span>/gi, ' ');\n } else {\n return '';\n }\n },\n // ---------------------------------------------------------------------\n // :: Replace brackets with html entities\n // ---------------------------------------------------------------------\n escape_brackets: function escape_brackets(string) {\n return string.replace(/\\[/g, '[')\n .replace(/\\]/g, ']')\n .replace(/\\\\/g, '\');\n },\n // ---------------------------------------------------------------------\n // :: complmentary function\n // ---------------------------------------------------------------------\n unescape_brackets: function unescape_brackets(string) {\n return string.replace(/[/g, '[')\n .replace(/]/g, ']')\n .replace(/\/g, '\\\\');\n },\n // ---------------------------------------------------------------------\n // :: return number of characters without formatting\n // ---------------------------------------------------------------------\n length: function(string, raw) {\n if (!string) {\n return 0;\n }\n return $.terminal.split_characters(raw ? string : text(string)).length;\n },\n // ---------------------------------------------------------------------\n // :: split characters handling emoji, surogate pairs and combine chars\n // ---------------------------------------------------------------------\n split_characters: function split_characters(string) {\n var result = [];\n var get_next_character = make_next_char_fun(string);\n while (string.length) {\n var chr = get_next_character(string);\n string = string.slice(chr.length);\n result.push(chr);\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: return string where array items are in columns padded spaces\n // :: after adding align tabs arr.join('\\t\\t') looks much better\n // ---------------------------------------------------------------------\n columns: function(array, cols, space) {\n var no_formatting = array.map(function(string) {\n return $.terminal.strip(string);\n });\n var lengths = no_formatting.map(function(string) {\n return strlen(string);\n });\n if (typeof space === 'undefined') {\n space = 4;\n }\n var length = Math.max.apply(null, lengths) + space;\n // we need value - 1 because index starts from 0\n var column_limit = Math.floor(cols / length) - 1;\n if (column_limit < 1) {\n return array.join('\\n');\n }\n var lines = [];\n for (var i = 0, len = array.length; i < len; i += column_limit) {\n var line = array.slice(i, i + column_limit);\n var last = line.pop();\n lines.push(line.reduce(function(acc, string) {\n var stripped = $.terminal.strip(string);\n var pad = new Array(length - stripped.length + 1).join(' ');\n acc.push(string + pad);\n return acc;\n }, []).join('') + last);\n }\n return lines.join('\\n');\n },\n // ---------------------------------------------------------------------\n // :: Remove formatting from text\n // ---------------------------------------------------------------------\n strip: function strip(str) {\n if (!$.terminal.have_formatting(str)) {\n return str;\n }\n return $.terminal.format_split(str).map(function(str) {\n if ($.terminal.is_formatting(str)) {\n str = str.replace(format_parts_re, '$6');\n return str.replace(/\\\\([[\\]])/g, function(whole, bracket) {\n return bracket;\n });\n }\n return str;\n }).join('');\n },\n // ---------------------------------------------------------------------\n // :: Return active terminal\n // ---------------------------------------------------------------------\n active: function active() {\n return terminals.front();\n },\n // ---------------------------------------------------------------------\n // :: Implmentation detail id is always length of terminals Cycle\n // ---------------------------------------------------------------------\n last_id: function last_id() {\n var len = terminals.length();\n return len - 1;\n },\n // ---------------------------------------------------------------------\n // :: Function that works with strings like 'asd' 'asd\\' asd' \"asd asd\"\n // :: asd\\ 123 -n -b / [^ ]+ / /\\s+/ asd\\ a it creates a regex and\n // :: numbers and replaces escape characters in double quotes\n // :: if strict is set to false it only strips single and double quotes\n // :: and escapes spaces\n // ---------------------------------------------------------------------\n parse_argument: function parse_argument(arg, strict) {\n function parse_string(string) {\n // split string to string literals and non-strings\n return string.split(string_re).map(function(string) {\n // remove quotes if before are even number of slashes\n // we don't remove slases becuase they are handled by JSON.parse\n if (string.match(/^['\"`]/)) {\n // fixing regex to match empty string is not worth it\n if (string === '\"\"' || string === \"''\" || string === \"``\") {\n return '';\n }\n var quote = string[0];\n var re = new RegExp(\"(\\\\\\\\\\\\\\\\(?:\\\\\\\\\\\\\\\\)*)\" + quote, \"g\");\n string = string.replace(re, '$1').replace(/^[`'\"]|[`'\"]$/g, '');\n if (quote === \"'\") {\n string = string.replace(/\"/g, '\\\\\"');\n }\n }\n string = '\"' + string + '\"';\n // use build in function to parse rest of escaped characters\n return JSON.parse(string);\n }).join('');\n }\n if (strict === false) {\n if (arg[0] === \"'\" && arg[arg.length - 1] === \"'\") {\n return arg.replace(/^'|'$/g, '');\n } else if (arg[0] === \"`\" && arg[arg.length - 1] === \"`\") {\n return arg.replace(/^`|`$/g, '');\n } else if (arg[0] === '\"' && arg[arg.length - 1] === '\"') {\n return arg.replace(/^\"|\"$/g, '').replace(/\\\\([\" ])/g, '$1');\n } else if (arg.match(/\\/.*\\/[gimy]*$/)) {\n return arg;\n } else if (arg.match(/['\"`]]/)) {\n // part of arg is in quote\n return parse_string(arg);\n } else {\n return arg.replace(/\\\\ /g, ' ');\n }\n }\n if (arg === 'true') {\n return true;\n } else if (arg === 'false') {\n return false;\n }\n var regex = arg.match(re_re);\n if (regex) {\n return new RegExp(regex[1], regex[2]);\n } else if (arg.match(/['\"`]/)) {\n return parse_string(arg);\n } else if (arg.match(/^-?[0-9]+$/)) {\n return parseInt(arg, 10);\n } else if (arg.match(float_re)) {\n return parseFloat(arg);\n } else {\n return arg.replace(/\\\\(['\"() ])/g, '$1');\n }\n },\n // ---------------------------------------------------------------------\n // :: function split and parse arguments\n // ---------------------------------------------------------------------\n parse_arguments: function parse_arguments(string) {\n return $.map(string.match(command_re) || [], $.terminal.parse_argument);\n },\n // ---------------------------------------------------------------------\n // :: Function split and strips single and double quotes\n // :: and escapes spaces\n // ---------------------------------------------------------------------\n split_arguments: function split_arguments(string) {\n return $.map(string.match(command_re) || [], function(arg) {\n return $.terminal.parse_argument(arg, false);\n });\n },\n // ---------------------------------------------------------------------\n // :: Function that returns an object {name,args}. Arguments are parsed\n // :: using the function parse_arguments\n // ---------------------------------------------------------------------\n parse_command: function parse_command(string) {\n return process_command(string, $.terminal.parse_argument);\n },\n // ---------------------------------------------------------------------\n // :: Same as parse_command but arguments are parsed using split_arguments\n // ---------------------------------------------------------------------\n split_command: function split_command(string) {\n return process_command(string, function(arg) {\n return $.terminal.parse_argument(arg, false);\n });\n },\n // ---------------------------------------------------------------------\n // :; function that parse arguments like yargs library\n // ---------------------------------------------------------------------\n parse_options: function parse_options(arg, options) {\n var settings = $.extend({}, {\n boolean: []\n }, options);\n if (typeof arg === 'string') {\n return parse_options($.terminal.split_arguments(arg), options);\n }\n var result = {\n _: []\n };\n function token(value) {\n this.value = value;\n }\n var rest = arg.reduce(function(acc, arg) {\n var str = typeof arg === 'string' ? arg : '';\n if (str.match(/^--?[^-]/) && acc instanceof token) {\n result[acc.value] = true;\n }\n if (str.match(/^--[^-]/)) {\n var name = str.replace(/^--/, '');\n if (settings.boolean.indexOf(name) === -1) {\n return new token(name);\n } else {\n result[name] = true;\n }\n } else if (str.match(/^-[^-]/)) {\n var single = str.replace(/^-/, '').split('');\n if (settings.boolean.indexOf(single.slice(-1)[0]) === -1) {\n var last = single.pop();\n }\n single.forEach(function(single) {\n result[single] = true;\n });\n if (last) {\n return new token(last);\n }\n } else if (acc instanceof token) {\n result[acc.value] = arg;\n } else if (arg) {\n result._.push(arg);\n }\n return null;\n }, null);\n if (rest instanceof token) {\n result[rest.value] = true;\n }\n return result;\n },\n // ---------------------------------------------------------------------\n // :: function executed for each text inside [[ .... ]] in echo\n // ---------------------------------------------------------------------\n extended_command: function extended_command(term, string, options) {\n var settings = $.extend({\n invokeMethods: false\n }, options);\n var deferred = new $.Deferred();\n try {\n change_hash = false;\n var m = string.match(extended_command_re);\n if (m) {\n if (!settings.invokeMethods) {\n warn('To invoke terminal or cmd methods you need to enable ' +\n 'invokeMethods option');\n deferred.reject();\n } else {\n string = m[1];\n var obj = m[2] === 'terminal' ? term : term.cmd();\n var fn = m[3];\n try {\n var args = eval('[' + m[4] + ']');\n if (!obj[fn]) {\n term.error('Unknow function ' + fn);\n } else {\n var ret = obj[fn].apply(term, args);\n if (ret && ret.then) {\n return ret;\n }\n }\n deferred.resolve();\n } catch (e) {\n term.error('Invalid invocation in ' +\n $.terminal.escape_brackets(string));\n deferred.reject();\n }\n }\n } else {\n return term.exec(string, true).done(function() {\n change_hash = true;\n });\n }\n } catch (e) {\n // error is process in exec\n deferred.reject();\n }\n return deferred.promise();\n },\n // ---------------------------------------------------------------------\n // :: ES6 iterator for a given string that handle emoji and formatting\n // ---------------------------------------------------------------------\n iterator: function(string) {\n function formatting(string) {\n if ($.terminal.is_formatting(string)) {\n if (string.match(/\\]\\\\\\]/)) {\n string = string.replace(/\\]\\\\\\]/g, ']\\\\\\\\]');\n }\n }\n return string;\n }\n if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {\n var len = $.terminal.length(string);\n var i = 0;\n var obj = {};\n obj[Symbol.iterator] = function() {\n return {\n next: function() {\n if (i < len) {\n var text = $.terminal.substring(string, i, i + 1);\n i++;\n return {\n value: formatting(text)\n };\n } else {\n return {\n done: true\n };\n }\n }\n };\n };\n return obj;\n }\n },\n // ---------------------------------------------------------------------\n // :: object that can be used in string methods intead of regex\n // ---------------------------------------------------------------------\n formatter: new (function() {\n try {\n this[Symbol.split] = function(string) {\n return $.terminal.format_split(string);\n };\n this[Symbol.match] = function(string) {\n return string.match(format_re);\n };\n this[Symbol.replace] = function(string, replacer) {\n return string.replace(format_parts_re, replacer);\n };\n this[Symbol.search] = function(string) {\n return string.search(format_re);\n };\n } catch (e) {\n }\n })(),\n // ---------------------------------------------------------------------\n // :: helper function that add formatter before nested_formatting\n // ---------------------------------------------------------------------\n new_formatter: function(formatter) {\n var formatters = $.terminal.defaults.formatters;\n for (var i = 0; i < formatters.length; ++i) {\n if (formatters[i] === $.terminal.nested_formatting) {\n formatters.splice(i, 0, formatter);\n return;\n }\n }\n formatters.push(formatter);\n }\n };\n // -------------------------------------------------------------------------\n $.terminal.Exception = function Terminal_Exception(type, message, stack) {\n if (arguments.length === 1) {\n this.message = arguments[0];\n this.type = 'TERMINAL';\n } else {\n this.type = type;\n this.message = message;\n if (stack) {\n this.stack = stack;\n }\n }\n };\n $.terminal.Exception.prototype = new Error();\n $.terminal.Exception.prototype.toString = function() {\n return this.message + '\\n' + this.stack;\n };\n // -----------------------------------------------------------------------\n // Helper plugins and functions\n // -----------------------------------------------------------------------\n $.fn.visible = function() {\n return this.css('visibility', 'visible');\n };\n $.fn.hidden = function() {\n return this.css('visibility', 'hidden');\n };\n // -----------------------------------------------------------------------\n var warnings = [];\n function warn(msg) {\n msg = '[jQuery Terminal] ' + msg;\n if (warnings.indexOf(msg) === -1) {\n warnings.push(msg);\n /* eslint-disable */\n if (console) {\n if (console.warn) {\n console.warn(msg);\n } else if (console.log) {\n console.log(msg);\n }\n /* eslint-enable */\n } else {\n // prevent catching in outer try..catch\n setTimeout(function() {\n throw new Error('WARN: ' + msg);\n }, 0);\n }\n }\n }\n // -----------------------------------------------------------------------\n // JSON-RPC CALL\n // -----------------------------------------------------------------------\n var ids = {}; // list of url based ids of JSON-RPC\n $.jrpc = function(url, method, params, success, error) {\n var deferred = new $.Deferred();\n var options;\n if ($.isPlainObject(url)) {\n options = url;\n } else {\n options = {\n url: url,\n method: method,\n params: params,\n success: success,\n error: error\n };\n }\n function validJSONRPC(response) {\n return $.isNumeric(response.id) &&\n (typeof response.result !== 'undefined' ||\n typeof response.error !== 'undefined');\n }\n ids[options.url] = ids[options.url] || 0;\n var request = {\n 'jsonrpc': '2.0',\n 'method': options.method,\n 'params': options.params,\n 'id': ++ids[options.url]\n };\n $.ajax({\n url: options.url,\n beforeSend: function beforeSend(jxhr, settings) {\n if (is_function(options.request)) {\n options.request(jxhr, request);\n }\n settings.data = JSON.stringify(request);\n },\n success: function success(response, status, jqXHR) {\n var content_type = jqXHR.getResponseHeader('Content-Type');\n if (!content_type.match(/(application|text)\\/json/)) {\n warn('Response Content-Type is neither application/json' +\n ' nor text/json');\n }\n var json;\n try {\n json = JSON.parse(response);\n } catch (e) {\n if (options.error) {\n options.error(jqXHR, 'Invalid JSON', e);\n } else {\n throw new $.terminal.Exception('JSON', 'Invalid JSON', e.stack);\n }\n deferred.reject({message: 'Invalid JSON', response: response});\n return;\n }\n if (is_function(options.response)) {\n options.response(jqXHR, json);\n }\n if (validJSONRPC(json) || options.method === 'system.describe') {\n // don't catch errors in success callback\n if (options.success) {\n options.success(json, status, jqXHR);\n }\n deferred.resolve(json);\n } else {\n if (options.error) {\n options.error(jqXHR, 'Invalid JSON-RPC');\n }\n deferred.reject({message: 'Invalid JSON-RPC', response: response});\n }\n },\n error: options.error,\n contentType: 'application/json',\n dataType: 'text',\n async: true,\n cache: false,\n // timeout: 1,\n type: 'POST'\n });\n return deferred.promise();\n };\n // -----------------------------------------------------------------------\n $.rpc = function(url, method, params) {\n var deferred = new $.Deferred();\n function success(res) {\n if (res.error) {\n deferred.reject(res.error);\n } else {\n deferred.resolve(res.result);\n }\n }\n function error(jqXHR, status, message) {\n deferred.reject({message: message});\n }\n $.jrpc(url, method, params, success, error);\n return deferred.promise();\n };\n // -----------------------------------------------------------------------\n function terminal_ready(term) {\n return !!(term.closest('body').length &&\n term.is(':visible') &&\n term.find('.cmd-prompt').length);\n }\n // -----------------------------------------------------------------------\n // :: Create fake terminal to calcualte the dimention of one character\n // :: this will make terminal work if terminal div is not added to the\n // :: DOM at init like with:\n // :: $('').terminal().echo('foo bar').appendTo('body');\n // -----------------------------------------------------------------------\n function get_char_size(term) {\n var rect;\n if (terminal_ready(term)) {\n var $prompt = term.find('.cmd-prompt').clone().css({\n visiblity: 'hidden',\n position: 'absolute'\n });\n $prompt.appendTo(term.find('.cmd')).html(' ');\n rect = $prompt[0].getBoundingClientRect();\n $prompt.remove();\n } else {\n var temp = $('
').appendTo('body');\n temp.addClass(term.attr('class')).attr('id', term.attr('id'));\n if (term) {\n var style = term.attr('style');\n if (style) {\n style = style.split(/\\s*;\\s*/).filter(function(s) {\n return !s.match(/display\\s*:\\s*none/i);\n }).join(';');\n temp.attr('style', style);\n }\n }\n rect = temp.find('.terminal-line')[0].getBoundingClientRect();\n }\n var result = {\n width: rect.width,\n height: rect.height\n };\n if (temp) {\n temp.remove();\n }\n return result;\n }\n // -----------------------------------------------------------------------\n // :: calculate numbers of characters\n // -----------------------------------------------------------------------\n function get_num_chars(terminal, char_size) {\n var width = terminal.find('.terminal-fill').width();\n var result = Math.floor(width / char_size.width);\n // random number to not get NaN in node.js but big enough to\n // not wrap exception\n return result || 1000;\n }\n // -----------------------------------------------------------------------\n // :: Calculate number of lines that fit without scroll\n // -----------------------------------------------------------------------\n function get_num_rows(terminal, char_size) {\n var height = terminal.find('.terminal-fill').height();\n return Math.floor(height / char_size.height);\n }\n // -----------------------------------------------------------------------\n function all(array, fn) {\n var same = array.filter(function(item) {\n return item[fn]() === item;\n });\n return same.length === array.length;\n }\n // -----------------------------------------------------------------------\n function string_case(string) {\n var array = string.split('');\n if (all(array, 'toLowerCase')) {\n return 'lower';\n } else if (all(array, 'toUpperCase')) {\n return 'upper';\n } else {\n return 'mixed';\n }\n }\n // -----------------------------------------------------------------------\n function same_case(string) {\n return string_case(string) !== 'mixed';\n }\n // -----------------------------------------------------------------------\n // fix for jQuery bug\n function is_function(object) {\n return get_type(object) === 'function';\n }\n // -----------------------------------------------------------------------\n function is_object(object) {\n return object && typeof object === 'object';\n }\n // -----------------------------------------------------------------------\n function is_promise(object) {\n return is_object(object) && is_function(object.then || object.done);\n }\n // -----------------------------------------------------------------------\n function is_deferred(object) {\n return is_promise(object) && is_function(object.promise);\n }\n // -----------------------------------------------------------------------\n if (!Array.isArray) {\n Array.isArray = function(arg) {\n return Object.prototype.toString.call(arg) === '[object Array]';\n };\n }\n // -----------------------------------------------------------------------\n function is_array(object) {\n return Array.isArray(object);\n }\n // -----------------------------------------------------------------------\n function get_type(object) {\n if (typeof object === 'function') {\n return 'function';\n }\n if (object === null) {\n return object + '';\n }\n if (Array.isArray(object)) {\n return 'array';\n }\n if (typeof object === 'object') {\n return 'object';\n }\n return typeof object;\n }\n // -----------------------------------------------------------------------\n // :: TERMINAL PLUGIN CODE\n // -----------------------------------------------------------------------\n var version_set = !$.terminal.version.match(/^\\{\\{/);\n var copyright = 'Copyright (c) 2011-2021 Jakub T. Jankiewicz ' +\n '';\n var version_string = version_set ? ' v. ' + $.terminal.version : ' ';\n // regex is for placing version string aligned to the right\n var reg = new RegExp(' {' + version_string.length + '}$');\n var name_ver = 'jQuery Terminal Emulator' +\n (version_set ? version_string : '');\n // -----------------------------------------------------------------------\n // :: Terminal Signatures\n // -----------------------------------------------------------------------\n var signatures = [\n ['jQuery Terminal', '(c) 2011-2021 jcubic'],\n [name_ver, copyright.replace(/^Copyright | *<.*>/g, '')],\n [name_ver, copyright.replace(/^Copyright /, '')],\n [\n ' _______ ________ __',\n ' / / _ /_ ____________ _/__ ___/______________ _____ / /',\n ' __ / / // / // / _ / _/ // / / / _ / _/ / / \\\\/ / _ \\\\/ /',\n '/ / / // / // / ___/ // // / / / ___/ // / / / / /\\\\ / // / /__',\n '\\\\___/____ \\\\\\\\__/____/_/ \\\\__ / /_/____/_//_/_/_/_/_/ \\\\/\\\\__\\\\_\\\\___/',\n ' \\\\/ /____/ '\n .replace(reg, ' ') + version_string,\n copyright\n ],\n [\n ' __ _____ ________ ' +\n ' __',\n ' / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ ' +\n ' / /',\n ' __ / // // // // // _ // _// // / / // _ // _// // // \\\\/ // _ ' +\n '\\\\/ /',\n '/ / // // // // // ___// / / // / / // ___// / / / / // // /\\\\ // // ' +\n '/ /__',\n '\\\\___//____ \\\\\\\\___//____//_/ _\\\\_ / /_//____//_/ /_/ /_//_//_/ /_/ \\\\' +\n '__\\\\_\\\\___/',\n (' \\\\/ /____/ ' +\n ' ').replace(reg, '') + version_string,\n copyright\n ]\n ];\n // -----------------------------------------------------------------------\n // :: Default options\n // -----------------------------------------------------------------------\n $.terminal.nested_formatting.__meta__ = true;\n // if set to false nested formatting will not inherit styles colors and attribues\n $.terminal.nested_formatting.__inherit__ = true;\n // nested formatting will always return different length so we silent the warning\n $.terminal.nested_formatting.__no_warn__ = true;\n $.terminal.defaults = {\n prompt: '> ',\n history: true,\n exit: true,\n clear: true,\n enabled: true,\n maskChar: '*',\n wrap: true,\n checkArity: true,\n raw: false,\n tabindex: 1,\n invokeMethods: false,\n exceptionHandler: null,\n pauseEvents: true,\n softPause: false,\n mousewheel: null,\n touchscroll: null,\n memory: false,\n cancelableAjax: true,\n processArguments: true,\n execAnimation: false,\n execAnimationDelay: 100,\n linksNoReferrer: false,\n useCache: true,\n anyLinks: false,\n linksNoFollow: false,\n processRPCResponse: null,\n completionEscape: true,\n onCommandChange: null,\n mobileDelete: is_mobile,\n onPositionChange: null,\n convertLinks: true,\n extra: {},\n tabs: 4,\n historySize: 60,\n scrollObject: null,\n historyState: false,\n importHistory: false,\n historyFilter: null,\n echoCommand: true,\n scrollOnEcho: true,\n login: null,\n outputLimit: -1,\n formatters: [$.terminal.nested_formatting],\n unixFormatting: {\n escapeBrackets: false,\n ansiParser: {},\n ansiArt: false\n },\n onAjaxError: null,\n pasteImage: true,\n scrollBottomOffset: 20,\n wordAutocomplete: true,\n caseSensitiveAutocomplete: true,\n caseSensitiveSearch: true,\n clickTimeout: 200,\n holdTimeout: 400,\n holdRepeatTimeout: 200,\n repeatTimeoutKeys: [],\n mobileIngoreAutoSpace: [],\n request: $.noop,\n response: $.noop,\n describe: 'procs',\n onRPCError: null,\n keymap: null,\n doubleTab: null,\n doubleTabEchoCommand: false,\n completion: false,\n onInit: $.noop,\n onClear: $.noop,\n onBlur: $.noop,\n onFocus: $.noop,\n onTerminalChange: $.noop,\n onExit: $.noop,\n onPush: $.noop,\n onPop: $.noop,\n keypress: $.noop,\n keydown: $.noop,\n renderHandler: null,\n onAfterRedraw: $.noop,\n onEchoCommand: $.noop,\n onPaste: $.noop,\n onFlush: $.noop,\n onBeforeCommand: null,\n onAfterCommand: null,\n onBeforeEcho: null,\n onAfterEcho: null,\n onBeforeLogin: null,\n onAfterLogout: null,\n onBeforeLogout: null,\n allowedAttributes: ['title', /^aria-/, 'id', /^data-/],\n strings: {\n comletionParameters: 'From version 1.0.0 completion function need to' +\n ' have two arguments',\n wrongPasswordTryAgain: 'Wrong username or password try again!',\n wrongPassword: 'Wrong username or password!',\n ajaxAbortError: 'Error while aborting ajax call!',\n wrongArity: \"Wrong number of arguments. Function '%s' expects %s got\" +\n ' %s!',\n commandNotFound: \"Command '%s' Not Found!\",\n oneRPCWithIgnore: 'You can use only one rpc with describe == false ' +\n 'or rpc without system.describe',\n oneInterpreterFunction: \"You can't use more than one function (rpc \" +\n 'without system.describe or with option describe == false count' +\n 's as one)',\n loginFunctionMissing: \"You didn't specify a login function\",\n noTokenError: 'Access denied (no token)',\n serverResponse: 'Server responded',\n wrongGreetings: 'Wrong value of greetings parameter',\n notWhileLogin: \"You can't call `%s' function while in login\",\n loginIsNotAFunction: 'Authenticate must be a function',\n canExitError: \"You can't exit from main interpreter\",\n invalidCompletion: 'Invalid completion',\n invalidSelector: 'Sorry, but terminal said that you use invalid ' +\n 'selector!',\n invalidTerminalId: 'Invalid Terminal ID',\n login: 'login',\n password: 'password',\n recursiveLoop: 'Recursive loop in echo detected, skip',\n notAString: '%s function: argument is not a string',\n redrawError: 'Internal error, wrong position in cmd redraw',\n invalidStrings: 'Command %s have unclosed strings',\n defunctTerminal: \"You can't call method on terminal that was destroyed\"\n }\n };\n // -------------------------------------------------------------------------\n // :: All terminal globals\n // -------------------------------------------------------------------------\n var requests = []; // for canceling on CTRL+D\n var terminals = new Cycle(); // list of terminals global in this scope\n // state for all terminals, terminals can't have own array fo state because\n // there is only one popstate event\n var save_state = []; // hold objects returned by export_view by history API\n var hash_commands;\n var change_hash = false; // don't change hash on Init\n var fire_hash_change = true;\n var first_instance = true; // used by history state\n $.fn.terminal = function(init_interpreter, options) {\n function StorageHelper(memory) {\n if (memory) {\n this.storage = {};\n }\n this.set = function(key, value) {\n if (memory) {\n this.storage[key] = value;\n } else {\n $.Storage.set(key, value);\n }\n };\n this.get = function(key) {\n if (memory) {\n return this.storage[key];\n } else {\n return $.Storage.get(key);\n }\n };\n this.remove = function(key) {\n if (memory) {\n delete this.storage[key];\n } else {\n $.Storage.remove(key);\n }\n };\n }\n // ---------------------------------------------------------------------\n // :: helper function\n // ---------------------------------------------------------------------\n function get_processed_command(command) {\n if ($.terminal.unclosed_strings(command)) {\n var string = $.terminal.escape_brackets(command);\n var message = sprintf(strings().invalidStrings, \"`\" + string + \"`\");\n throw new $.terminal.Exception(message);\n } else if (is_function(settings.processArguments)) {\n return process_command(command, settings.processArguments);\n } else if (settings.processArguments) {\n return $.terminal.parse_command(command);\n } else {\n return $.terminal.split_command(command);\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function that use option to render objects\n // ---------------------------------------------------------------------\n function preprocess_value(value, options) {\n if ($.terminal.Animation && value instanceof $.terminal.Animation) {\n value.start(self);\n return false;\n }\n if (is_function(settings.renderHandler)) {\n var ret = settings.renderHandler.call(self, value, options, self);\n if (ret === false) {\n return false;\n }\n if (typeof ret === 'string' || is_node(ret) || is_promise(ret)) {\n return ret;\n } else {\n return value;\n }\n }\n return value;\n }\n // ---------------------------------------------------------------------\n // :: call when line is out of view when outputLimit is used\n // :: NOTE: it's not called when less plugin is used onClear is called\n // :: instead because less call term::clear() after export old view\n // ---------------------------------------------------------------------\n function unmount(node) {\n var index = node.data('index');\n var line = lines[index];\n var options = line[1];\n if (is_function(options.unmount)) {\n options.unmount.call(self, node);\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function used in render and in update\n // ---------------------------------------------------------------------\n function prepare_render(value, options) {\n if (is_node(value)) {\n var settings = $.extend({}, options, {\n raw: true,\n finalize: function(div) {\n div.find('.terminal-render-item').replaceWith(value);\n if (options && is_function(options.finalize)) {\n options.finalize(div, self);\n }\n }\n });\n return ['
', settings];\n }\n }\n // ---------------------------------------------------------------------\n // :: helper function that renders DOM nodes and jQuery objects\n // ---------------------------------------------------------------------\n function render(value, options) {\n var ret = prepare_render(value, options);\n if (ret) {\n self.echo.apply(self, ret);\n return true;\n }\n }\n // ---------------------------------------------------------------------\n function get_node(index) {\n return output.find('[data-index=' + index + ']');\n }\n // ---------------------------------------------------------------------\n // :: test if object can be rendered\n // ---------------------------------------------------------------------\n function is_node(object) {\n return object instanceof $.fn.init || object instanceof Element;\n }\n // ---------------------------------------------------------------------\n // :: Display object on terminal\n // ---------------------------------------------------------------------\n function display_object(object) {\n object = preprocess_value(object);\n if (object === false) {\n return;\n }\n if (render(object)) {\n return;\n }\n if (typeof object === 'string') {\n self.echo(object);\n } else if (is_array(object)) {\n self.echo($.map(object, function(object) {\n return JSON.stringify(object);\n }).join(' '));\n } else if (typeof object === 'object') {\n self.echo(JSON.stringify(object));\n } else {\n self.echo(object);\n }\n }\n // ---------------------------------------------------------------------\n // :: Display line code in the file if line numbers are present\n // ---------------------------------------------------------------------\n function print_line(url_spec, cols) {\n var re = /(.*):([0-9]+):([0-9]+)$/;\n // google chrome have line and column after filename\n var m = url_spec.match(re);\n if (m) {\n // TODO: do we need to call pause/resume or return promise?\n self.pause(settings.softPause);\n $.get(m[1], function(response) {\n var file = m[1];\n var code = response.split('\\n');\n var n = +m[2] - 1;\n var start = n > 2 ? n - 2 : 0;\n var lines = code.slice(start, n + 3).map(function(line, i) {\n var prefix = '[' + (n + i - 1) + ']: ';\n var limit = cols - prefix.length - 4;\n if (line.length > limit) {\n line = line.substring(0, limit) + '...';\n }\n if (n > 2 ? i === 2 : i === n) {\n line = '[[;#f00;]' +\n $.terminal.escape_brackets(line) + ']';\n }\n return prefix + line;\n }).filter(Boolean).join('\\n');\n if (lines.length) {\n self.echo('[[b;white;]' + file + ']');\n self.echo(lines).resume();\n }\n }, 'text');\n }\n }\n // ---------------------------------------------------------------------\n // :: Helper function\n // ---------------------------------------------------------------------\n function display_json_rpc_error(error) {\n if (is_function(settings.onRPCError)) {\n settings.onRPCError.call(self, error);\n } else {\n self.error('[RPC] ' + error.message);\n if (error.error && error.error.message) {\n error = error.error;\n // more detailed error message\n var msg = '\\t' + error.message;\n if (error.file) {\n msg += ' in file \"' + error.file.replace(/.*\\//, '') + '\"';\n }\n if (error.at) {\n msg += ' at line ' + error.at;\n }\n self.error(msg);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Create interpreter function from url string\n // ---------------------------------------------------------------------\n function make_basic_json_rpc(url, auth) {\n var interpreter = function(method, params) {\n self.pause(settings.softPause);\n $.jrpc({\n url: url,\n method: method,\n params: params,\n request: function(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n response: function(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n success: function success(json) {\n if (json.error) {\n display_json_rpc_error(json.error);\n } else if (is_function(settings.processRPCResponse)) {\n settings.processRPCResponse.call(self, json.result, self);\n } else if (json.result !== null) {\n display_object(json.result);\n }\n self.resume();\n },\n error: ajax_error\n });\n };\n // this is the interpreter function\n return function(command, terminal) {\n if (command === '') {\n return;\n }\n try {\n command = get_processed_command(command);\n } catch (e) {\n // exception can be thrown on invalid regex\n display_exception(e, 'TERMINAL (get_processed_command)');\n return;\n // throw e; // this will show stack in other try..catch\n }\n if (!auth || command.name === 'help') {\n // allows to call help without a token\n interpreter(command.name, command.args);\n } else {\n var token = terminal.token(true);\n if (token) {\n interpreter(command.name, [token].concat(command.args));\n } else {\n // should never happen\n terminal.error('[AUTH] ' + strings().noTokenError);\n }\n }\n };\n }\n // ---------------------------------------------------------------------\n // :: Create interpreter function from Object. If the value is object\n // :: it will create nested interpreters\n // ---------------------------------------------------------------------\n function make_object_interpreter(object, arity, login, fallback) {\n // function that maps commands to object methods\n // it keeps terminal context\n return function(user_command, terminal) {\n if (user_command === '') {\n return;\n }\n var command;\n try {\n command = get_processed_command(user_command);\n } catch (e) {\n // exception can be thrown on invalid regex\n if (is_function(settings.exception)) {\n settings.exception(e, self);\n } else {\n self.error('Error: ' + (e.message || e));\n }\n return;\n // throw e; // this will show stack in other try..catch\n }\n var val = object[command.name];\n var type = get_type(val);\n if (type === 'function') {\n if (arity && val.length !== command.args.length) {\n self.error(\n '[Arity] ' +\n sprintf(\n strings().wrongArity,\n command.name,\n val.length,\n command.args.length\n )\n );\n } else {\n return val.apply(self, command.args);\n }\n } else if (type === 'object' || type === 'string') {\n var commands = [];\n if (type === 'object') {\n commands = Object.keys(val);\n val = make_object_interpreter(\n val,\n arity,\n login\n );\n }\n terminal.push(val, {\n prompt: command.name + '> ',\n name: command.name,\n completion: type === 'object' ? commands : undefined\n });\n } else if (is_function(fallback)) {\n fallback(user_command, self);\n } else if (is_function(settings.onCommandNotFound)) {\n settings.onCommandNotFound.call(self, user_command, self);\n } else {\n terminal.error(sprintf(strings().commandNotFound, command.name));\n }\n };\n }\n // ---------------------------------------------------------------------\n function ajax_error(xhr, status, error) {\n self.resume(); // onAjaxError can use pause/resume call it first\n if (is_function(settings.onAjaxError)) {\n settings.onAjaxError.call(self, xhr, status, error);\n } else if (status !== 'abort') {\n self.error('[AJAX] ' + status + ' - ' +\n strings().serverResponse + ':\\n' +\n $.terminal.escape_brackets(xhr.responseText));\n }\n }\n // ---------------------------------------------------------------------\n // :: function create interpreter object based on JSON-RPC meta data\n // ---------------------------------------------------------------------\n function make_json_rpc_object(url, auth, success) {\n function jrpc_success(json) {\n if (json.error) {\n display_json_rpc_error(json.error);\n } else if (is_function(settings.processRPCResponse)) {\n settings.processRPCResponse.call(self, json.result, self);\n } else {\n display_object(json.result);\n }\n self.resume();\n }\n function jrpc_request(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n }\n function jrpc_response(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n }\n function response(response) {\n var procs = response;\n // we check if it's false before we call this function but\n // it don't hurt to be explicit here\n if (settings.describe !== false && settings.describe !== '') {\n settings.describe.split('.').forEach(function(field) {\n procs = procs[field];\n });\n }\n if (procs && procs.length) {\n var interpreter_object = {};\n $.each(procs, function(_, proc) {\n if ($.isPlainObject(proc) && typeof proc.name === 'string') {\n interpreter_object[proc.name] = function() {\n var append = auth && proc.name !== 'help';\n var args = Array.prototype.slice.call(arguments);\n var args_len = args.length + (append ? 1 : 0);\n if (settings.checkArity && proc.params &&\n proc.params.length !== args_len) {\n self.error(\n '[Arity] ' +\n sprintf(\n strings().wrongArity,\n proc.name,\n proc.params.length,\n args_len\n )\n );\n } else {\n self.pause(settings.softPause);\n if (append) {\n var token = self.token(true);\n if (token) {\n args = [token].concat(args);\n } else {\n self.error('[AUTH] ' +\n strings().noTokenError);\n }\n }\n $.jrpc({\n url: url,\n method: proc.name,\n params: args,\n request: jrpc_request,\n response: jrpc_response,\n success: jrpc_success,\n error: ajax_error\n });\n }\n };\n }\n });\n var login = typeof auth === 'string' ? auth : 'login';\n interpreter_object.help = interpreter_object.help || function(fn) {\n if (typeof fn === 'undefined') {\n var names = procs.map(function(proc) {\n return proc.name;\n }).join(', ') + ', help';\n self.echo('Available commands: ' + names);\n } else {\n var found = false;\n $.each(procs, function(_, proc) {\n if (proc.name === fn) {\n found = true;\n var msg = '';\n msg += '[[bu;;]' + proc.name + ']';\n if (proc.params) {\n var params = proc.params;\n if (auth && proc.name !== login) {\n params = params.slice(1);\n }\n msg += ' ' + params.join(' ');\n }\n if (proc.help) {\n msg += '\\n' + proc.help;\n }\n self.echo(msg);\n return false;\n }\n });\n if (!found) {\n if (fn === 'help') {\n self.echo('[[bu;;]help] [method]\\ndisplay help ' +\n 'for the method or list of methods if not' +\n ' specified');\n } else {\n var msg = 'Method `' + fn + \"' not found \";\n self.error(msg);\n }\n }\n }\n };\n success(interpreter_object);\n } else {\n success(null);\n }\n }\n return $.jrpc({\n url: url,\n method: 'system.describe',\n params: [],\n success: response,\n request: jrpc_request,\n response: jrpc_response,\n error: function error() {\n success(null);\n }\n });\n }\n // ---------------------------------------------------------------------\n // :: function create interpeter function and call finalize with\n // :: interpreter and optional completion\n // ---------------------------------------------------------------------\n function make_interpreter(user_intrp, login, finalize) {\n finalize = finalize || $.noop;\n var type = get_type(user_intrp);\n var object;\n var result = {};\n var rpc_count = 0; // only one rpc can be use for array\n var fn_interpreter;\n if (type === 'array') {\n object = {};\n // recur will be called when previous acync call is finished\n (function recur(interpreters, success) {\n if (interpreters.length) {\n var first = interpreters[0];\n var rest = interpreters.slice(1);\n var type = get_type(first);\n if (type === 'string') {\n self.pause(settings.softPause);\n if (settings.describe === false) {\n if (++rpc_count === 1) {\n fn_interpreter = make_basic_json_rpc(first, login);\n } else {\n self.error(strings().oneRPCWithIgnore);\n }\n recur(rest, success);\n } else {\n make_json_rpc_object(first, login, function(new_obj) {\n if (new_obj) {\n $.extend(object, new_obj);\n } else if (++rpc_count === 1) {\n fn_interpreter = make_basic_json_rpc(\n first,\n login\n );\n } else {\n self.error(strings().oneRPCWithIgnore);\n }\n self.resume();\n recur(rest, success);\n });\n }\n } else if (type === 'function') {\n if (fn_interpreter) {\n self.error(strings().oneInterpreterFunction);\n } else {\n fn_interpreter = first;\n }\n recur(rest, success);\n } else if (type === 'object') {\n $.extend(object, first);\n recur(rest, success);\n }\n } else {\n success();\n }\n })(user_intrp, function() {\n finalize({\n interpreter: make_object_interpreter(\n object,\n false,\n login,\n fn_interpreter && fn_interpreter.bind(self)\n ),\n completion: Object.keys(object)\n });\n });\n } else if (type === 'string') {\n if (settings.describe === false) {\n object = {\n interpreter: make_basic_json_rpc(user_intrp, login)\n };\n if ($.isArray(settings.completion)) {\n object.completion = settings.completion;\n }\n finalize(object);\n } else {\n self.pause(settings.softPause);\n make_json_rpc_object(user_intrp, login, function(object) {\n if (object) {\n result.interpreter = make_object_interpreter(\n object,\n false,\n login\n );\n result.completion = Object.keys(object);\n } else {\n // no procs in system.describe\n result.interpreter = make_basic_json_rpc(user_intrp, login);\n }\n finalize(result);\n self.resume();\n });\n }\n } else if (type === 'object') {\n finalize({\n interpreter: make_object_interpreter(\n user_intrp,\n settings.checkArity,\n login\n ),\n completion: Object.keys(user_intrp)\n });\n } else {\n // allow $('').terminal();\n if (type === 'undefined') {\n user_intrp = $.noop;\n } else if (type !== 'function') {\n var msg = type + ' is invalid interpreter value';\n throw new $.terminal.Exception(msg);\n }\n // single function don't need bind\n finalize({\n interpreter: user_intrp,\n completion: settings.completion\n });\n }\n }\n // ---------------------------------------------------------------------\n // :: Create JSON-RPC authentication function\n // ---------------------------------------------------------------------\n function make_json_rpc_login(url, login) {\n var method = get_type(login) === 'boolean' ? 'login' : login;\n return function(user, passwd, callback) {\n self.pause(settings.softPause);\n $.jrpc({\n url: url,\n method: method,\n params: [user, passwd],\n request: function(jxhr, request) {\n try {\n settings.request.call(self, jxhr, request, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n response: function(jxhr, response) {\n try {\n settings.response.call(self, jxhr, response, self);\n } catch (e) {\n display_exception(e, 'USER');\n }\n },\n success: function success(response) {\n if (!response.error && response.result) {\n callback(response.result);\n } else {\n // null will trigger message that login fail\n callback(null);\n }\n self.resume();\n },\n error: ajax_error\n });\n };\n // default name is login so you can pass true\n }\n // ---------------------------------------------------------------------\n // :: display Exception on terminal\n // ---------------------------------------------------------------------\n function display_exception(e, label, silent) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, label);\n } else {\n self.exception(e, label);\n if (!silent) {\n setTimeout(function() {\n throw e;\n }, 0);\n }\n }\n }\n // ---------------------------------------------------------------------\n function links(string) {\n function format(_, style, color, background, _class, data, text) {\n function formatting(s, text) {\n return '[[' + [\n style + (s || ''),\n color,\n background,\n _class,\n text || data\n ].join(';') + ']';\n }\n function escaped(_) {\n return ']' + formatting('!', _) + _ + ']' + formatting();\n }\n if (!style.match(/!/)) {\n var m = text.match(email_full_re) || text.match(url_full_re);\n if (m) {\n return formatting('!', m[1]) + text + ']';\n } else if (text.match(email_re) || text.match(url_nf_re)) {\n var output = text.replace(email_re, escaped)\n .replace(url_nf_re, escaped);\n return formatting('', data) + output + ']';\n }\n }\n return _;\n }\n function linkify(string) {\n return string.replace(email_re, '[[!;;]$1]').\n replace(url_nf_re, '[[!;;]$1]');\n }\n if (!$.terminal.have_formatting(string)) {\n return linkify(string);\n }\n return $.terminal.format_split(string).map(function(str) {\n if ($.terminal.is_formatting(str)) {\n return str.replace(format_parts_re, format);\n } else {\n return linkify(str);\n }\n }).join('');\n }\n // ---------------------------------------------------------------------\n function should_wrap(string, options) {\n return (strlen(text(string)) > options.cols ||\n string.match(/\\n/)) &&\n ((settings.wrap === true &&\n options.wrap === undefined) ||\n settings.wrap === false &&\n options.wrap === true);\n }\n // ---------------------------------------------------------------------\n var line_cache;\n if ('Map' in root) {\n line_cache = new Map();\n }\n // ---------------------------------------------------------------------\n function process_extended_commands(string, line, line_settings) {\n if (line_settings.exec || line.options.clear_exec) {\n return $.terminal.each_extended_command(string, function(command) {\n // redraw should not execute commands and it have\n // and lines variable have all extended commands\n if (line_settings.exec) {\n line.options.exec = false;\n line.options.clear_exec = true;\n var trim = command.trim();\n if (prev_exec_cmd && prev_exec_cmd === trim) {\n prev_exec_cmd = '';\n self.error(strings().recursiveLoop);\n } else {\n prev_exec_cmd = trim;\n $.terminal.extended_command(self, command, {\n invokeMethods: line_settings.invokeMethods\n }).then(function() {\n prev_exec_cmd = '';\n });\n }\n }\n });\n }\n return string;\n }\n // ---------------------------------------------------------------------\n function process_line(line) {\n // prevent exception in display exception\n try {\n var use_cache = !is_function(line.value);\n var line_settings = $.extend({\n exec: true,\n raw: false,\n finalize: $.noop,\n useCache: use_cache,\n invokeMethods: false,\n formatters: true,\n convertLinks: settings.convertLinks\n }, line.options || {});\n var string = stringify_value(line.value);\n if (string && is_function(string.then)) {\n // handle function that return a promise #629\n return string.then(function(string) {\n process_line($.extend(line, {\n value: string,\n options: line_settings\n }));\n });\n }\n if (string !== '') {\n if (!line_settings.raw) {\n if (settings.useCache && line_settings.useCache) {\n var key = string;\n if (line_cache && line_cache.has(key)) {\n var data = line_cache.get(key);\n buffer.append(\n data.input,\n line.index,\n line_settings,\n data.raw\n );\n return true;\n }\n }\n if (line_settings.formatters) {\n try {\n string = $.terminal.apply_formatters(\n string,\n $.extend(settings, {echo: true})\n );\n } catch (e) {\n display_exception(e, 'FORMATTING');\n }\n }\n string = process_extended_commands(string, line, line_settings);\n if (string === '') {\n return;\n }\n if (line_settings.convertLinks) {\n string = links(string);\n }\n var raw_string = string;\n string = crlf($.terminal.normalize(string));\n string = $.terminal.encode(string, {\n tabs: settings.tabs\n });\n //string = $.terminal.normalize(string);\n var array;\n var cols = line_settings.cols = self.cols();\n if (should_wrap(string, line_settings)) {\n var words = line_settings.keepWords;\n array = $.terminal.split_equal(string, cols, words);\n } else if (string.match(/\\n/)) {\n array = string.split(/\\n/);\n }\n }\n } else {\n raw_string = '';\n }\n var arg = array || string;\n if (line_cache && key && use_cache) {\n line_cache.set(key, {input: arg, raw: raw_string});\n }\n buffer.append(arg, line.index, line_settings, raw_string);\n } catch (e) {\n buffer.clear();\n // don't display exception if exception throw in terminal\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL');\n } else {\n alert_exception('[Internal Exception(process_line)]', e);\n }\n }\n // is it work with unpromise that ignore undefined\n return true;\n }\n // ---------------------------------------------------------------------\n // :: Update terminal lines\n // ---------------------------------------------------------------------\n function redraw(options) {\n options = $.extend({}, {\n // should be used when single line is updated\n update: false,\n // should be used if you want to scroll to bottom after redraw\n scroll: true\n }, options || {});\n if (!options.update) {\n command_line.resize(num_chars);\n // we don't want reflow while processing lines\n var detached_output = output.empty().detach();\n }\n try {\n buffer.clear();\n unpromise(lines.render(self.rows(), function(lines_to_show) {\n return lines_to_show.map(function(line) {\n return process_line(line);\n });\n }), function() {\n self.flush(options);\n if (!options.update) {\n command_line.before(detached_output); // reinsert output\n }\n fire_event('onAfterRedraw');\n });\n } catch (e) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL (redraw)');\n } else {\n alert_exception('[redraw]', e);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Function limit output lines based on outputLimit option\n // ---------------------------------------------------------------------\n function limit_lines() {\n if (settings.outputLimit >= 0) {\n var limit;\n if (settings.outputLimit === 0) {\n limit = self.rows();\n } else {\n limit = settings.outputLimit;\n }\n var $lines = output.find('> div > div');\n if ($lines.length + 1 > limit) {\n var max = $lines.length - limit + 1;\n var for_remove = $lines.slice(0, max);\n // you can't get parent if you remove the\n // element so we first get the parent\n var parents = for_remove.parent();\n for_remove.remove();\n parents.each(function() {\n var $self = $(this);\n if ($self.is(':empty')) {\n unmount($self);\n // there can be divs inside parent that\n // was not removed\n $self.remove();\n }\n });\n lines.limit_snapshot(max);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Display user greetings or terminal signature\n // ---------------------------------------------------------------------\n function show_greetings() {\n if (settings.greetings === undefined) {\n // signature have ascii art so it's not suite for screen readers\n self.echo(self.signature, {finalize: a11y_hide, formatters: false});\n } else if (settings.greetings) {\n var type = typeof settings.greetings;\n if (type === 'string') {\n self.echo(settings.greetings);\n } else if (type === 'function') {\n self.echo(function() {\n try {\n return settings.greetings.call(self, self.echo);\n } catch (e) {\n settings.greetings = null;\n display_exception(e, 'greetings');\n }\n });\n } else {\n self.error(strings().wrongGreetings);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Display prompt and last command\n // ---------------------------------------------------------------------\n function echo_command(command) {\n if (typeof command === 'undefined') {\n command = self.get_command();\n }\n // true will return last rendered string\n var prompt = command_line.prompt(true);\n var mask = command_line.mask();\n switch (typeof mask) {\n case 'string':\n command = command.replace(/./g, mask);\n break;\n case 'boolean':\n if (mask) {\n command = command.replace(/./g, settings.maskChar);\n } else {\n command = $.terminal.escape_formatting(command);\n }\n break;\n }\n var options = {\n exec: false,\n formatters: false,\n finalize: function finalize(div) {\n a11y_hide(div.addClass('terminal-command'));\n fire_event('onEchoCommand', [div, command]);\n }\n };\n command = $.terminal.apply_formatters(command, {command: true});\n self.echo(prompt + command, options);\n }\n // ---------------------------------------------------------------------\n function have_scrollbar() {\n return fill.outerWidth() !== self.outerWidth();\n }\n // ---------------------------------------------------------------------\n // :: Helper function that restore state. Call import_view or exec\n // ---------------------------------------------------------------------\n function restore_state(spec) {\n // spec [terminal_id, state_index, command]\n var terminal = terminals.get()[spec[0]];\n if (!terminal) {\n throw new $.terminal.Exception(strings().invalidTerminalId);\n }\n var command_idx = spec[1];\n if (save_state[command_idx]) { // state exists\n terminal.import_view(save_state[command_idx]);\n } else {\n // restore state\n change_hash = false;\n var command = spec[2];\n if (command) {\n terminal.exec(command).done(function() {\n change_hash = true;\n save_state[command_idx] = terminal.export_view();\n });\n }\n }\n /*if (spec[3].length) {\n restore_state(spec[3]);\n }*/\n }\n // ---------------------------------------------------------------------\n function make_label_error(label) {\n return function(e) {\n self.error('[' + label + '] ' + (e.message || e)).resume();\n };\n }\n // ---------------------------------------------------------------------\n // :: Helper function\n // ---------------------------------------------------------------------\n function maybe_update_hash() {\n if (change_hash) {\n fire_hash_change = false;\n location.hash = '#' + JSON.stringify(hash_commands);\n setTimeout(function() {\n fire_hash_change = true;\n }, 100);\n }\n }\n // ---------------------------------------------------------------------\n // :: Wrapper over interpreter, it implements exit and catches all\n // :: exeptions from user code and displays them on the terminal\n // ---------------------------------------------------------------------\n var first_command = true;\n var resume_callbacks = [];\n function commands(command, silent, exec) {\n function init_state() {\n // execHash need first empty command too\n if (settings.historyState || settings.execHash && exec) {\n if (!save_state.length) {\n // first command in first terminal don't have hash\n self.save_state();\n } else {\n self.save_state(null);\n }\n }\n }\n // -----------------------------------------------------------------\n function before_async_exec() {\n // variables defined later in commands\n if (!exec) {\n change_hash = true;\n if (settings.historyState) {\n self.save_state(command, false);\n }\n change_hash = saved_change_hash;\n }\n }\n // -----------------------------------------------------------------\n function after_exec() {\n deferred.resolve();\n fire_event('onAfterCommand', [command]);\n }\n // -----------------------------------------------------------------\n function show(result) {\n if (typeof result !== 'undefined') {\n display_object(result);\n }\n after_exec();\n self.resume();\n }\n // -----------------------------------------------------------------\n function is_animation_promise(ret) {\n return is_function(ret.done || ret.then) && animating;\n }\n // -----------------------------------------------------------------\n function invoke() {\n // Call user interpreter function\n var result = interpreter.interpreter.call(self, command, self);\n before_async_exec();\n if (result) {\n // auto pause/resume when user return promises\n // it should not pause when user return promise from read()\n if (!force_awake) {\n if (is_animation_promise(result)) {\n paused = true;\n } else {\n self.pause(settings.softPause);\n }\n }\n force_awake = false;\n var error = make_label_error('Command');\n // when for native Promise object work only in jQuery 3.x\n if (is_function(result.done || result.then)) {\n return unpromise(result, show, error);\n } else {\n return $.when(result).done(show).catch(error);\n }\n } else {\n if (paused) {\n resume_callbacks.push(function() {\n // exec with resume/pause in user code\n after_exec();\n });\n } else {\n after_exec();\n }\n return deferred.promise();\n }\n }\n // -----------------------------------------------------------------\n // first command store state of the terminal before the command get\n // executed\n if (first_command) {\n first_command = false;\n init_state();\n }\n try {\n // this callback can disable commands\n if (fire_event('onBeforeCommand', [command]) === false) {\n return;\n }\n if (exec) {\n prev_exec_cmd = command.trim();\n prev_command = $.terminal.split_command(prev_exec_cmd);\n } else {\n prev_command = $.terminal.split_command(command);\n }\n if (!ghost()) {\n // exec execute this function wihout the help of cmd plugin\n // that add command to history on enter\n if (exec && (is_function(settings.historyFilter) &&\n settings.historyFilter(command) ||\n command.match(settings.historyFilter))) {\n command_line.history().append(command);\n }\n }\n var interpreter = interpreters.top();\n if (!silent && settings.echoCommand) {\n echo_command(command);\n }\n // new promise will be returned to exec that will resolve his\n // returned promise\n var deferred = new $.Deferred();\n // we need to save sate before commands is deleyd because\n // execute_extended_command disable it and it can be executed\n // after delay\n var saved_change_hash = change_hash;\n if (command.match(/^\\s*login\\s*$/) && self.token(true)) {\n before_async_exec();\n if (self.level() > 1) {\n self.logout(true);\n } else {\n self.logout();\n }\n after_exec();\n } else if (settings.exit && command.match(/^\\s*exit\\s*$/) &&\n !in_login) {\n before_async_exec();\n var level = self.level();\n if (level === 1 && self.get_token() || level > 1) {\n if (self.get_token(true)) {\n self.set_token(undefined, true);\n }\n self.pop();\n }\n after_exec();\n } else if (settings.clear && command.match(/^\\s*clear\\s*$/) &&\n !in_login) {\n before_async_exec();\n self.clear();\n after_exec();\n } else {\n var ret = invoke();\n if (ret) {\n return ret;\n }\n }\n return deferred.promise();\n } catch (e) {\n display_exception(e, 'USER', exec);\n self.resume();\n if (exec) {\n throw e;\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: The logout function removes Storage, disables history and runs\n // :: the login function. This function is called only when options.login\n // :: function is defined. The check for this is in the self.pop method\n // ---------------------------------------------------------------------\n function global_logout() {\n if (fire_event('onBeforeLogout', [], true) === false) {\n return;\n }\n clear_loging_storage();\n fire_event('onAfterlogout', [], true);\n self.login(global_login_fn, true, initialize);\n }\n // ---------------------------------------------------------------------\n function clear_loging_storage() {\n var name = self.prefix_name(true) + '_';\n storage.remove(name + 'token');\n storage.remove(name + 'login');\n }\n // ---------------------------------------------------------------------\n // :: Save the interpreter name for use with purge\n // ---------------------------------------------------------------------\n function maybe_append_name(interpreter_name) {\n var storage_key = self.prefix_name() + '_interpreters';\n var names = storage.get(storage_key);\n if (names) {\n names = JSON.parse(names);\n } else {\n names = [];\n }\n if ($.inArray(interpreter_name, names) === -1) {\n names.push(interpreter_name);\n storage.set(storage_key, JSON.stringify(names));\n }\n }\n // ---------------------------------------------------------------------\n // :: Function enables history, sets prompt, runs interpreter function\n // ---------------------------------------------------------------------\n function prepare_top_interpreter(silent) {\n var interpreter = interpreters.top();\n var name = self.prefix_name(true);\n if (!ghost()) {\n maybe_append_name(name);\n }\n var login = self.login_name(true);\n command_line.name(name + (login ? '_' + login : ''));\n var prompt = interpreter.prompt;\n if (is_function(prompt)) {\n prompt = context_callback_proxy(prompt);\n }\n if (prompt !== command_line.prompt()) {\n if (is_function(interpreter.prompt)) {\n // prevent flicker of old prompt until async prompt finishes\n command_line.prompt('');\n }\n command_line.prompt(interpreter.prompt);\n }\n if (typeof interpreter.history !== 'undefined') {\n self.history().toggle(interpreter.history);\n }\n if ($.isPlainObject(interpreter.keymap)) {\n command_line.keymap(null).keymap($.extend(\n {},\n terminal_init_keymap,\n $.omap(interpreter.keymap, function(name, fun) {\n return function() {\n var args = [].slice.call(arguments);\n try {\n return fun.apply(self, args);\n } catch (e) {\n display_exception(e, 'USER KEYMAP');\n }\n };\n })\n ));\n }\n command_line.set('');\n init_queue.resolve();\n if (!silent && is_function(interpreter.onStart)) {\n interpreter.onStart.call(self, self);\n }\n }\n // ---------------------------------------------------------------------\n function fire_event(name, args, skip_local) {\n args = (args || []).concat([self]); // create new array\n // even can be fired before interpreters is created\n var top = interpreters && interpreters.top();\n if (top && is_function(top[name]) && !skip_local) {\n try {\n return top[name].apply(self, args);\n } catch (e) {\n delete top[name];\n display_exception(e, name);\n }\n } else if (is_function(settings[name])) {\n try {\n return settings[name].apply(self, args);\n } catch (e) {\n settings[name] = null;\n display_exception(e, name);\n }\n }\n }\n var scroll_to_view = (function() {\n function scroll_to_view(visible) {\n if (!visible) {\n // try catch for Node.js unit tests\n try {\n self.scroll_to(self.find('.cmd-cursor-line'));\n return true;\n } catch (e) {\n return true;\n }\n }\n }\n // we don't want debounce in Unit Tests\n if (typeof global !== 'undefined' && typeof global.it === 'function') {\n return scroll_to_view;\n }\n return debounce(scroll_to_view, 100, {\n leading: true,\n trailing: false\n });\n })();\n // ---------------------------------------------------------------------\n function make_cursor_visible() {\n var cursor = self.find('.cmd-cursor-line');\n return cursor.is_fully_in_viewport(self).then(scroll_to_view);\n }\n // ---------------------------------------------------------------------\n function replace_hash(state) {\n if (typeof history !== 'undefined' && history.replaceState) {\n var new_hash = '#' + JSON.stringify(state);\n var url = location.href.replace(/#.*$/, new_hash);\n history.replaceState(null, '', url);\n }\n }\n // ---------------------------------------------------------------------\n function hashchange() {\n if (fire_hash_change && settings.execHash) {\n try {\n if (location.hash) {\n var hash = location.hash.replace(/^#/, '');\n hash_commands = JSON.parse(decodeURIComponent(hash));\n } else {\n hash_commands = [];\n }\n if (hash_commands.length) {\n restore_state(hash_commands[hash_commands.length - 1]);\n } else if (save_state[0]) {\n self.import_view(save_state[0]);\n }\n } catch (e) {\n display_exception(e, 'TERMINAL');\n }\n }\n }\n // ---------------------------------------------------------------------\n function initialize() {\n prepare_top_interpreter();\n show_greetings();\n if (lines.length) {\n // for case when showing long error before init\n if (echo_delay.length) {\n // for case when greetting is async function\n $.when.apply($, echo_delay).then(self.refresh);\n } else {\n self.refresh();\n }\n }\n function next() {\n onPause = $.noop;\n if (!was_paused && self.enabled()) {\n // resume login if user didn't call pause in onInit\n // if user pause in onInit wait with exec until it\n // resume\n self.resume(true);\n }\n }\n // was_paused flag is workaround for case when user call exec before\n // login and pause in onInit, 3rd exec will have proper timing (will\n // execute after onInit resume)\n var was_paused = false;\n if (is_function(settings.onInit)) {\n onPause = function() { // local in terminal\n was_paused = true;\n };\n var ret;\n try {\n ret = settings.onInit.call(self, self);\n } catch (e) {\n display_exception(e, 'OnInit');\n } finally {\n if (!is_promise(ret)) {\n next();\n } else {\n ret.then(next).catch(function(e) {\n display_exception(e, 'OnInit');\n next();\n });\n }\n }\n }\n if (first_instance) {\n first_instance = false;\n $(window).on('hashchange', hashchange);\n }\n }\n // ---------------------------------------------------------------------\n // :: If Ghost don't store anything in localstorage\n // ---------------------------------------------------------------------\n function ghost() {\n return in_login || command_line.mask() !== false;\n }\n // ---------------------------------------------------------------------\n // :: Keydown event handler\n // ---------------------------------------------------------------------\n function user_key_down(e) {\n var result, top = interpreters.top();\n if (is_function(top.keydown)) {\n result = top.keydown.call(self, e, self);\n if (result !== undefined) {\n return result;\n }\n } else if (is_function(settings.keydown)) {\n result = settings.keydown.call(self, e, self);\n if (result !== undefined) {\n return result;\n }\n }\n }\n var keymap = {\n 'CTRL+D': function(e, original) {\n if (!in_login) {\n if (command_line.get() === '') {\n if (interpreters.size() > 1 ||\n is_function(global_login_fn)) {\n self.pop('');\n } else {\n self.resume();\n }\n } else {\n original();\n }\n }\n return false;\n },\n 'CTRL+C': function() {\n with_selection(function(html) {\n if (html === '') {\n var command = self.get_command();\n var position = self.get_position();\n command = command.slice(0, position) + '^C' +\n command.slice(position + 2);\n echo_command(command);\n self.set_command('');\n } else {\n var clip = self.find('textarea');\n text_to_clipboard(clip, process_selected_html(html));\n }\n });\n return false;\n },\n 'CTRL+L': function() {\n self.clear();\n return false;\n },\n 'TAB': function(e, orignal) {\n // TODO: move this to cmd plugin\n // add completion = array | function\n // !!! Problem complete more then one key need terminal\n var top = interpreters.top(), completion, caseSensitive;\n if (typeof top.caseSensitiveAutocomplete !== 'undefined') {\n caseSensitive = top.caseSensitiveAutocomplete;\n } else {\n caseSensitive = settings.caseSensitiveAutocomplete;\n }\n if (settings.completion &&\n get_type(settings.completion) !== 'boolean' &&\n top.completion === undefined) {\n completion = settings.completion;\n } else {\n completion = top.completion;\n }\n if (completion === 'settings') {\n completion = settings.completion;\n }\n function resolve(commands) {\n // local copy\n commands = commands.slice();\n // default commands should not match for arguments\n if (!self.before_cursor(false).match(/\\s/)) {\n if (settings.clear && $.inArray('clear', commands) === -1) {\n commands.push('clear');\n }\n if (settings.exit && $.inArray('exit', commands) === -1) {\n commands.push('exit');\n }\n }\n self.complete(commands, {\n echo: true,\n word: settings.wordAutocomplete,\n escape: settings.completionEscape,\n caseSensitive: caseSensitive,\n echoCommand: settings.doubleTabEchoCommand,\n doubleTab: settings.doubleTab\n });\n }\n if (completion) {\n switch (get_type(completion)) {\n case 'function':\n var string = self.before_cursor(settings.wordAutocomplete);\n if (completion.length === 3) {\n var error = new Error(strings().comletionParameters);\n display_exception(error, 'USER');\n return false;\n }\n var result = completion.call(self, string, resolve);\n unpromise(result, resolve, make_label_error('Completion'));\n break;\n case 'array':\n resolve(completion);\n break;\n default:\n throw new $.terminal.Exception(strings().invalidCompletion);\n }\n } else {\n orignal();\n }\n return false;\n },\n 'CTRL+V': function(e, original) {\n original(e);\n self.oneTime(200, function() {\n self.scroll_to_bottom();\n });\n return true;\n },\n 'CTRL+TAB': function() {\n if (terminals.length() > 1) {\n self.focus(false);\n return false;\n }\n },\n 'PAGEDOWN': function() {\n self.scroll(self.height());\n },\n 'PAGEUP': function() {\n self.scroll(-self.height());\n }\n };\n // ---------------------------------------------------------------------\n function key_down(e) {\n // Prevent to be executed by cmd: CTRL+D, TAB, CTRL+TAB (if more\n // then one terminal)\n var result, i;\n if (animating) {\n return false;\n }\n if (self.enabled()) {\n if (!self.paused()) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n if (e.which !== 9) { // not a TAB\n tab_count = 0;\n }\n } else {\n if (!settings.pauseEvents) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n }\n if (e.which === 68 && e.ctrlKey) { // CTRL+D (if paused)\n if (settings.pauseEvents) {\n result = user_key_down(e);\n if (result !== undefined) {\n return result;\n }\n }\n if (requests.length) {\n for (i = requests.length; i--;) {\n var r = requests[i];\n if (r.readyState !== 4) {\n try {\n r.abort();\n } catch (error) {\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(\n self,\n e,\n 'AJAX ABORT'\n );\n } else {\n self.error(strings().ajaxAbortError);\n }\n }\n }\n }\n requests = [];\n }\n self.resume();\n }\n return false;\n }\n }\n }\n // ---------------------------------------------------------------------\n function key_press(e) {\n var top = interpreters.top();\n if (enabled && (!paused || !settings.pauseEvents)) {\n if (is_function(top.keypress)) {\n return top.keypress.call(self, e, self);\n } else if (is_function(settings.keypress)) {\n return settings.keypress.call(self, e, self);\n }\n }\n }\n // ---------------------------------------------------------------------\n // :: Typing animation generator\n // ---------------------------------------------------------------------\n function typed(finish_typing_fn) {\n return function typing_animation(message, options) {\n var formattted = $.terminal.apply_formatters(message);\n animating = true;\n var prompt = self.get_prompt();\n var char_i = 0;\n var len = $.terminal.length(formattted);\n if (message.length > 0) {\n var new_prompt = '';\n if (options.prompt) {\n new_prompt = options.prompt;\n } else {\n self.set_prompt('');\n }\n var bottom = self.is_bottom();\n var interval = setInterval(function() {\n var chr = $.terminal.substring(formattted, char_i, char_i + 1);\n new_prompt += chr;\n self.set_prompt(new_prompt);\n if (chr === '\\n' && bottom) {\n self.scroll_to_bottom();\n }\n char_i++;\n if (char_i === len) {\n clearInterval(interval);\n setTimeout(function() {\n // swap command with prompt\n finish_typing_fn(message, prompt, options);\n animating = false;\n }, options.delay);\n }\n }, options.delay);\n }\n };\n }\n // ---------------------------------------------------------------------\n var typed_prompt = typed(function(message, _, options) {\n self.set_prompt(message);\n options.finalize();\n });\n // ---------------------------------------------------------------------\n var typed_message = typed(function(message, prompt, options) {\n self.set_prompt(prompt);\n self.echo(message, $.extend({}, options, {typing: false}));\n });\n // ---------------------------------------------------------------------\n var typed_enter = (function() {\n var helper = typed(function(message, prompt, options) {\n self.set_prompt(prompt);\n with_prompt(prompt, function(prompt) {\n self.echo(prompt + message, $.extend({}, options, {typing: false}));\n }, self);\n });\n return function(prompt, message, options) {\n return helper(message, $.extend({}, options, {prompt: prompt}));\n };\n })();\n // ---------------------------------------------------------------------\n function with_typing(kind, else_fn, finalize_fn) {\n return function with_typing_fn(string, options) {\n var d = new $.Deferred();\n when_ready(function ready() {\n var locals = $.extend({\n typing: false,\n delay: settings.execAnimationDelay\n }, options);\n if (locals.typing) {\n if (typeof string !== 'string') {\n return d.reject(kind + ': Typing animation require string');\n }\n if (typeof locals.delay !== 'number' || isNaN(locals.delay)) {\n return d.reject(kind + ': Invalid argument, delay need to' +\n ' be a number');\n }\n var p = self.typing(kind, locals.delay, string, locals);\n p.then(function() {\n d.resolve();\n });\n } else {\n else_fn(string, locals);\n }\n if (is_function(finalize_fn)) {\n finalize_fn(string, locals);\n }\n });\n if (options && options.typing) {\n return d.promise();\n }\n return self;\n };\n }\n // ---------------------------------------------------------------------\n function ready(queue) {\n return function(fun) {\n queue.add(fun);\n };\n }\n // ---------------------------------------------------------------------\n function strings() {\n return $.extend(\n {},\n $.terminal.defaults.strings,\n settings && settings.strings || {}\n );\n }\n // ---------------------------------------------------------------------\n var self = this;\n if (this.length > 1) {\n return this.each(function() {\n $.fn.terminal.call(\n $(this),\n init_interpreter,\n $.extend({name: self.selector}, options)\n );\n });\n }\n var body_terminal;\n if (self.is('body,html')) {\n // terminal already exists on body\n if (self.hasClass('full-screen-terminal')) {\n var data = self.find('> .terminal').data('terminal');\n if (data) {\n return data;\n }\n }\n body_terminal = self;\n self = $('').appendTo('body');\n $('body').addClass('full-screen-terminal');\n } else if (self.data('terminal')) {\n // terminal already exists\n return self.data('terminal');\n }\n // -----------------------------------------------------------------\n // TERMINAL METHODS\n // -----------------------------------------------------------------\n var public_api = $.omap({\n id: function() {\n return terminal_id;\n },\n // -------------------------------------------------------------\n // :: Clear the output\n // -------------------------------------------------------------\n clear: function() {\n if (fire_event('onClear') !== false) {\n buffer.clear();\n lines.clear(function(i) {\n return get_node(i);\n });\n output[0].innerHTML = '';\n self.prop({scrollTop: 0});\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Return an object that can be used with import_view to\n // :: restore the state\n // -------------------------------------------------------------\n export_view: function() {\n var user_export = fire_event('onExport');\n user_export = user_export || {};\n return $.extend({}, {\n focus: enabled,\n mask: command_line.mask(),\n prompt: self.get_prompt(),\n command: self.get_command(),\n position: command_line.position(),\n lines: clone(lines.data()),\n interpreters: interpreters.clone(),\n history: command_line.history().data\n }, user_export);\n },\n // -------------------------------------------------------------\n // :: Restore the state of the previous exported view\n // -------------------------------------------------------------\n import_view: function(view) {\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'import_view'));\n }\n fire_event('onImport', [view]);\n when_ready(function ready() {\n self.set_prompt(view.prompt);\n self.set_command(view.command);\n command_line.position(view.position);\n command_line.mask(view.mask);\n if (view.focus) {\n self.focus();\n }\n lines.import(clone(view.lines).filter(function(line) {\n return line[0];\n }));\n if (view.interpreters instanceof Stack) {\n interpreters = view.interpreters;\n }\n if (settings.importHistory) {\n command_line.history().set(view.history);\n }\n redraw();\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Store current terminal state\n // -------------------------------------------------------------\n save_state: function(command, ignore_hash, index) {\n // save_state.push({view:self.export_view(), join:[]});\n if (typeof index !== 'undefined') {\n save_state[index] = self.export_view();\n } else {\n save_state.push(self.export_view());\n }\n if (!$.isArray(hash_commands)) {\n hash_commands = [];\n }\n if (command !== undefined && !ignore_hash) {\n var state = [\n terminal_id,\n save_state.length - 1,\n command\n ];\n hash_commands.push(state);\n maybe_update_hash();\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Execute a command, it will handle commands that do AJAX\n // :: calls and have delays, if the second argument is set to\n // :: true it will not echo executed command\n // :: if second argument is object is used as options\n // -------------------------------------------------------------\n exec: function(command, silent, options) {\n function invoke(silent) {\n // commands may return promise from user code\n // it will resolve exec promise when user promise\n // is resolved\n var ret = commands(command, silent, true);\n unpromise(ret, function() {\n // reset prev command for push called after exec\n // so push didn't get name/prompt from exec command\n prev_command = null;\n d.resolve();\n }, function() {\n prev_command = null;\n d.reject();\n });\n }\n if (silent && typeof silent === 'object') {\n options = silent;\n silent = null;\n }\n var exec_settings = $.extend({\n deferred: null,\n silent: false,\n typing: false,\n delay: settings.execAnimationDelay\n }, options);\n if (silent === null) {\n silent = exec_settings.silent;\n }\n if (!is_deferred(exec_settings.deferred)) {\n exec_settings.deferred = new $.Deferred();\n }\n var d = exec_settings.deferred;\n cmd_ready(function ready() {\n if ($.isArray(command)) {\n (function recur() {\n var cmd = command.shift();\n if (cmd) {\n self.exec(cmd, silent, options).done(recur);\n } else {\n d.resolve();\n }\n })();\n } else if (paused) {\n // both commands executed here (resume will call Term::exec)\n // delay command multiple time\n delayed_commands.push([command, silent, exec_settings]);\n } else if (exec_settings.typing && !silent) {\n var delay = exec_settings.delay;\n paused = true;\n var ret = self.typing('enter', delay, command, {\n delay: delay\n });\n ret.then(function() {\n paused = false;\n invoke(true);\n });\n d.then(function() {\n paused = false;\n });\n } else {\n invoke(silent);\n }\n });\n // while testing it didn't executed last exec when using this\n // for resolved deferred\n return d.promise();\n },\n // -------------------------------------------------------------\n // :: bypass login function that wait untill you type user/pass\n // :: it hide implementation detail\n // -------------------------------------------------------------\n autologin: function(user, token, silent) {\n self.trigger('terminal.autologin', [user, token, silent]);\n return self;\n },\n // -------------------------------------------------------------\n // :: Function changes the prompt of the command line to login\n // :: with a password and calls the user login function with\n // :: the callback that expects a token. The login is successful\n // :: if the user calls it with value that is truthy\n // -------------------------------------------------------------\n login: function(auth, infinite, success, error) {\n logins.push([].slice.call(arguments));\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'login'));\n }\n if (!is_function(auth)) {\n throw new Error(strings().loginIsNotAFunction);\n }\n in_login = true;\n if (self.token() && self.level() === 1 && !autologin) {\n in_login = false; // logout will call login\n self.logout(true);\n } else if (self.token(true) && self.login_name(true)) {\n in_login = false;\n if (is_function(success)) {\n success();\n }\n return self;\n }\n // don't store login data in history\n if (settings.history) {\n command_line.history().disable();\n }\n function popUserPass() {\n while (self.level() > level) {\n self.pop(undefined, true);\n }\n if (settings.history) {\n command_line.history().enable();\n }\n }\n // so we know how many times call pop\n var level = self.level();\n function login_callback(user, token, silent) {\n if (token) {\n popUserPass();\n var name = self.prefix_name(true) + '_';\n storage.set(name + 'token', token);\n storage.set(name + 'login', user);\n in_login = false;\n fire_event('onAfterLogin', [user, token]);\n if (is_function(success)) {\n // will be used internaly since users know\n // when login success (they decide when\n // it happen by calling the callback -\n // this funtion)\n success();\n }\n } else {\n if (infinite) {\n if (!silent) {\n self.error(strings().wrongPasswordTryAgain);\n }\n self.pop(undefined, true).set_mask(false);\n } else {\n in_login = false;\n if (!silent) {\n self.error(strings().wrongPassword);\n }\n self.pop(undefined, true).pop(undefined, true);\n }\n // used only to call pop in push\n if (is_function(error)) {\n error();\n }\n }\n if (self.paused()) {\n self.resume();\n }\n self.off('terminal.autologin');\n }\n self.on('terminal.autologin', function(event, user, token, silent) {\n if (fire_event('onBeforeLogin', [user, token]) === false) {\n return;\n }\n login_callback(user, token, silent);\n });\n self.push(function(user) {\n self.set_mask(settings.maskChar).push(function(pass) {\n try {\n if (fire_event('onBeforeLogin', [user, pass]) === false) {\n popUserPass();\n return;\n }\n self.pause();\n var ret = auth.call(self, user, pass, function(\n token,\n silent) {\n login_callback(user, token, silent);\n });\n if (ret && is_function(ret.then || ret.done)) {\n (ret.then || ret.done).call(ret, function(token) {\n login_callback(user, token);\n }).catch(function(err) {\n self.pop(undefined, true).pop(undefined, true);\n self.error(err.message);\n if (is_function(error)) {\n error();\n }\n if (self.paused()) {\n self.resume();\n }\n self.off('terminal.autologin');\n });\n }\n } catch (e) {\n display_exception(e, 'AUTH');\n }\n }, {\n prompt: strings().password + ': ',\n name: 'password'\n });\n }, {\n prompt: strings().login + ': ',\n name: 'login'\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: User defined settings and defaults as well\n // -------------------------------------------------------------\n settings: function() {\n return settings;\n },\n // -------------------------------------------------------------\n // :: Get string before cursor\n // -------------------------------------------------------------\n before_cursor: function(word) {\n var pos = command_line.position();\n var command = command_line.get().slice(0, pos);\n var cmd_strings = command.split(' ');\n var string; // string before cursor that will be completed\n if (word) {\n if (cmd_strings.length === 1) {\n string = cmd_strings[0];\n } else {\n var m = command.match(/(\\\\?\")/g);\n var double_quotes = m ? m.filter(function(chr) {\n return !chr.match(/^\\\\/);\n }).length : 0;\n m = command.match(/'/g);\n var single_quote = m ? m.length : 0;\n if (single_quote % 2 === 1) {\n string = command.match(/('[^']*)$/)[0];\n } else if (double_quotes % 2 === 1) {\n string = command.match(/(\"(?:[^\"]|\\\\\")*)$/)[0];\n } else {\n string = cmd_strings[cmd_strings.length - 1];\n for (i = cmd_strings.length - 1; i > 0; i--) {\n // treat escape space as part of the string\n var prev_string = cmd_strings[i - 1];\n if (prev_string[prev_string.length - 1] === '\\\\') {\n string = cmd_strings[i - 1] + ' ' + string;\n } else {\n break;\n }\n }\n }\n }\n } else {\n string = command;\n }\n return string;\n },\n // -------------------------------------------------------------\n // :: complete word or command based on array of words\n // -------------------------------------------------------------\n complete: function(commands, options) {\n options = $.extend({\n word: true,\n echo: false,\n escape: true,\n echoCommand: false,\n caseSensitive: true,\n doubleTab: null\n }, options || {});\n var sensitive = options.caseSensitive;\n // cursor can be in the middle of the command\n // so we need to get the text before the cursor\n var string = self.before_cursor(options.word).replace(/\\\\\"/g, '\"');\n var quote = false;\n if (options.word) {\n if (string.match(/^\"/)) {\n quote = '\"';\n } else if (string.match(/^'/)) {\n quote = \"'\";\n }\n if (quote) {\n string = string.replace(/^[\"']/, '');\n }\n }\n if (tab_count % 2 === 0) {\n command = self.before_cursor(options.word);\n } else {\n var test = self.before_cursor(options.word);\n if (test !== command) {\n // command line changed between TABS - ignore\n return;\n }\n }\n var safe = $.terminal.escape_regex(string);\n if (options.escape) {\n safe = safe.replace(/(\\\\+)([\"'() ])/g, function(_, slash, chr) {\n if (chr.match(/[()]/)) {\n return slash + '\\\\?\\\\' + chr;\n } else {\n return slash + '?' + chr;\n }\n });\n }\n function escape(string) {\n if (quote === '\"') {\n string = string.replace(/\"/g, '\\\\\"');\n }\n if (!quote && options.escape) {\n string = string.replace(/([\"'() ])/g, '\\\\$1');\n }\n return string;\n }\n function matched_strings() {\n var matched = [];\n for (var i = commands.length; i--;) {\n if (commands[i].match(/\\n/) && options.word) {\n warn('If you use commands with newlines you ' +\n 'should use word option for complete or' +\n ' wordAutocomplete terminal option');\n }\n if (regex.test(commands[i])) {\n var match = escape(commands[i]);\n if (!sensitive && same_case(match)) {\n if (string.toLowerCase() === string) {\n match = match.toLowerCase();\n } else if (string.toUpperCase() === string) {\n match = match.toUpperCase();\n }\n }\n matched.push(match);\n }\n }\n return matched;\n }\n var flags = sensitive ? '' : 'i';\n var regex = new RegExp('^' + safe, flags);\n var matched = matched_strings();\n function replace(input, replacement) {\n var text = self.get_command();\n var pos = self.get_position();\n var re = new RegExp('^' + input, 'i');\n var pre = text.slice(0, pos);\n var post = text.slice(pos);\n var to_insert = replacement.replace(re, '') + (quote || '');\n self.set_command(pre + to_insert + post);\n self.set_position((pre + to_insert).length);\n }\n if (matched.length === 1) {\n if (options.escape) {\n replace(safe, matched[0]);\n } else {\n self.insert(matched[0].replace(regex, '') + (quote || ''));\n }\n command = self.before_cursor(options.word);\n return true;\n } else if (matched.length > 1) {\n if (++tab_count >= 2) {\n tab_count = 0;\n if (options.echo) {\n if (is_function(options.doubleTab)) {\n // new API old is keep for backward compatibility\n if (options.echoCommand) {\n echo_command();\n }\n var ret = options.doubleTab.call(\n self,\n string,\n matched,\n echo_command\n );\n if (typeof ret === 'undefined') {\n return true;\n } else {\n return ret;\n }\n } else if (options.doubleTab !== false) {\n echo_command();\n var text = matched.slice().reverse().join('\\t\\t');\n self.echo($.terminal.escape_brackets(text), {\n keepWords: true,\n formatters: false\n });\n }\n return true;\n }\n } else {\n var common = common_string(escape(string), matched, sensitive);\n if (common) {\n replace(safe, common);\n command = self.before_cursor(options.word);\n return true;\n }\n }\n }\n },\n // -------------------------------------------------------------\n // :: Return commands function from top interpreter\n // -------------------------------------------------------------\n commands: function() {\n return interpreters.top().interpreter;\n },\n // -------------------------------------------------------------\n // :: Low Level method that overwrites interpreter\n // -------------------------------------------------------------\n set_interpreter: function(user_intrp, login) {\n var defer = $.Deferred();\n function overwrite_interpreter() {\n self.pause(settings.softPause);\n make_interpreter(user_intrp, login, function(result) {\n self.resume();\n var top = interpreters.top();\n $.extend(top, result);\n prepare_top_interpreter(true);\n defer.resolve();\n });\n }\n if (is_function(login)) {\n self.login(login, true, overwrite_interpreter);\n } else if (get_type(user_intrp) === 'string' && login) {\n self.login(\n make_json_rpc_login(user_intrp, login),\n true,\n overwrite_interpreter\n );\n } else {\n overwrite_interpreter();\n }\n return defer.promise();\n },\n // -------------------------------------------------------------\n // :: Show user greetings or terminal signature\n // -------------------------------------------------------------\n greetings: function() {\n show_greetings();\n return self;\n },\n // -------------------------------------------------------------\n // :: Return true if terminal is paused false otherwise\n // -------------------------------------------------------------\n paused: function() {\n return paused;\n },\n // -------------------------------------------------------------\n // :: Pause the terminal, it should be used for ajax calls\n // -------------------------------------------------------------\n pause: function(visible) {\n cmd_ready(function ready() {\n onPause();\n paused = true;\n command_line.disable(visible || is_android);\n if (!visible) {\n command_line.find('.cmd-prompt').hidden();\n }\n fire_event('onPause');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Resume the previously paused terminal\n // -------------------------------------------------------------\n resume: function(silent) {\n cmd_ready(function ready() {\n paused = false;\n if (enabled && terminals.front() === self) {\n command_line.enable(silent);\n }\n command_line.find('.cmd-prompt').visible();\n var original = delayed_commands;\n delayed_commands = [];\n for (var i = 0; i < original.length; ++i) {\n self.exec.apply(self, original[i]);\n }\n self.trigger('resume');\n var fn = resume_callbacks.shift();\n if (fn) {\n fn();\n }\n self.scroll_to_bottom();\n fire_event('onResume');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return the number of characters that fit into the width of\n // :: the terminal\n // -------------------------------------------------------------\n cols: function() {\n if (settings.numChars) {\n return settings.numChars;\n }\n if (!num_chars || num_chars === 1000) {\n num_chars = get_num_chars(self, char_size);\n }\n return num_chars;\n },\n // -------------------------------------------------------------\n // :: Return the number of lines that fit into the height of the\n // :: terminal\n // -------------------------------------------------------------\n rows: function() {\n if (settings.numRows) {\n return settings.numRows;\n }\n if (!num_rows) {\n num_rows = get_num_rows(self, char_size);\n }\n return num_rows;\n },\n // -------------------------------------------------------------\n // :: Return the History object\n // -------------------------------------------------------------\n history: function() {\n return command_line.history();\n },\n // -------------------------------------------------------------\n // :: Return size of the terminal instance\n // -------------------------------------------------------------\n geometry: function() {\n var style = window.getComputedStyle(self[0]);\n function padding(name) {\n return parseInt(style.getPropertyValue('padding-' + name), 10) || 0;\n }\n var left = padding('left');\n var right = padding('right');\n var top = padding('top');\n var bottom = padding('bottom');\n return {\n terminal: {\n padding: {\n left: left,\n right: right,\n top: top,\n bottom: bottom\n },\n width: old_width + left + right,\n height: old_height + top + bottom\n },\n char: char_size,\n cols: this.cols(),\n rows: this.rows()\n };\n },\n // -------------------------------------------------------------\n // :: toggle recording of history state\n // -------------------------------------------------------------\n history_state: function(toggle) {\n function run() {\n settings.historyState = true;\n if (!save_state.length) {\n self.save_state();\n } else if (terminals.length() > 1) {\n self.save_state(null);\n }\n }\n if (toggle) {\n // if set to true and if set from user command we need\n // not to include the command\n if (typeof window.setImmediate === 'undefined') {\n setTimeout(run, 0);\n } else {\n setImmediate(run);\n }\n } else {\n settings.historyState = false;\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: clear the history state\n // -------------------------------------------------------------\n clear_history_state: function() {\n hash_commands = [];\n save_state = [];\n return self;\n },\n // -------------------------------------------------------------\n // :: Switch to the next terminal\n // -------------------------------------------------------------\n next: function() {\n if (terminals.length() === 1) {\n return self;\n } else {\n terminals.front().disable();\n var next = terminals.rotate().enable();\n // 50 provides buffer in viewport\n var x = next.offset().top - 50;\n $('html,body').animate({scrollTop: x}, 500);\n try {\n trigger_terminal_change(next);\n } catch (e) {\n display_exception(e, 'onTerminalChange');\n }\n return next;\n }\n },\n // -------------------------------------------------------------\n // :: Make the terminal in focus or blur depending on the first\n // :: argument. If there is more then one terminal it will\n // :: switch to next one, if the second argument is set to true\n // :: the events will be not fired. Used on init\n // -------------------------------------------------------------\n focus: function(toggle, silent) {\n cmd_ready(function ready() {\n if (terminals.length() === 1) {\n if (toggle === false) {\n self.disable(silent);\n } else {\n self.enable(silent);\n }\n } else if (toggle === false) {\n self.next();\n } else {\n var front = terminals.front();\n if (front !== self) {\n // there should be only from terminal enabled but tests\n // sometime fail because there where more them one\n // where cursor have blink class\n terminals.forEach(function(terminal) {\n if (terminal !== self && terminal.enabled()) {\n terminal.disable(silent);\n }\n });\n if (!silent) {\n try {\n trigger_terminal_change(self);\n } catch (e) {\n display_exception(e, 'onTerminalChange');\n }\n }\n }\n terminals.set(self);\n self.enable(silent);\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Disable/Enable terminal that can be enabled by click\n // -------------------------------------------------------------\n freeze: function(freeze) {\n when_ready(function ready() {\n if (freeze) {\n self.disable();\n frozen = true;\n } else {\n frozen = false;\n self.enable();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: check if terminal is frozen\n // -------------------------------------------------------------\n frozen: function() {\n return frozen;\n },\n // -------------------------------------------------------------\n // :: Enable the terminal\n // -------------------------------------------------------------\n enable: function(silent) {\n if (!enabled && !frozen) {\n if (num_chars === undefined) {\n // enabling first time\n self.resize();\n }\n cmd_ready(function ready() {\n var ret;\n if (!silent && !enabled) {\n fire_event('onFocus');\n }\n if (!silent && ret === undefined || silent) {\n enabled = true;\n if (!self.paused()) {\n command_line.enable(true);\n }\n }\n });\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: function clear formatting cache if you don't longer need it\n // :: cache is used if option useCache is set to true\n // -------------------------------------------------------------\n clear_cache: 'Map' in root ? function() {\n buffer.clear_cache();\n line_cache.clear();\n return self;\n } : function() {\n return self;\n },\n // -------------------------------------------------------------\n // :: Disable the terminal\n // -------------------------------------------------------------\n disable: function(silent) {\n cmd_ready(function ready() {\n var ret;\n if (!silent && enabled) {\n ret = fire_event('onBlur');\n }\n if (!silent && ret === undefined || silent) {\n enabled = false;\n command_line.disable();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: return true if the terminal is enabled\n // -------------------------------------------------------------\n enabled: function() {\n return enabled;\n },\n // -------------------------------------------------------------\n // :: Return the terminal signature depending on the size of the terminal\n // -------------------------------------------------------------\n signature: function() {\n var cols = self.cols();\n for (var i = signatures.length; i--;) {\n var lengths = signatures[i].map(function(line) {\n return line.length;\n });\n if (Math.max.apply(null, lengths) <= cols) {\n return signatures[i].join('\\n') + '\\n';\n }\n }\n return '';\n },\n // -------------------------------------------------------------\n // :: Return the version number\n // -------------------------------------------------------------\n version: function() {\n return $.terminal.version;\n },\n // -------------------------------------------------------------\n // :: Return actual command line object (jquery object with cmd\n // :: methods)\n // -------------------------------------------------------------\n cmd: function() {\n return command_line;\n },\n // -------------------------------------------------------------\n // :: Return the current command entered by terminal\n // -------------------------------------------------------------\n get_command: function() {\n return command_line.get();\n },\n // -------------------------------------------------------------\n // :: better API than echo_command that supports animation\n // -------------------------------------------------------------\n enter: with_typing('enter', echo_command),\n // -------------------------------------------------------------\n // :: Change the command line to the new one\n // -------------------------------------------------------------\n set_command: function(command, silent) {\n when_ready(function ready() {\n // TODO: refactor to use options - breaking change\n if (typeof command !== 'string') {\n command = JSON.stringify(command);\n }\n command_line.set(command, undefined, silent);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Change position of the command line\n // -------------------------------------------------------------\n set_position: function(position, relative) {\n when_ready(function ready() {\n command_line.position(position, relative);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return position of the command line\n // -------------------------------------------------------------\n get_position: function() {\n return command_line.position();\n },\n // -------------------------------------------------------------\n // :: Insert text into the command line after the cursor\n // -------------------------------------------------------------\n insert: function(string, stay) {\n if (typeof string === 'string') {\n when_ready(function ready() {\n var bottom = self.is_bottom();\n command_line.insert(string, stay);\n if (settings.scrollOnEcho || bottom) {\n self.scroll_to_bottom();\n }\n });\n return self;\n } else {\n throw new Error(sprintf(strings().notAString, 'insert'));\n }\n },\n // -------------------------------------------------------------\n // :: Set the prompt of the command line\n // -------------------------------------------------------------\n set_prompt: with_typing('prompt', function(prompt) {\n if (is_function(prompt)) {\n command_line.prompt(function(callback) {\n return prompt.call(self, callback, self);\n });\n } else {\n command_line.prompt(prompt);\n }\n }, function(prompt) {\n interpreters.top().prompt = prompt;\n }),\n // -------------------------------------------------------------\n // :: Return the prompt used by the terminal\n // -------------------------------------------------------------\n get_prompt: function() {\n return interpreters.top().prompt;\n // command_line.prompt(); - can be a wrapper\n //return command_line.prompt();\n },\n // -------------------------------------------------------------\n // :: Enable or Disable mask depedning on the passed argument\n // :: the mask can also be character (in fact it will work with\n // :: strings longer then one)\n // -------------------------------------------------------------\n set_mask: function(mask) {\n when_ready(function ready() {\n command_line.mask(mask === true ? settings.maskChar : mask);\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Return the ouput of the terminal as text\n // :: the output may contain user terminal formatting\n // -------------------------------------------------------------\n get_output: function(raw) {\n if (raw) {\n return lines.data();\n } else {\n return lines.get_snapshot();\n }\n },\n // -------------------------------------------------------------\n // :: Recalculate and redraw everything\n // -------------------------------------------------------------\n resize: function(width, height) {\n if (!self.is(':visible')) {\n // delay resize if terminal not visible\n self.stopTime('resize');\n self.oneTime(500, 'resize', function() {\n self.resize(width, height);\n });\n } else {\n if (width && height) {\n self.width(width);\n self.height(height);\n }\n width = self.width();\n height = self.height();\n if (typeof settings.numChars !== 'undefined' ||\n typeof settings.numRows !== 'undefined') {\n command_line.resize(settings.numChars);\n self.refresh();\n fire_event('onResize');\n return;\n }\n var new_num_chars = get_num_chars(self, char_size);\n var new_num_rows = get_num_rows(self, char_size);\n // only if number of chars changed\n if (new_num_chars !== num_chars ||\n new_num_rows !== num_rows) {\n self.clear_cache();\n num_chars = new_num_chars;\n num_rows = new_num_rows;\n command_line.resize(num_chars);\n self.refresh();\n fire_event('onResize');\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: redraw the terminal and invalidate cache\n // -------------------------------------------------------------\n refresh: function() {\n if (char_size.width !== 0) {\n self[0].style.setProperty('--char-width', char_size.width);\n }\n self.clear_cache();\n if (command) {\n command_line.resize();\n }\n redraw({\n scroll: false,\n update: true\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Flush the output to the terminal\n // -------------------------------------------------------------\n flush: function(options) {\n options = $.extend({}, {\n update: false,\n scroll: true\n }, options || {});\n when_ready(function ready() {\n try {\n if (buffer.is_empty()) {\n return self;\n }\n var bottom = self.is_bottom();\n var scroll = (settings.scrollOnEcho && options.scroll) || bottom;\n var wrapper;\n // print all lines\n var first = true;\n var appending_to_partial = false;\n var partial = $();\n var snapshot;\n if (!options.update) {\n partial = self.find('.partial');\n snapshot = lines.get_partial();\n }\n // TODO: refactor buffer.flush(), there is way\n // to many levels of abstractions in one place\n buffer.flush(function(data) {\n if (!data) {\n if (!partial.length) {\n wrapper = $('');\n snapshot = [];\n } else if (first) {\n first = false;\n appending_to_partial = true;\n wrapper = partial;\n }\n } else if (is_function(data.finalize)) {\n if (scroll) {\n wrapper.find('img').on('load', function() {\n self.scroll_to_bottom();\n });\n }\n // this is finalize function from echo\n if (options.update) {\n lines.update_snapshot(data.index, snapshot);\n var selector = '> div[data-index=' + data.index + ']';\n var node = output.find(selector);\n if (node.html() !== wrapper.html()) {\n node.replaceWith(wrapper);\n }\n } else {\n wrapper.appendTo(output);\n if (!partial.length) {\n lines.make_snapshot(snapshot);\n }\n }\n wrapper.attr('data-index', data.index);\n appending_to_partial = !data.newline;\n wrapper.toggleClass('partial', appending_to_partial);\n if (appending_to_partial) {\n partial = wrapper;\n }\n data.finalize(wrapper);\n } else {\n var line = data.line;\n var div;\n if (typeof data.raw === 'string') {\n if (appending_to_partial) {\n snapshot[snapshot.length - 1] += data.raw;\n } else {\n snapshot.push(data.raw);\n }\n }\n if (appending_to_partial) {\n div = wrapper.children().last().append(line);\n appending_to_partial = false;\n } else {\n div = $('').html(line);\n if (data.newline) {\n div.addClass('cmd-end-line');\n }\n wrapper.append(div);\n }\n // width = '100%' does some weird extra magic\n // that makes the height correct. Any other\n // value doesn't work.\n div.css('width', '100%');\n }\n });\n var cmd_prompt = self.find('.cmd-prompt');\n var cmd_outer = self.find('.cmd');\n partial = self.find('.partial');\n var last_row;\n if (partial.length === 0) {\n cmd_prompt.css('margin-left', 0);\n cmd_outer.css('top', 0);\n command_line.__set_prompt_margin(0);\n last_row = self.find('.terminal-output div:last-child' +\n ' div:last-child');\n // check if the div is parital fix #695\n if (last_row.css('display') === 'inline-block') {\n last_row.css({\n width: '100%',\n display: ''\n });\n }\n } else {\n last_row = partial.children().last();\n // Remove width='100%' for two reasons:\n // 1. so we can measure the width right here\n // 2. so that the background of this last line of output\n // doesn't occlude the first line of input to the right\n last_row.css({\n width: '',\n display: 'inline-block'\n });\n var last_row_rect = last_row[0].getBoundingClientRect();\n var partial_width = last_row_rect.width;\n // Shift command prompt up one line and to the right\n // enough so that it appears directly next to the\n // partially constructed output line\n cmd_prompt.css('margin-left', partial_width);\n cmd_outer.css('top', -last_row_rect.height);\n // Measure length of partial line in characters\n var char_width = self.geometry().char.width;\n var prompt_margin = Math.round(partial_width / char_width);\n command_line.__set_prompt_margin(prompt_margin);\n }\n limit_lines();\n fire_event('onFlush');\n var cmd_cursor = self.find('.cmd-cursor');\n var offset = self.find('.cmd').offset();\n var self_offset = self.offset();\n setTimeout(function() {\n css(self[0], {\n '--terminal-height': self.height(),\n '--terminal-x': offset.left - self_offset.left,\n '--terminal-y': offset.top - self_offset.top,\n '--terminal-scroll': self.prop('scrollTop')\n });\n // Firefox won't reflow the cursor automatically, so\n // hide it briefly then reshow it\n cmd_cursor.hide();\n setTimeout(function() {\n cmd_cursor.show();\n }, 0);\n }, 0);\n if (scroll) {\n self.scroll_to_bottom();\n }\n } catch (e1) {\n if (is_function(settings.exceptionHandler)) {\n try {\n settings.exceptionHandler.call(\n self,\n e1,\n 'TERMINAL (Flush)'\n );\n } catch (e2) {\n settings.exceptionHandler = $.noop;\n alert_exception('[exceptionHandler]', e2);\n }\n } else {\n alert_exception('[Flush]', e1);\n }\n } finally {\n buffer.clear();\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Update the output line - line number can be negative\n // -------------------------------------------------------------\n update: function(line, value, options) {\n when_ready(function ready() {\n if (line < 0) {\n line = lines.length() + line; // yes +\n }\n if (!lines.valid_index(line)) {\n self.error('Invalid line number ' + line);\n } else if (value === null) {\n lines.update(line, null);\n output.find('[data-index=' + line + ']').remove();\n } else {\n value = preprocess_value(value, {\n update: true,\n line: line\n });\n if (value === false) {\n return self;\n }\n unpromise(value, function(value) {\n var ret = prepare_render(value, options);\n if (ret) {\n value = ret[0];\n options = ret[1];\n }\n options = lines.update(line, value, options);\n var next = process_line({\n value: value,\n index: line,\n options: options\n });\n // process_line can return a promise\n // value is function that resolve to promise\n unpromise(next, function() {\n // trigger flush even if next is undefined\n self.flush({\n scroll: false,\n update: true\n });\n });\n });\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: convenience method for removing selected line\n // -------------------------------------------------------------\n remove_line: function(line) {\n return self.update(line, null);\n },\n // -------------------------------------------------------------\n // :: return index of last line in case when you need to update\n // :: after something is echo on the terminal\n // -------------------------------------------------------------\n last_index: function() {\n return lines.length() - 1;\n },\n // -------------------------------------------------------------\n // :: Print data to the terminal output. It can have options\n // :: * flush - indicate that arg should be send to DOM\n // :: * raw - indicate if it should handle input as html\n // :: * finalize - function call with container div\n // :: * keepWords - inform how to wrap text\n // :: * formatters - inform function if it should use formatters\n // :: on input string - good to prevent XSS when you want\n // :: advanced server side controling of terminal\n // :: you can echo: promise, function, strings array or string\n // -------------------------------------------------------------\n echo: function(arg, options) {\n var arg_defined = arguments.length > 0;\n var d = new $.Deferred();\n function echo(arg) {\n try {\n var locals = $.extend({\n flush: true,\n exec: true,\n raw: settings.raw,\n finalize: $.noop,\n unmount: $.noop,\n delay: settings.execAnimationDelay,\n ansi: false,\n typing: false,\n keepWords: false,\n invokeMethods: settings.invokeMethods,\n onClear: null,\n formatters: true,\n allowedAttributes: settings.allowedAttributes,\n newline: true\n }, options || {});\n // finalize function is passed around and invoked\n // in terminal::flush after content is added to DOM\n (function(finalize) {\n locals.finalize = function(div) {\n if (locals.raw) {\n div.addClass('raw');\n }\n if (locals.ansi) {\n div.addClass('ansi');\n }\n try {\n if (is_function(finalize)) {\n finalize.call(self, div);\n }\n var $images = div.find('img');\n $images.each(function() {\n var self = $(this);\n var img = new Image();\n img.onerror = function() {\n self.replaceWith(use_broken_image);\n };\n img.src = this.src;\n });\n } catch (e) {\n display_exception(e, 'USER:echo(finalize)');\n finalize = null;\n }\n };\n })(locals.finalize);\n if (locals.flush) {\n // flush buffer if there was no flush after previous echo\n if (!buffer.empty()) {\n self.flush();\n }\n }\n if (fire_event('onBeforeEcho', [arg]) === false) {\n return;\n }\n if (locals.typing) {\n if (typeof arg !== 'string') {\n return d.reject('echo: Typing animation require string' +\n ' or promise that resolve to string');\n }\n if (typeof locals.delay !== 'number' || isNaN(locals.delay)) {\n return d.reject('echo: Invalid argument, delay need to' +\n ' be a number');\n }\n var p = self.typing('echo', locals.delay, arg, locals);\n p.then(function() {\n d.resolve();\n });\n return;\n }\n var value;\n if (typeof arg === 'function') {\n value = arg.bind(self);\n } else if (typeof arg === 'undefined') {\n if (arg_defined) {\n value = String(arg);\n } else {\n value = '';\n }\n } else {\n var ret = preprocess_value(arg, {});\n if (ret === false) {\n return self;\n }\n value = ret;\n }\n if (is_promise(value)) {\n echo_promise = true;\n }\n unpromise(value, function(value) {\n if (render(value, locals)) {\n return self;\n }\n var index = lines.length();\n var last_newline = lines.has_newline();\n if (!last_newline) {\n index--;\n }\n if (!locals.newline && value[value.length - 1] === '\\n') {\n // This adjusts the value, so that when it updates or\n // refresh the lines list it does the right thing.\n value = value.slice(0, -1);\n locals.newline = true;\n }\n var next = process_line({\n value: value,\n options: locals,\n index: index\n });\n // queue async functions in echo\n if (is_promise(next)) {\n echo_promise = true;\n }\n lines.push([value, locals]);\n unpromise(next, function() {\n // extended commands should be processed only\n // once in echo and not on redraw\n if (locals.flush) {\n self.flush();\n fire_event('onAfterEcho', [arg]);\n }\n echo_promise = false;\n var original = echo_delay;\n echo_delay = [];\n for (var i = 0; i < original.length; ++i) {\n self.echo.apply(self, original[i]);\n }\n });\n });\n } catch (e) {\n // if echo throw exception we can't use error to\n // display that exception\n if (is_function(settings.exceptionHandler)) {\n settings.exceptionHandler.call(self, e, 'TERMINAL (echo)');\n } else {\n alert_exception('[Terminal.echo]', e);\n }\n }\n }\n if (echo_promise) {\n echo_delay.push([arg, options]);\n } else {\n echo(arg);\n }\n if (options && options.typing) {\n return d.promise();\n }\n return self;\n },\n // -------------------------------------------------------------\n typing: function(type, delay, string, options) {\n var d = new $.Deferred();\n var settings;\n var finish;\n if (typeof options === 'object') {\n finish = options.finalize || $.noop;\n settings = $.extend({}, options, {\n delay: delay,\n finalize: done\n });\n } else {\n finish = options || $.noop;\n settings = {\n delay: delay,\n finalize: done\n };\n }\n function done() {\n d.resolve();\n if (is_function(finish)) {\n finish.apply(self, arguments);\n }\n }\n when_ready(function ready() {\n if (['prompt', 'echo', 'enter'].indexOf(type) >= 0) {\n if (type === 'prompt') {\n typed_prompt(string, settings);\n } else if (type === 'echo') {\n typed_message(string, settings);\n } else if (type === 'enter') {\n with_prompt(self.get_prompt(), function(prompt) {\n typed_enter(prompt, string, settings);\n }, self);\n }\n } else {\n d.reject('Invalid type only `echo` and `prompt` are supported');\n }\n });\n return d.promise();\n },\n // -------------------------------------------------------------\n // :: echo red text\n // -------------------------------------------------------------\n error: function(message, options) {\n options = $.extend({}, options, {raw: false, formatters: false});\n function format(string) {\n if (typeof string !== 'string') {\n string = String(string);\n }\n // quick hack to fix trailing backslash\n var str = $.terminal.escape_brackets(string).\n replace(/\\\\$/, '\').\n replace(url_re, ']$1[[;;;terminal-error]');\n return '[[;;;terminal-error]' + str + ']';\n }\n if (typeof message === 'function') {\n return self.echo(function() {\n return format(message.call(self));\n }, options);\n }\n if (message && message.then) {\n message.then(function(string) {\n self.echo(format(string));\n }).catch(make_label_error('Echo Error'));\n return self;\n }\n return self.echo(format(message), options);\n },\n // -------------------------------------------------------------\n // :: Display Exception on terminal\n // -------------------------------------------------------------\n exception: function(e, label) {\n var message = exception_message(e);\n if (label) {\n message = '[' + label + ']: ' + message;\n }\n if (message) {\n self.error(message, {\n finalize: function(div) {\n div.addClass('terminal-exception terminal-message');\n },\n keepWords: true\n });\n }\n if (typeof e.fileName === 'string') {\n // display filename and line which throw exeption\n self.pause(settings.softPause);\n $.get(e.fileName, function(file) {\n var num = e.lineNumber - 1;\n var line = file.split('\\n')[num];\n if (line) {\n self.error('[' + e.lineNumber + ']: ' + line);\n }\n self.resume();\n }, 'text');\n }\n if (e.stack) {\n var stack = $.terminal.escape_brackets(e.stack);\n var output = stack.split(/\\n/g).map(function(trace) {\n // nested formatting will handle urls but that formatting\n // can be removed - this code was created before\n // that formatting existed (see commit ce01c3f5)\n return '[[;;;terminal-error]' +\n trace.replace(url_re, function(url) {\n return ']' + url + '[[;;;terminal-error]';\n }) + ']';\n }).join('\\n');\n self.echo(output, {\n finalize: function(div) {\n div.addClass('terminal-exception terminal-stack-trace');\n },\n formatters: false\n });\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Scroll Div that holds the terminal\n // -------------------------------------------------------------\n scroll: function(amount) {\n var pos;\n amount = Math.round(amount);\n if (self.prop) { // work with jQuery > 1.6\n if (amount > self.prop('scrollTop') && amount > 0) {\n self.prop('scrollTop', 0);\n }\n pos = self.prop('scrollTop');\n self.scrollTop(pos + amount);\n } else {\n if (amount > self.prop('scrollTop') && amount > 0) {\n self.prop('scrollTop', 0);\n }\n pos = self.prop('scrollTop');\n self.scrollTop(pos + amount);\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Exit all interpreters and logout. The function will throw\n // :: exception if there is no login provided\n // -------------------------------------------------------------\n logout: function(local) {\n if (in_login) {\n throw new Error(sprintf(strings().notWhileLogin, 'logout'));\n }\n when_ready(function ready() {\n if (local) {\n var login = logins.pop();\n self.set_token(undefined, true);\n self.login.apply(self, login);\n } else if (interpreters.size() === 1 && self.token()) {\n self.logout(true);\n } else {\n while (interpreters.size() > 1) {\n // pop will call global_logout that will call login\n // and size will be > 0; this is workaround the problem\n if (self.token()) {\n self.logout(true).pop().pop();\n } else {\n self.pop();\n }\n }\n }\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Function returns the token returned by callback function\n // :: in login function. It does nothing (return undefined) if\n // :: there is no login\n // -------------------------------------------------------------\n token: function(local) {\n return storage.get(self.prefix_name(local) + '_token');\n },\n // -------------------------------------------------------------\n // :: Function sets the token to the supplied value. This function\n // :: works regardless of wherer settings.login is supplied\n // -------------------------------------------------------------\n set_token: function(token, local) {\n var name = self.prefix_name(local) + '_token';\n if (typeof token === 'undefined') {\n storage.remove(name);\n } else {\n storage.set(name, token);\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Function get the token either set by the login method or\n // :: by the set_token method.\n // -------------------------------------------------------------\n get_token: function(local) {\n return self.token(local);\n },\n // -------------------------------------------------------------\n // :: Function return Login name entered by the user\n // -------------------------------------------------------------\n login_name: function(local) {\n return storage.get(self.prefix_name(local) + '_login');\n },\n // -------------------------------------------------------------\n // :: Function returns the name of current interpreter\n // -------------------------------------------------------------\n name: function() {\n return interpreters.top().name;\n },\n // -------------------------------------------------------------\n // :: Function return prefix name for login/token\n // -------------------------------------------------------------\n prefix_name: function(local) {\n var name = (settings.name ? settings.name + '_' : '') +\n terminal_id;\n if (local && interpreters.size() > 1) {\n var local_name = interpreters.map(function(intrp) {\n return intrp.name || '';\n }).slice(1).join('_');\n if (local_name) {\n name += '_' + local_name;\n }\n }\n return name;\n },\n // -------------------------------------------------------------\n // :: wrapper for common use case\n // -------------------------------------------------------------\n read: function(message, success, cancel) {\n var options;\n if (typeof arguments[1] === 'object') {\n options = $.extend({\n typing: false,\n delay: settings.execAnimationDelay,\n success: $.noop,\n cancel: $.noop\n }, arguments[1]);\n } else {\n options = {\n typing: false,\n success: success || $.noop,\n cancel: cancel || $.noop\n };\n }\n if (options.typing) {\n var prompt = self.get_prompt();\n options.typing = false;\n return self.typing('prompt', options.delay, message).then(function() {\n return self.set_prompt(prompt).read(message, options);\n });\n }\n // return from read() should not pause terminal\n force_awake = true;\n var defer = jQuery.Deferred();\n var read = false;\n self.push(function(string) {\n read = true;\n defer.resolve(string);\n if (is_function(options.success)) {\n options.success(string);\n }\n self.pop();\n if (settings.history) {\n command_line.history().enable();\n }\n }, {\n name: 'read',\n history: false,\n prompt: message || '',\n onExit: function() {\n if (!read) {\n defer.reject();\n if (is_function(options.cancel)) {\n options.cancel();\n }\n }\n }\n });\n if (settings.history) {\n command_line.history().disable();\n }\n return defer.promise();\n },\n // -------------------------------------------------------------\n // :: Push a new interenter on the Stack\n // -------------------------------------------------------------\n push: function(interpreter, options) {\n cmd_ready(function ready() {\n options = options || {};\n var defaults = {\n infiniteLogin: false\n };\n var push_settings = $.extend({}, defaults, options);\n if (!push_settings.name && prev_command) {\n // name the interpreter from last command\n push_settings.name = prev_command.name;\n }\n if (push_settings.prompt === undefined) {\n push_settings.prompt = (push_settings.name || '>') + ' ';\n }\n // names.push(options.name);\n var top = interpreters.top();\n if (top) {\n top.mask = command_line.mask();\n }\n var was_paused = paused;\n function init() {\n fire_event('onPush', [top, interpreters.top()]);\n prepare_top_interpreter();\n }\n //self.pause();\n make_interpreter(interpreter, options.login, function(ret) {\n // result is object with interpreter and completion properties\n interpreters.push($.extend({}, ret, push_settings));\n if (push_settings.completion === true) {\n if ($.isArray(ret.completion)) {\n interpreters.top().completion = ret.completion;\n } else if (!ret.completion) {\n interpreters.top().completion = false;\n }\n }\n if (push_settings.login) {\n var error;\n var type = get_type(push_settings.login);\n if (type === 'function') {\n error = push_settings.infiniteLogin ? $.noop : self.pop;\n self.login(\n push_settings.login,\n push_settings.infiniteLogin,\n init,\n error\n );\n } else if (get_type(interpreter) === 'string' &&\n type === 'string' || type === 'boolean') {\n error = push_settings.infiniteLogin ? $.noop : self.pop;\n self.login(\n make_json_rpc_login(\n interpreter,\n push_settings.login\n ),\n push_settings.infiniteLogin,\n init,\n error\n );\n }\n } else {\n init();\n }\n if (!was_paused && self.enabled()) {\n self.resume();\n }\n });\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove the last interpreter from the Stack\n // -------------------------------------------------------------\n pop: function(string, silent) {\n if (string !== undefined) {\n echo_command(string);\n }\n var token = self.token(true);\n var top;\n if (interpreters.size() === 1) {\n top = interpreters.top();\n if (settings.login) {\n if (!silent) {\n fire_event('onPop', [top, null]);\n }\n global_logout();\n fire_event('onExit');\n } else {\n self.error(strings().canExitError);\n }\n } else {\n if (token) {\n clear_loging_storage();\n }\n var current = interpreters.pop();\n top = interpreters.top();\n prepare_top_interpreter();\n // restore mask\n self.set_mask(top.mask);\n if (!silent) {\n fire_event('onPop', [current, top]);\n }\n // we check in case if you don't pop from password interpreter\n if (in_login && self.get_prompt() !== strings().login + ': ') {\n in_login = false;\n }\n if (is_function(current.onExit)) {\n try {\n current.onExit.call(self, self);\n } catch (e) {\n current.onExit = $.noop;\n display_exception(e, 'onExit');\n }\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: Change terminal option(s) at runtime\n // -------------------------------------------------------------\n option: function(object_or_name, value) {\n if (typeof value === 'undefined') {\n if (typeof object_or_name === 'string') {\n return settings[object_or_name];\n } else if (typeof object_or_name === 'object') {\n $.each(object_or_name, function(key, value) {\n settings[key] = value;\n });\n }\n } else {\n settings[object_or_name] = value;\n if (object_or_name.match(/^num(Chars|Rows)$/)) {\n redraw();\n }\n }\n return self;\n },\n // -------------------------------------------------------------\n // :: invoke keydown shorcut\n // -------------------------------------------------------------\n invoke_key: function(shortcut) {\n command_line.invoke_key(shortcut);\n return self;\n },\n // -------------------------------------------------------------\n // :: change terminal keymap at runtime\n // -------------------------------------------------------------\n keymap: function(keymap, fn) {\n if (arguments.length === 0) {\n return command_line.keymap();\n }\n if (typeof fn === 'undefined') {\n if (typeof keymap === 'string') {\n return command_line.keymap(keymap);\n } else if ($.isPlainObject(keymap)) {\n // argument is an object\n keymap = $.extend(\n {},\n terminal_init_keymap,\n $.omap(keymap || {}, function(key, fn) {\n if (!terminal_init_keymap[key]) {\n return fn.bind(self);\n }\n return function(e, original) {\n // new keymap function will get default as 2nd arg\n return fn.call(self, e, function() {\n return terminal_init_keymap[key](e, original);\n });\n };\n })\n );\n command_line.keymap(null).keymap(keymap);\n }\n } else if (typeof fn === 'function') {\n var key = keymap;\n if (!terminal_init_keymap[key]) {\n command_line.keymap(key, fn.bind(self));\n } else {\n command_line.keymap(key, function(e, original) {\n return fn.call(self, e, function() {\n return terminal_init_keymap[key](e, original);\n });\n });\n }\n }\n },\n // -------------------------------------------------------------\n // :: Return how deep you are in nested interpreters\n // -------------------------------------------------------------\n level: function() {\n return interpreters.size();\n },\n // -------------------------------------------------------------\n // :: Reinitialize the terminal\n // -------------------------------------------------------------\n reset: function() {\n when_ready(function ready() {\n self.clear();\n while (interpreters.size() > 1) {\n interpreters.pop();\n }\n initialize();\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove all local storage left by terminal, it will not\n // :: logout you until you refresh the browser\n // -------------------------------------------------------------\n purge: function() {\n when_ready(function ready() {\n var prefix = self.prefix_name() + '_';\n var names = storage.get(prefix + 'interpreters');\n if (names) {\n $.each(JSON.parse(names), function(_, name) {\n storage.remove(name + '_commands');\n storage.remove(name + '_token');\n storage.remove(name + '_login');\n });\n }\n command_line.purge();\n storage.remove(prefix + 'interpreters');\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: Remove all events and DOM nodes left by terminal, it will\n // :: not purge the terminal so you will have the same state\n // :: when you refresh the browser\n // -------------------------------------------------------------\n destroy: function() {\n when_ready(function ready() {\n command_line.destroy().remove();\n self.resizer('unbind');\n self.touch_scroll('unbind');\n font_resizer.resizer('unbind').remove();\n $(document).unbind('.terminal_' + self.id());\n $(window).unbind('.terminal_' + self.id());\n self.unbind('click wheel mousewheel mousedown mouseup');\n self.removeData('terminal').removeClass('terminal').\n unbind('.terminal');\n if (settings.width) {\n self.css('width', '');\n }\n if (settings.height) {\n self.css('height', '');\n }\n $(window).off('blur', blur_terminal).\n off('focus', focus_terminal);\n self.find('.terminal-fill, .terminal-font').remove();\n self.stopTime();\n terminals.remove(terminal_id);\n if (visibility_observer) {\n if (visibility_observer.unobserve) {\n visibility_observer.unobserve(self[0]);\n } else {\n clearInterval(visibility_observer);\n }\n }\n var scroll_marker = self.find('.terminal-scroll-marker');\n if (is_bottom_observer) {\n is_bottom_observer.unobserve(scroll_marker[0]);\n }\n scroll_marker.remove();\n if (mutation_observer) {\n mutation_observer.disconnect();\n }\n if (!terminals.length()) {\n $(window).off('hashchange');\n }\n if (is_mobile) {\n self.off([\n 'touchstart.terminal',\n 'touchmove.terminal',\n 'touchend.terminal'\n ].join(' '));\n }\n output.remove();\n wrapper.remove();\n if (body_terminal) {\n var $body = $(body_terminal);\n if ($body.attr('class') === 'full-screen-terminal') {\n $body.removeAttr('class');\n } else {\n $body.removeClass('full-screen-terminal');\n }\n self.remove();\n }\n defunct = true;\n });\n return self;\n },\n // -------------------------------------------------------------\n // :: ref: https://stackoverflow.com/a/18927969/387194\n // -------------------------------------------------------------\n scroll_to: function(elem) {\n var scroll = self.scrollTop() - self.offset().top + $(elem).offset().top;\n self.scrollTop(scroll);\n return self;\n },\n // -------------------------------------------------------------\n scroll_to_bottom: function() {\n var scrollHeight;\n if (self.prop) {\n scrollHeight = self.prop('scrollHeight');\n } else {\n scrollHeight = self.attr('scrollHeight');\n }\n self.scrollTop(scrollHeight);\n return self;\n },\n // -------------------------------------------------------------\n // :: return true if terminal div or body is at the bottom\n // :: is use scrollBottomOffset option as margin for the check\n // -------------------------------------------------------------\n is_bottom: function() {\n if (settings.scrollBottomOffset === -1) {\n return false;\n } else if (typeof is_bottom_detected === 'boolean') {\n return is_bottom_detected;\n } else {\n var scroll_height, scroll_top, height;\n scroll_height = self[0].scrollHeight;\n scroll_top = self[0].scrollTop;\n height = self[0].offsetHeight;\n var limit = scroll_height - settings.scrollBottomOffset;\n return scroll_top + height > limit;\n }\n },\n // -------------------------------------------------------------\n // :: create terminal object clone, used by pipe\n // -------------------------------------------------------------\n duplicate: function() {\n var copy = $(self);\n return $.extend(copy, public_api);\n },\n // -------------------------------------------------------------\n // :: return output flush buffer\n // -------------------------------------------------------------\n get_output_buffer: function(options) {\n var settings = $.extend({\n html: false\n }, options);\n var result = [];\n var append = false;\n buffer.forEach(function(data) {\n if (data) {\n if (is_function(data.finalize)) {\n append = !data.newline;\n } else {\n var output;\n if (settings.html) {\n output = data.line;\n } else {\n output = data.raw;\n }\n if (append) {\n var last = result.length - 1;\n result[last] += output;\n } else {\n result.push(output);\n }\n }\n }\n });\n if (settings.html) {\n return result.map(function(line) {\n return '
' + line + '
';\n }).join('\\n');\n }\n return result.join('\\n');\n },\n // -------------------------------------------------------------\n // :: clear flush buffer\n // -------------------------------------------------------------\n clear_buffer: function() {\n buffer.clear();\n return self;\n }\n }, function(name, fun) {\n // wrap all functions and display execptions\n return function() {\n if (defunct) {\n if (!settings.exceptionHandler) {\n throw new $.terminal.Exception(strings().defunctTerminal);\n }\n }\n try {\n return fun.apply(self, [].slice.apply(arguments));\n } catch (e) {\n // exec catch by command (resume call exec)\n if (name !== 'exec' && name !== 'resume') {\n display_exception(e, e.type || 'TERMINAL', true);\n }\n if (!settings.exceptionHandler) {\n throw e;\n }\n }\n };\n });\n $.extend(self, public_api);\n // -----------------------------------------------------------------\n // :: INIT CODE\n // -----------------------------------------------------------------\n if (self.length === 0) {\n var msg = sprintf(strings().invalidSelector);\n throw new $.terminal.Exception(msg);\n }\n self.data('terminal', self);\n // synchronize the echo calls (used for async functions) that need\n // to be called in order\n var echo_delay = [];\n var echo_promise = false;\n // var names = []; // stack if interpreter names\n var prev_command; // used for name on the terminal if not defined\n var prev_exec_cmd;\n var tab_count = 0; // for tab completion\n var output; // .terminal-output jquery object\n var terminal_id = terminals.length();\n var force_awake = false; // flag used to don't pause when user return read() call\n var num_chars; // numer of chars in line\n var num_rows; // number of lines that fit without scrollbar\n var command; // for tab completion\n var logins = new Stack(); // stack of logins\n var command_queue = new DelayQueue();\n var animating = false; // true on typing animation\n var init_queue = new DelayQueue();\n var when_ready = ready(init_queue);\n var cmd_ready = ready(command_queue);\n var is_bottom_detected;\n var is_bottom_observer;\n var in_login = false;// some Methods should not be called when login\n // TODO: Try to use mutex like counter for pause/resume\n var onPause = $.noop;// used to indicate that user call pause onInit\n var old_width, old_height;\n var delayed_commands = []; // used when exec commands while paused\n var settings = $.extend(\n {},\n $.terminal.defaults,\n {\n name: self.selector,\n exit: !!(options && options.login || !options)\n },\n options || {}\n );\n if (typeof settings.width === 'number') {\n self.width(settings.width);\n }\n if (typeof settings.height === 'number') {\n self.height(settings.height);\n }\n var char_size = get_char_size(self);\n // this is needed when terminal have selector with --size that is not\n // bare .terminal so fake terminal will not get the proper size #602\n var need_char_size_recalculate = !terminal_ready(self);\n // so it's the same as in TypeScript definition for options\n delete settings.formatters;\n // used to throw error when calling methods on destroyed terminal\n var defunct = false;\n // ---------------------------------------------------------------------\n // :: FormatBuffer is used to to buffer the lines that echoed\n // :: it have append function that have 2 options raw and finalize\n // :: raw - will not encode the string and finalize if a function that\n // :: will have div container of the line as first argument\n // :: actuall echo to the terminal happen when calling flush\n // ---------------------------------------------------------------------\n var buffer = new FormatBuffer(function(options) {\n return {\n linksNoReferrer: settings.linksNoReferrer,\n linksNoFollow: settings.linksNoFollow,\n anyLinks: settings.anyLinks,\n charWidth: char_size.width,\n useCache: settings.useCache,\n escape: false,\n allowedAttributes: options.allowedAttributes || []\n };\n });\n var lines = new OutputLines(function() {\n return settings;\n });\n var storage = new StorageHelper(settings.memory);\n var enabled = settings.enabled;\n var frozen = false;\n var paused = false;\n var autologin = true; // set to false if onBeforeLogin return false\n var interpreters;\n var command_line;\n var old_enabled;\n var visibility_observer;\n var mutation_observer;\n // backward compatibility\n if (settings.ignoreSystemDescribe === true) {\n settings.describe = false;\n }\n // register ajaxSend for cancel requests on CTRL+D\n $(document).bind('ajaxSend.terminal_' + self.id(), function(e, xhr) {\n requests.push(xhr);\n });\n var wrapper = $('
').appendTo(self);\n $(broken_image).hide().appendTo(wrapper);\n var font_resizer = $('
').appendTo(self);\n var fill = $('
').appendTo(self);\n output = $('
').addClass('terminal-output').attr('role', 'log')\n .appendTo(wrapper);\n self.addClass('terminal');\n // before login event\n if (settings.login && fire_event('onBeforeLogin') === false) {\n autologin = false;\n }\n // create json-rpc authentication function\n var base_interpreter;\n if (typeof init_interpreter === 'string') {\n base_interpreter = init_interpreter;\n } else if (is_array(init_interpreter)) {\n // first JSON-RPC\n for (var i = 0, len = init_interpreter.length; i < len; ++i) {\n if (typeof init_interpreter[i] === 'string') {\n base_interpreter = init_interpreter[i];\n break;\n }\n }\n }\n var global_login_fn;\n if (is_function(settings.login)) {\n global_login_fn = settings.login;\n } else if (base_interpreter &&\n (typeof settings.login === 'string' || settings.login === true)) {\n global_login_fn = make_json_rpc_login(base_interpreter, settings.login);\n }\n terminals.append(self);\n function focus_terminal() {\n if (old_enabled) {\n self.focus();\n self.scroll_to_bottom();\n }\n }\n // -------------------------------------------------------------------------------\n function blur_terminal() {\n old_enabled = enabled;\n self.disable().find('.cmd textarea').trigger('blur', [true]);\n }\n // -------------------------------------------------------------------------------\n function stringify_value(value) {\n if (is_function(value)) {\n value = value();\n }\n if (value && is_function(value.then)) {\n return value.then(stringify_value);\n }\n if (get_type(value) !== 'string') {\n if (is_function(settings.parseObject)) {\n var ret = settings.parseObject(value);\n if (get_type(ret) === 'string') {\n value = ret;\n }\n } else if (is_array(value)) {\n value = $.terminal.columns(value, self.cols(), settings.tabs);\n } else {\n value = String(value);\n }\n }\n return value;\n }\n // -------------------------------------------------------------------------------\n function context_callback_proxy(fn) {\n if (fn.proxy) {\n return fn;\n }\n var wrapper = function(callback) {\n return fn.call(self, callback, self);\n };\n wrapper.proxy = true;\n return wrapper;\n }\n // -------------------------------------------------------------------------------\n // paste event is not testable in node\n // istanbul ignore next\n function paste_event(e) {\n e = e.originalEvent;\n // we don't care about browser that don't support clipboard data\n // those browser simple will not have this feature normal paste\n // is cross-browser and it's handled by cmd plugin\n function is_type(item, type) {\n return item.type.indexOf(type) !== -1;\n }\n function echo_image(image) {\n self.echo('', {raw: true});\n }\n function data_uri(blob) {\n var URL = window.URL || window.webkitURL;\n return URL.createObjectURL(blob);\n }\n function echo(object, ignoreEvents) {\n if (!ignoreEvents) {\n var event = {\n target: self\n };\n if (typeof object === 'string') {\n event.text = object;\n } else if (object instanceof Blob) {\n event.image = data_uri(object);\n }\n var ret = fire_event('onPaste', [event]);\n if (ret) {\n if (is_function(ret.then || ret.done)) {\n return (ret.then || ret.done).call(ret, function(ret) {\n echo(ret, true);\n });\n } else {\n echo(ret, true);\n }\n } else if (ret !== false) {\n echo(event.image || event.text, true);\n }\n } else if (object instanceof Blob) {\n echo_image(data_uri(object));\n } else if (typeof object === 'string') {\n if (object.match(/^(data:|blob:)/)) {\n echo_image(object);\n } else {\n self.insert(object);\n }\n }\n }\n if (e.clipboardData) {\n if (self.enabled()) {\n var items = e.clipboardData.items;\n if (items) {\n for (var i = 0; i < items.length; i++) {\n if (is_type(items[i], 'image') && settings.pasteImage) {\n var blob = items[i].getAsFile();\n echo(blob);\n } else if (is_type(items[i], 'text/plain')) {\n items[i].getAsString(function(text) {\n echo(text.replace(/\\r/g, ''));\n });\n }\n }\n } else if (e.clipboardData.getData) {\n var text = e.clipboardData.getData('text/plain');\n echo(text.replace(/\\r/g, ''));\n }\n return false;\n }\n }\n }\n $(document).on('paste.terminal_' + self.id(), paste_event);\n var terminal_init_keymap = $.extend(\n {},\n keymap,\n $.omap(settings.keymap || {}, function(key, fn) {\n if (!keymap[key]) {\n return fn.bind(self);\n }\n return function(e, original) {\n // new keymap function will get default as 2nd argument\n return fn.call(self, e, function() {\n return keymap[key](e, original);\n });\n };\n })\n );\n make_interpreter(init_interpreter, settings.login, function(interpreter) {\n if (settings.completion && typeof settings.completion !== 'boolean' ||\n !settings.completion) {\n // overwrite interpreter completion by global setting #224\n // we use string to indicate that it need to be taken from settings\n // so we are able to change it using option API method\n interpreter.completion = 'settings';\n }\n var prompt = settings.prompt;\n if (is_function(prompt)) {\n prompt = context_callback_proxy(prompt);\n }\n interpreters = new Stack($.extend({}, settings.extra, {\n name: settings.name,\n prompt: prompt,\n keypress: settings.keypress,\n keydown: settings.keydown,\n resize: settings.onResize,\n greetings: settings.greetings,\n mousewheel: settings.mousewheel,\n history: settings.history,\n keymap: terminal_init_keymap\n }, interpreter));\n // CREATE COMMAND LINE\n command_line = $('').appendTo(wrapper).cmd({\n tabindex: settings.tabindex,\n mobileDelete: settings.mobileDelete,\n mobileIngoreAutoSpace: settings.mobileIngoreAutoSpace,\n prompt: global_login_fn ? false : prompt,\n history: settings.memory ? 'memory' : settings.history,\n historyFilter: settings.historyFilter,\n historySize: settings.historySize,\n caseSensitiveSearch: settings.caseSensitiveSearch,\n onPaste: settings.onPaste,\n width: '100%',\n enabled: false,\n charWidth: char_size.width,\n keydown: key_down,\n keymap: terminal_init_keymap,\n clickTimeout: settings.clickTimeout,\n holdTimeout: settings.holdTimeout,\n holdRepeatTimeout: settings.holdRepeatTimeout,\n repeatTimeoutKeys: settings.repeatTimeoutKeys,\n allowedAttributes: settings.allowedAttributes,\n keypress: key_press,\n tabs: settings.tabs,\n onPositionChange: function() {\n var args = [].slice.call(arguments);\n make_cursor_visible();\n fire_event('onPositionChange', args);\n },\n onCommandChange: function(command) {\n // resize is not triggered when insert called just after init\n // and scrollbar appear\n if (old_width !== fill.width()) {\n // resizer handler will update old_width\n self.resizer();\n }\n fire_event('onCommandChange', [command]);\n make_cursor_visible();\n },\n commands: commands\n });\n function disable(e) {\n if (is_mobile) {\n return;\n }\n e = e.originalEvent;\n if (e) {\n // e.terget is body when click outside of context menu to close it\n // even if you click on terminal\n var node = document.elementFromPoint(e.clientX, e.clientY);\n if (!$(node).closest('.terminal').length && self.enabled()) {\n // we only need to disable when click outside of terminal\n // click on other terminal is handled by focus event\n self.disable();\n }\n }\n }\n self.oneTime(100, function() {\n $(document).bind('click.terminal_' + self.id(), disable).\n bind('contextmenu.terminal_' + self.id(), disable);\n });\n var $win = $(window);\n // cordova application, if keyboard was open and we resume, it will be\n // closed so we need to disable terminal so you can enable it with tap\n document.addEventListener(\"resume\", function() {\n self.disable();\n });\n // istanbul ignore next\n if (is_mobile) {\n (function() {\n self.addClass('terminal-mobile');\n var start;\n var move;\n var enabled;\n var SENSITIVITY = 3;\n var clip = command_line.clip();\n var HOLD_TIME = 200;\n var timer;\n clip.$node.off('touchstart.cmd');\n self.on('touchstart.terminal', function(e) {\n e = e.originalEvent;\n window.touch_event = e;\n if (e.target.tagName.toLowerCase() === 'a') {\n return;\n }\n if (!frozen && e.touches.length === 1) {\n enabled = self.enabled();\n var point = e.touches[0];\n start = {\n x: point.clientX,\n y: point.clientY\n };\n timer = setTimeout(function() {\n start = null;\n }, HOLD_TIME);\n }\n }).on('touchmove.terminal', function(e) {\n if (e.touches.length === 1 && start) {\n var point = e.touches[0];\n var diff_x = Math.abs(point.clientX - start.x);\n var diff_y = Math.abs(point.clientY - start.y);\n if (diff_x > SENSITIVITY || diff_y > SENSITIVITY) {\n move = true;\n }\n }\n }).on('touchend.terminal', function() {\n if (start) {\n clearTimeout(timer);\n if (!move) {\n if (!enabled) {\n clip.focus();\n self.focus();\n } else {\n clip.blur();\n self.disable();\n }\n }\n }\n move = false;\n start = null;\n });\n })();\n } else {\n // work weird on mobile\n $win.on('focus.terminal_' + self.id(), focus_terminal).\n on('blur.terminal_' + self.id(), blur_terminal);\n // context is used to check if terminal should not scroll to bottom\n // after right click on e.g. img, canvas, a and then click to hide\n // the menu. The problem is that right click on those elements don't\n // move the textarea to show proper context menu like save as on images\n // or open on links. See #644 bug\n var was_ctx_event;\n // detect mouse drag\n (function() {\n var count = 0;\n var $target;\n var name = 'click_' + self.id();\n var textarea = self.find('.cmd textarea');\n function click() {\n if ($target.is('.terminal') ||\n $target.is('.terminal-wrapper')) {\n var len = self.get_command().length;\n self.set_position(len);\n } else if ($target.closest('.cmd-prompt').length) {\n self.set_position(0);\n }\n if (!textarea.is(':focus')) {\n textarea.focus();\n }\n reset();\n }\n function reset() {\n count = 0;\n $target = null;\n }\n var ignore_elements = '.terminal-output textarea,' +\n '.terminal-output input';\n self.mousedown(function(e) {\n if (!scrollbar_event(e, fill)) {\n $target = $(e.target);\n }\n }).mouseup(function() {\n if (was_ctx_event) {\n was_ctx_event = false;\n return;\n }\n if ($target && $target.closest(ignore_elements).length) {\n if (enabled) {\n self.disable();\n }\n } else if (get_selected_html() === '' && $target) {\n if (++count === 1) {\n if (!frozen) {\n if (!enabled) {\n self.focus();\n self.scroll_to_bottom();\n } else {\n var timeout = settings.clickTimeout;\n self.oneTime(timeout, name, click);\n return;\n }\n }\n } else {\n self.stopTime(name);\n }\n }\n reset();\n }).dblclick(function() {\n reset();\n self.stopTime(name);\n });\n })();\n (function() {\n var $clip = command_line.clip().$node;\n function is_context_event(e) {\n return e.type === 'mousedown' && e.buttons === 2 ||\n e.type === 'contextmenu';\n }\n var event_name;\n if ('oncontextmenu' in window) {\n event_name = 'contextmenu.terminal';\n } else {\n event_name = 'mousedown.terminal';\n }\n self.on(event_name, function(e) {\n was_ctx_event = get_selected_html() === '' && is_context_event(e);\n if (was_ctx_event) {\n var $target = $(e.target);\n if ($target.is('img,value,audio,object,canvas,a')) {\n return;\n }\n if (!self.enabled()) {\n self.enable();\n }\n var cmd_offset = command_line.offset();\n var cmd_rect = command_line[0].getBoundingClientRect();\n var self_offset = self.offset();\n var top_limit = self_offset.top - cmd_offset.top;\n var top = Math.max(e.pageY - cmd_offset.top - 20, top_limit);\n var left = e.pageX - cmd_offset.left - 20;\n var height = 4 * 14;\n var width = 5 * 14;\n var rect = self[0].getBoundingClientRect();\n // we need width without scrollbar\n var content_width = fill.outerWidth();\n // fix jumping when click near bottom or left edge #592\n var diff_h = (top + cmd_rect.top + height);\n diff_h = diff_h - rect.height - rect.top;\n var diff_w = (left + cmd_rect.left + width);\n // in Chrome scrollbar is added even when width\n // of textarea is smaller, adding 1px solved the issue\n diff_w = diff_w - content_width - rect.left;\n if (diff_h > 0) {\n height -= Math.ceil(diff_h);\n }\n if (diff_w > 0) {\n width -= Math.ceil(diff_w);\n }\n $clip.attr('style', [\n 'left:' + left + 'px !important',\n 'top:' + top + 'px !important',\n 'width:' + width + 'px !important',\n 'height:' + height + 'px !important'\n ].join(';'));\n if (!$clip.is(':focus')) {\n $clip.focus();\n }\n self.stopTime('textarea');\n self.oneTime(100, 'textarea', function() {\n var props = {\n left: '',\n top: '',\n width: '',\n height: ''\n };\n if (!is_css_variables_supported) {\n var in_line = self.find('.cmd .cmd-cursor-line')\n .prevUntil('.cmd-prompt').length;\n props.top = in_line * 14 + 'px';\n }\n $clip.css(props);\n });\n self.stopTime('selection');\n self.everyTime(20, 'selection', function() {\n if ($clip[0].selection !== $clip[0].value) {\n if (get_textarea_selection($clip[0])) {\n clear_textarea_selection($clip[0]);\n select(\n self.find('.terminal-output')[0],\n self.find('.cmd div:last-of-type')[0]\n );\n self.stopTime('selection');\n }\n }\n });\n }\n });\n })();\n self.on('scroll', function() {\n var $textarea = self.find('textarea');\n var rect = self[0].getBoundingClientRect();\n var height = self[0].scrollHeight;\n var scrollTop = self.scrollTop();\n var diff = height - (scrollTop + rect.height);\n // if scrolled to bottom top need to be aligned with cursor line\n // done by CSS file using css variables\n if (diff === 0) {\n $textarea.css('top', '');\n } else {\n $textarea.css('top', -diff);\n }\n });\n }\n self.on('click', 'a', function(e) {\n var $this = $(this);\n if ($this.closest('.terminal-exception').length) {\n var href = $this.attr('href');\n if (href.match(/:[0-9]+$/)) { // display line if specified\n e.preventDefault();\n print_line(href, self.cols());\n }\n }\n // refocus because links have tabindex in case where user want\n // tab change urls, we can ignore this function on click\n if (enabled) {\n self.find('.cmd textarea').focus();\n }\n });\n function calculate_char_size() {\n var width = char_size.width;\n char_size = get_char_size(self);\n if (width !== char_size.width) {\n command_line.option('charWidth', char_size.width).refresh();\n }\n }\n resize();\n function resize() {\n if (self.is(':visible')) {\n var width = fill.width();\n var height = fill.height();\n if (need_char_size_recalculate) {\n need_char_size_recalculate = !terminal_ready(self);\n calculate_char_size();\n }\n // prevent too many calculations in IE\n if (old_height !== height || old_width !== width) {\n self.resize();\n }\n old_height = height;\n old_width = width;\n }\n }\n function create_resizers() {\n var options = {\n prefix: 'terminal-'\n };\n self.resizer('unbind').resizer(resize, options);\n font_resizer.resizer('unbind').resizer(function() {\n calculate_char_size();\n self.resize();\n }, options);\n }\n function bottom_detect(intersections) {\n is_bottom_detected = intersections[0].intersectionRatio === 1;\n }\n function create_bottom_detect() {\n if (window.IntersectionObserver) {\n var top = $('