<?php
require('json_rpc.php');
class Demo {
static $login_documentation = "return auth token";
public function login($user, $passwd) {
if (strcmp($user, 'demo') == 0 &&
strcmp($passwd, 'demo') == 0) {
// If you need to handle more than one user you can
// create new token and save it in database
return md5($user . ":" . $passwd);
} else {
throw new Exception("Wrong Password");
}
}
static $ls_documentation = "list directory if token is" .
" valid";
public function ls($token, $path) {
if (strcmp(md5("demo:demo"), $token) == 0) {
if (preg_match("/\.\./", $path)) {
throw new Exception("No directory traversal Dude");
}
$base = preg_replace("/(.*\/).*/", "$1",
$_SERVER["SCRIPT_FILENAME"]);
$path = $base . ($path[0] != '/' ? "/" : "") . $path;
$dir = opendir($path);
while($name = readdir($dir)) {
$fname = $path."/".$name;
if (!is_dir($name) && !is_dir($fname)) {
$list[] = $name;
}
}
closedir($dir);
return $list;
} else {
throw new Exception("Access Denied");
}
}
static $whoami_documentation = "return user information";
public function whoami() {
return array(
"user-agent" => $_SERVER["HTTP_USER_AGENT"],
"your ip" => $_SERVER['REMOTE_ADDR'],
"referer" => $_SERVER["HTTP_REFERER"],
"request uri" => $_SERVER["REQUEST_URI"]);
}
}
handle_json_rpc(new Demo());
?>
NOTE: If you use json_rpc.php file (which handle json-rpc) from the package you have always help function which display all methods or documentation strings if you provide them.
If you want secure login you should generate random token in login JSON-RPC function, and store it in database or session. For example: md5(time()). You can also use SSL to make it more secured.
See demo in action. login is "demo" and password is "demo". Available command are "ls", "whoami", "help" and "help [rpc-method]"
You can use different server side language instead of php.
Autocomplete
Adding autocomplete to terminal is simple use complete option with array or function as in api documentation or true value if you use JSON-RPC with system.describe or object as interpreter.
You can also create custom completion, for instance add, menu with items that you can click on that's added on keypress, From version 0.12.0 of the terminal there are two new api methods complete and before_cursor that simplify the code.
From version 2.8.0 repo now includes the file that will add menu autocomplete automatically.
The complition options is the same, it can call callback function 2 argument, or return a promise. The value need to be array of strings.
Below is first, original code that shows example, how to create autocomplete menu.
var ul;
var cmd;
var empty = {
options: [],
args: []
};
var commands = {
'get-command': {
options: ['name', 'age', 'description', 'address'],
args: ['clear']
},
'git': {
args: ['commit', 'push', 'pull'],
options: ['amend', 'hard', 'version', 'help']
},
'get-name': empty,
'get-age': empty,
'get-money': empty
};
var ul;
var term = $('body').terminal($.noop, {
onInit: function(term) {
var wrapper = term.cmd().find('.cursor').wrap('<span/>').parent()
.addClass('cmd-wrapper');
ul = $('<ul></ul>').appendTo(wrapper);
ul.on('click', 'li', function() {
term.insert($(this).text());
ul.empty();
});
},
keydown: function(e) {
var term = this;
// setTimeout because terminal is adding characters in keypress
// we use keydown because we need to prevent default action for
// tab and still execute custom code
setTimeout(function() {
ul.empty();
var command = term.get_command();
var name = command.match(/^([^\s]*)/)[0];
if (name) {
var word = term.before_cursor(true);
var regex = new RegExp('^' + $.terminal.escape_regex(word));
var list;
if (name == word) {
list = Object.keys(commands);
} else if (command.match(/\s/)) {
if (commands[name]) {
if (word.match(/^--/)) {
list = commands[name].options.map(function(option) {
return '--' + option;
});
} else {
list = commands[name].args;
}
}
}
if (word.length >= 2 && list) {
var matched = [];
for (var i=list.length; i--;) {
if (regex.test(list[i])) {
matched.push(list[i]);
}
}
var insert = false;
if (e.which == 9) {
insert = term.complete(matched);
}
if (matched.length && !insert) {
ul.hide();
for (var i=0; i<matched.length; ++i) {
var str = matched[i].replace(regex, '');
$('<li>' + str + '</li>').appendTo(ul);
}
ul.show();
}
}
}
}, 0);
if (e.which == 9) {
return false;
}
}
});
If you want to add formatting for different sql command and not for main interpterer you can use stack of formatters. It require version >=1.0 that introduce extra option for interpreter. The example will work for any number of nested interpreters even you call push new in your mysql command.
// this regex will allow mixed case like SeLect
var re = new RegExp('^(' + uppercase.join('|') + ')$', 'i');
function mysql_formatter(string) {
return string.split(/((?:\s| )+)/).map(function(string) {
if (re.test(string)) {
return '[[b;white;]' + string + ']';
} else {
return string;
}
}).join('');
}
var formatters = [$.terminal.defaults.formatters];
$('body').terminal(function(command, term) {
if (command.match(/^\s*mysql\s*$/)) {
term.push(function(query) {
term.echo('executing ' + query, {formatters: false});
}, {
prompt: 'mysql> ',
name: 'mysql',
// extra property saved in interpreter
formatters: [mysql_formatter],
completion: keywords
});
}
}, {
onPush: function(before, after) {
$.terminal.defaults.formatters = after.formatters || [];
formatters.push($.terminal.defaults.formatters);
},
onPop: function(before, after) {
formatters.pop();
if (formatters.length > 0) {
$.terminal.defaults.formatters = formatters[formatters.length-1];
}
}
});
(function($) {
$.extend_if_has = function(desc, source, array) {
for (var i=array.length;i--;) {
if (typeof source[array[i]] != 'undefined') {
desc[array[i]] = source[array[i]];
}
}
return desc;
};
$.fn.dterm = function(interpeter, options) {
var defaults = Object.keys($.terminal.defaults);
var op = $.extend_if_has({}, options, defaults);
var term = this.append('<div/>').
terminal(interpeter, op);
if (!options.title) {
options.title = 'JQuery Terminal Emulator';
}
if (options.logoutOnClose) {
options.close = function(e, ui) {
term.logout();
term.clear();
};
} else {
options.close = function(e, ui) {
term.focus(false);
};
}
var self = this;
if (window.IntersectionObserver) {
var visibility_observer = new IntersectionObserver(function() {
if (self.is(':visible')) {
terminal.enable().resize();
} else {
self.disable();
}
}, {
root: document.body
});
visibility_observer.observe(terminal[0]);
}
this.dialog($.extend({}, options, {
resizeStop: function() {
var content = self.find('.ui-dialog-content');
terminal.resize(content.width(), content.height());
},
open: function(event, ui) {
if (!window.IntersectionObserver) {
setTimeout(function() {
terminal.enable().resize();
}, 100);
}
if (typeof options.open == 'function') {
options.open(event, ui);
}
},
show: 'fade',
closeOnEscape: false
}));
self.terminal = terminal;
return self;
};
})(jQuery);
Demo Scheme interpreter inside JQuery UI Dialog.
Click on button to with scheme interpreter inside UI Dialog.
Hint: you can use JQuery from scheme. There is defined $ function and functions for all jquery object methods, they names start with coma and they always return jquery object so you can do chaining.
NOTE: you should include jQuery Terminal css file after jQuery UI one otherwise you will have white text in terminal, insided of gray.
Interpreter allow to use multiline expressions. When you type not finished S-Expresion it change the prompt with set_prompt, contatenate current command with previous not finished expression and when you close last parentises end press enter it evaluate whole expression.
mysql - which call json-rpc service to execute mysql commands.
test - it display "pong" if you type "ping"
jQuery(function($) {
$('html').terminal(function(cmd, term) {
if (cmd == 'help') {
term.echo("available commands are mysql, js, test");
} else if (cmd == 'test'){
term.push(function(cmd, term) {
if (command == 'help') {
term.echo('type "ping" it will display "pong"');
} else if (cmd == 'ping') {
term.echo('pong');
} else {
term.echo('unknown command "' + cmd + '"');
}
}, {
prompt: 'test> ',
name: 'test'});
} else if (command == "js") {
term.push(function(command, term) {
var result = window.eval(command);
if (result != undefined) {
term.echo(String(result));
}
}, {
name: 'js',
prompt: 'js> '});
} else if (command == 'mysql') {
term.push(function(command, term) {
term.pause();
//$.jrpc is helper function which
//creates json-rpc request
$.jrpc("mysql-rpc-demo.php",
"query",
[command],
function(data) {
term.resume();
if (data.error) {
if (data.error.error && data.error.error.message) {
term.error(data.error.error.message); // php error
} else {
term.error(data.error.message); // json rpc error
}
} else {
if (typeof data.result == 'boolean') {
term.echo(data.result ?
'success' :
'fail');
} else {
var len = data.result.length;
for(var i=0;i<len; ++i) {
term.echo(data.result[i].join(' | '));
}
}
}
},
function(xhr, status, error) {
term.error('[AJAX] ' + status +
' - Server reponse is: \n' +
xhr.responseText);
term.resume();
}); // rpc call
}, {
greetings: "This is example of using mysql"+
" from terminal\n you are allowed to exe"+
"cute: select, insert, update and delete"+
" from/to table:\n table test(integer_"+
"value integer, varchar_value varchar(255))",
prompt: "mysql> "});
} else {
term.echo("unknow command " + command);
}
}, {
greetings: "multiple terminals demo use help"+
" to see available commands"
});});
If you want to display ascii table like real mysql command, take a look at asci_table function in leash project, it use wcwidth to calcuate the width of the characters but if you don't care about chenese characters you can replace it with string.length.
PHP code for mysql service:
<?php
require('json_rpc.php');
$conn = mysql_connect('localhost', 'user', 'password');
mysql_select_db('database');
class MysqlDemo {
public function query($query) {
if (preg_match("/create|drop/", $query)) {
throw new Exception("Sorry you are not allowed to ".
"execute '" . $query . "'");
}
if (!preg_match("/(select.*from *test|insert *into *".
"test.*|delete *from *test|update *t".
"est)/", $query)) {
throw new Exception("Sorry you can't execute '" .
$query . "' you are only allow".
"ed to select, insert, delete ".
"or update 'test' table");
}
if ($res = mysql_query($query)) {
if ($res === true) {
return true;
}
if (mysql_num_rows($res) > 0) {
while ($row = mysql_fetch_row($res)) {
$result[] = $row;
}
return $result;
} else {
return array();
}
} else {
throw new Exception("MySQL Error: ".mysql_error());
}
}
}
handle_json_rpc(new MysqlDemo());
?>
$(function() {
var frames = [];
var LINES_PER_FRAME = 14;
var DELAY = 67;
//star_wars is array of lines from 'js/star_wars.js'
var lines = star_wars.length;
for (var i=0; i<lines; i+=LINES_PER_FRAME) {
frames.push(star_wars.slice(i, i+LINES_PER_FRAME));
}
var stop = false;
//to show greetings after clearing the terminal
function greetings(term) {
term.echo('STAR WARS ASCIIMACTION\n'+
'Simon Jansen (C) 1997 - 2008\n'+
'www.asciimation.co.nz\n\n'+
'type "play" to start animation, '+
'press CTRL+D to stop');
}
function play(term, delay) {
var i = 0;
var next_delay;
if (delay == undefined) {
delay = DELAY;
}
function display() {
if (i == frames.length) {
i = 0;
}
term.clear();
if (frames[i][0].match(/[0-9]+/)) {
next_delay = frames[i][0] * delay;
} else {
next_delay = delay;
}
term.echo(frames[i++].slice(1).join('\n')+'\n');
if (!stop) {
setTimeout(display, next_delay);
} else {
term.clear();
greetings(term);
i = 0;
}
}
display();
}
$('#starwarsterm').terminal(function(command, term){
if (command == 'play') {
term.pause();
stop = false;
play(term);
}
}, {
width: 500,
height: 230,
prompt: 'starwars> ',
greetings: null,
onInit: function(term) {
greetings(term);
},
keypress: function(e, term) {
if (e.which == 100 && e.ctrlKey) {
stop = true;
term.resume();
return false;
}
}
});
});
Ask before executing a command
Someone ask me how to create, command that ask users before executing, and here is the code, it will keep asking until eather yes or no will be entered (or short y/n).
$('#term').terminal(function(command, term) {
if (command == 'foo') {
var history = term.history();
history.disable();
term.push(function(command) {
if (command.match(/^(y|yes)$/i)) {
term.echo('execute your command here');
term.pop();
history.enable();
} else if (command.match(/^(n|no)$/i)) {
term.pop();
history.enable();
}
}, {
prompt: 'Are you sure? '
});
}
});
Animation that emulate user typing
NOTE: in version 2.24.0 typing animation was added to the library. No animate all you have to do is:
The function also return a promise so you can use return value instead of a callback function.
Someone else asked if it's posible to create animation like user typing. Here is the code that emulate user typing on initialization of the terminal and before every ajax call, which can finish after animation.
$(function() {
var anim = false;
function typed(finish_typing) {
return function(term, message, delay, finish) {
anim = true;
var prompt = term.get_prompt();
var c = 0;
if (message.length > 0) {
term.set_prompt('');
var new_prompt = '';
var interval = setInterval(function() {
var chr = $.terminal.substring(message, c, c+1);
new_prompt += chr;
term.set_prompt(new_prompt);
c++;
if (c == length(message)) {
clearInterval(interval);
// execute in next interval
setTimeout(function() {
// swap command with prompt
finish_typing(term, message, prompt);
anim = false
finish && finish();
}, delay);
}
}, delay);
}
};
}
function length(string) {
string = $.terminal.strip(string);
return $('<span>' + string + '</span>').text().length;
}
var typed_prompt = typed(function(term, message, prompt) {
term.set_prompt(message + ' ');
});
var typed_message = typed(function(term, message, prompt) {
term.echo(message)
term.set_prompt(prompt);
});
$('body').terminal(function(cmd, term) {
var finish = false;
var msg = "Wait I'm executing ajax call";
term.set_prompt('> ');
typed_message(term, msg, 200, function() {
finish = true;
});
var args = {command: cmd};
$.get('commands.php', args, function(result) {
(function wait() {
if (finish) {
term.echo(result);
} else {
setTimeout(wait, 500);
}
})();
});
}, {
name: 'xxx',
greetings: null,
width: 500,
height: 300,
onInit: function(term) {
// first question
var msg = "Wellcome to my terminal";
typed_message(term, msg, 200, function() {
typed_prompt(term, "what's your name:", 100);
});
},
keydown: function(e) {
//disable keyboard when animating
if (anim) {
return false;
}
}
});
});
Progress bar animation
You can test it by executing command `progress 30`.
Here is the code for progres bar animation:
jQuery(function($) {
function progress(percent, width) {
var size = Math.round(width*percent/100);
var left = '', taken = '', i;
for (i=size; i--;) {
taken += '=';
}
if (taken.length > 0) {
taken = taken.replace(/=$/, '>');
}
for (i=width-size; i--;) {
left += ' ';
}
return '[' + taken + left + '] ' + percent + '%';
}
var animation = false;
var timer;
var prompt;
var string;
$('body').terminal(function(command, term) {
var cmd = $.terminal.parse_command(command);
if (cmd.name == 'progress') {
var i = 0, size = cmd.args[0];
prompt = term.get_prompt();
string = progress(0, size);
term.set_prompt(progress);
animation = true;
(function loop() {
string = progress(i++, size);
term.set_prompt(string);
if (i < 100) {
timer = setTimeout(loop, 100);
} else {
term.echo(progress(i, size) + ' [[b;green;]OK]')
.set_prompt(prompt);
animation = false
}
})();
}
}, {
keydown: function(e, term) {
if (animation) {
if (e.which == 68 && e.ctrlKey) { // CTRL+D
clearTimeout(timer);
animation = false;
term.echo(string + ' [[b;red;]FAIL]')
.set_prompt(prompt);
}
return false;
}
}
});
});
var resize = [];
$('<SELECTOR>').terminal(function(command, term) {
if (command.match(/ *less +[^ ]+/)) {
term.pause();
$.ajax({
// leading and trailing spaces and keep those inside argument
url: command.replace(/^\s+|\s+$/g, '').
replace(/^ */, '').split(/(\s+)/).slice(2).join(''),
method: 'GET',
dataType: 'text',
success: function(source) {
term.resume();
var export_data = term.export_view();
var less = true;
source = source.replace(/&/g, '&').
replace(/\[/g, '[').
replace(/\]/g, ']');
var cols = term.cols();
var rows = term.rows();
resize = [];
var lines = source.split('\n');
resize.push(function() {
if (less) {
cols = term.cols();
rows = term.rows();
print();
}
});
var pos = 0;
function print() {
term.clear();
term.echo(lines.slice(pos, pos+rows-1).join('\n'));
}
print();
term.push($.noop, {
keydown: function(e) {
if (term.get_prompt() !== '/') {
if (e.which == 191) {
term.set_prompt('/');
} else if (e.which === 38) { //up
if (pos > 0) {
--pos;
print();
}
} else if (e.which === 40) { //down
if (pos < lines.length-1) {
++pos;
print();
}
} else if (e.which === 34) { // Page up
pos += rows;
if (pos > lines.length-1-rows) {
pos = lines.length-1-rows;
}
print();
} else if (e.which === 33) { // page down
pos -= rows;
if (pos < 0) {
pos = 0;
}
print();
} else if (e.which == 81) { //Q
less = false;
term.pop().import_view(export_data);
}
return false;
} else {
if (e.which === 8 && term.get_command() === '') {
term.set_prompt(':');
} else if (e.which == 13) {
var command = term.get_command();
// basic search find only first
// instance and don't mark the result
if (command.length > 0) {
var regex = new RegExp(command);
for (var i=0; i<lines.length; ++i) {
if (regex.test(lines[i])) {
pos = i;
print();
term.set_command('');
break;
}
}
term.set_command('');
term.set_prompt(':');
}
return false;
}
}
},
prompt: ':'
});
}
});
}
}, {
onResize: function(term) {
for (var i=resize.length;i--;) {
resize[i](term);
}
}
});
Improved less command with syntax highlighting can be found in this pen.
Pipe operator
From version 2.4.0 there is file pipe.js include in npm and unpkg that have pipe operator. ALl you need is to include the file and you can use new pipe option to enable pipe.
var count = 0;
var term = $('body').terminal({
echo: function(string) {
return new Promise(function(resolve) {
term.echo(string);
setTimeout(resolve, 1000);
});
},
read: function() {
return term.read('').then(function(string) {
term.echo('read[' +(++count)+']: ' + string);
});
}
}, {
pipe: true
});
Pipe depend on read and echo that can be used as stdin and stdout in real terminal to combine the commands
You can execute echo foo | read or even echo foo | read | read it will also work if pipe is in string like this echo "pipe |" | read (it will also work if you escape quotes or pipe)
If you want to add bash history commands like !!, !$ or !* here is the code:
$('body').terminal(function(command, term) {
var cmd = $.terminal.parse_command(command);
if (command.match(/![*$]|\s*!!(:p)?\s*$|\s*!(.*)/)) {
var new_command;
var history = term.history();
var last = $.terminal.parse_command(history.last());
var match = command.match(/\s*!(?![!$*])(.*)/);
if (match) {
var re = new RegExp($.terminal.escape_regex(match[1]));
var history_data = history.data();
for (var i=history_data.length; i--;) {
if (re.test(history_data[i])) {
new_command = history_data[i];
break;
}
}
if (!new_command) {
var msg = $.terminal.defaults.strings.commandNotFound;
term.error(sprintf(msg, $.terminal.escape_brackets(match[1])));
}
} else if (command.match(/![*$]/)) {
if (last.args.length) {
var last_arg = last.args[last.args.length-1];
new_command = command.replace(/!\$/g, last_arg);
}
new_command = new_command.replace(/!\*/g, last.rest);
} else if (command.match(/\s*!!(:p)?/)) {
new_command = last.command;
}
if (new_command) {
term.echo(new_command);
}
if (!command.match(/![*$!]:p/)) {
if (new_command) {
term.exec(new_command, true);
}
}
} else if (cmd.name == 'echo') {
term.echo(cmd.rest);
}
}, {
// we need to disable history for bash history commands
historyFilter: function(command) {
return !command.match(/![*$]|\s*!!(:p)?\s*$|\s*!(.*)/);
}
});
Smooth CSS3 cursor animation
From version 0.8 terminal use CSS animation for blinking so you can change it without touching JavaScript code.
Here is different looking cursor blinking animation that can be use with terminal.
There were problems with terminal on touch devices (it now works without problems). I've found a project Keyboard that create virtual keyboard using jQuery UI. I've created a demo of working terminal with keyboard. The code still need tweeks to work full screen.
Since keyboard is not working on mobile, this demo is left for historical reason and because it's just one example of the use of terminal. Current version works with native keyboard (tested on Android and iPhone) if you find issues on the phone please add an issue.
Using History API for commands
As a response for this issue on github I came up with a way to keep every command response in history using HTML5 History API, so you can click back and forward buttons and it will show you previous and next commands.
$(function() {
var save_state = [];
var terminal = $('#term').terminal(function(command, term) {
var cmd = $.terminal.split_command(command);
var url;
if (cmd.name == 'open') {
term.pause();
// open html and display it on terminal as it is
url = cmd.args[0];
$.get(url, function(result) {
term.echo(result, {raw:true}).resume();
save_state.push(term.export_view());
history.pushState(save_state.length-1, null, url);
}, 'text');
} else {
// store all other commands
save_state.push(term.export_view());
url = '/' + cmd.name + '/' + cmd.args.join('/');
history.pushState(save_state.length-1, null, url);
}
});
save_state.push(terminal.export_view()); // save initial state
$(window).on('popstate', function(e) {
if (save_state.length) {
terminal.import_view(save_state[history.state || 0]);
}
});
});
So it keep current view of the terminal (after the command finishes) in save_state array and index in push state (I've try to put whole view in history.state but it didn't work). On back/forward buttons click it will get that value from array and restore the view of the terminal.
Version 0.9.0 introduced similar API but using url hash. To enable it use historyState option and to execute hash on load use execHash option.
Shell
You can also check my project LEASH - Browser Shell you will have shell without need to install anything on the server (so you don't need root access), it use lot of features of jQuery terminal, like better less command or python interpreter.
You can also use cordova application that use leash to have access to android shell.
If you want something lightweight you can check jsh.php, it's single file php shell.
To see 404 page (page not found error) just open any non exisitng page like /404. You will have few commands like:
wikipedia article reader with search.
jargon command for jargon file (hacker dictionary), try jargon hacker, you can click on underline terms to read description.
rfc command for reading rfc documents, if you execute without arguments it will show you index page, where you can press / to search and then click on the link, try search http.
jargon command that shows definitions from hacker dictionary.
Games: snake, tetris, rouge.
chunk-norris, fact and joke commands.
chat command that was onece on main page.
qr command that prints QR Code for a given text or URL.
matrix command that shows matrix rain animation.
dmr command that display ANSI art
record command that will save the commands you'll type in hash, so you can share the link to a session (be careful with long sessions, there is limit in URL hash size, depending on browser).
NOTE: if you want to share the link to 404 page with any command (e.g. on Twitter), best way is to use URL shortener like tinyurl.com. Because somtimes the links got broken, brackets in URL can be confusing.
The error page has also terminal based chat, come and say hi, if you stay a little longer you can find some other people. If you leave the page open you will get notication when someonne will write something (sound and favicon will update). Type help to see available commands.
Emoji
Inspired by a comment I've created a demo of emoji in terminal. From version 2.1.0 all needed files are included with the package, you need to include emoji.css and emoji.js and use this code that use iamcal/emoji-data
You can type emoji as unicode characters, limited number of ASCII emoticons, or full name of emoji (the one like :smiley:)
If you want to have tab completion with emoji names, simply get names from JSON data and use it in completion, check codepen demo for details.
Create Settings object from questions
Here is example of list of questions that are ask to the user to fill out. User can confirm
that the options he set are correct, if not he can correct his options. The code support:
Boolean options
String options
Passwords - masked inputs
This can be used as equivalent of the normal html form.
This solution is based on Leash
(visible when installing the app). And came up from
this issue on github.
var mask = ['root_password', 'password'];
var settings = {};
var questions = [
{
name: "root_password",
text: 'Enter your administration password',
prompt: "root password: "
},
{
name: "server",
text: "Type your server name"
},
{
name: "username",
text: "Your normal username"
},
{
name: "home",
text: "Home directory"
},
{
name: 'guest',
text: 'Allow guest sessions (Y)es/(N)o',
boolean: true
},
{
name: 'sudo',
text: 'Execute sudo for user accounts (Y)es/(N)o',
boolean: true
},
{
name: "password"
}
];
function ask_questions(step) {
var question = questions[step];
if (question) {
if (question.text) {
term.echo('[[b;#fff;]' + question.text + ']');
}
var show_mask = mask.indexOf(question.name) != -1;
term.push(function(command) {
if (show_mask) {
term.set_mask(false);
}
if (question.boolean) {
var value;
if (command.match(/^Y(es)?/i)) {
value = true;
} else if (command.match(/^N(o)?/i)) {
value = false;
}
if (typeof value != 'undefined') {
settings[question.name] = value;
term.pop();
ask_questions(step+1);
}
} else {
settings[question.name] = command;
term.pop();
ask_questions(step+1);
}
}, {
prompt: question.prompt || question.name + ": "
});
// set command and mask need to called after push
// otherwise they will not work
if (show_mask) {
term.set_mask(true);
}
if (typeof settings[question.name] != 'undefined') {
if (typeof settings[question.name] == 'boolean') {
term.set_command(settings[question.name] ? 'y' : 'n');
} else {
term.set_command(settings[question.name]);
}
}
} else {
finish();
}
}
function finish() {
term.echo('Your settings:');
var str = Object.keys(settings).map(function(key) {
var value = settings[key];
if (mask.indexOf(key) != -1) {
// mask everything except first and last character
var split = value.match(/^(.)(.*)(.)$/, '*');
value = split[1] + split[2].replace(/./g, '*') + split[3];
}
return '[[b;#fff;]' + key + ']: ' + value;
}).join('\n');
term.echo(str);
term.push(function(command) {
if (command.match(/^y$/i)) {
term.echo(JSON.stringify(settings));
term.pop().history().enable();
} else if (command.match(/^n$/i)) {
term.pop();
ask_questions(0);
}
}, {
prompt: 'Are those correct (y|n): '
});
}
term.history().disable();
ask_questions(0);
Inspired by adsense code, I've create small js file that you can include on your page to load jQuery Termianl, so you don't need to include jQuery or any other files, just one js file.
The spec for array is the same as arguments to terminal but with selector as first element, so second is function string or object or array, and the third are the options.
ReactJS Terminal
Here is example us ReactJS Component with two way data binding. It require version 1.11.0 (to be released, right now on devel branch). The change that was required is to allow to call set_command with silient option to not call onCommandChange event that was causing infinite update on render.
Sorry no color, To add highlighting to jsx I need to update library for sytanx highliting.
If you type closing parenthesis, it will jump to matched open one.
var position;
var timer;
$('body').terminal($.noop, {
name: 'parenthesis',
greetings: "start typing parenthesis",
keydown: function() {
if (position) {
this.set_position(position);
position = false;
}
},
keypress: function(e) {
var term = this;
if (e.key == ')') {
setTimeout(function() {
position = term.get_position();
var command = term.before_cursor();
var count = 1;
var close_pos = position - 1;
var c;
while (count > 0) {
c = command[--close_pos];
if (!c) {
return;
}
if (c === '(') {
count--;
} else if (c == ')') {
count++;
}
}
if (c == '(') {
clearTimeout(timer);
setTimeout(function() {
term.set_position(close_pos);
timer = setTimeout(function() {
term.set_position(position)
position = false;
}, 200);
}, 0);
}
}, 0);
} else {
position = false;
}
}
});
If you want to support case where parenthesis are inside strings (it shouldn't take that one into account) then you
can use this code (taken from my lisp interpteter lips, link in multiline example)
// copy from LIPS source code
var pre_parse_re = /("(?:\\[\S\s]|[^"])*"|\/(?! )[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|\(|\)|$)|;.*)/g;
var tokens_re = /("(?:\\[\S\s]|[^"])*"|\/(?! )[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|\(|\)|$)|\(|\)|'|"(?:\\[\S\s]|[^"])+|(?:\\[\S\s]|[^"])*"|;.*|(?:[-+]?(?:(?:\.[0-9]+|[0-9]+\.[0-9]+)(?:[eE][-+]?[0-9]+)?)|[0-9]+\.)[0-9]|\.|,@|,|`|[^(\s)]+)/gim;
// ----------------------------------------------------------------------
function tokenize(str) {
var count = 0;
var offset = 0;
var tokens = [];
str.split(pre_parse_re).filter(Boolean).forEach(function(string) {
if (string.match(pre_parse_re)) {
if (!string.match(/^;/)) {
var col = (string.split(/\n/), [""]).pop().length;
tokens.push({
token: string,
col,
offset: count + offset,
line: offset
});
count += string.length;
}
offset += (string.match("\n") || []).length;
return;
}
string.split('\n').filter(Boolean).forEach(function(line, i) {
var col = 0;
line.split(tokens_re).filter(Boolean).forEach(function(token) {
var line = i + offset;
var result = {
col,
line,
token,
offset: count + line
};
col += token.length;
count += token.length;
tokens.push(result);
});
});
});
return tokens;
}
var term = $('body').terminal($.noop, {
name: 'lips',
greetings: false,
// below is the code for parenthesis matching (jumping)
keydown: function() {
if (position) {
term.set_position(position);
position = false;
}
},
keypress: function(e) {
var term = this;
if (e.key == ')') {
setTimeout(function() {
position = term.get_position();
var command = term.before_cursor();
var len = command.split(/\n/)[0].length;
var tokens = tokenize(command);
var count = 1;
var token;
var i = tokens.length - 1;
while (count > 0) {
token = tokens[--i];
if (!token) {
return;
}
if (token.token === '(') {
count--;
} else if (token.token == ')') {
count++;
}
}
if (token.token == '(') {
clearTimeout(timer);
setTimeout(function() {
term.set_position(token.offset);
timer = setTimeout(function() {
term.set_position(position)
position = false;
}, 200);
}, 0);
}
}, 0);
} else {
position = false;
}
}
});
The code will tokenize strings and it will not split on parenthesis that are inside regular expressions or strings.
Multiline input
Here is code that can be use together with parenthesis balancing:
// balancing function code is different than previous example
// because we are counting whole command
function balance(code) {
// tokenize from previous example
var tokens = tokenize(code);
var count = 0;
var token;
var i = tokens.length;
while ((token = tokens[--i])) {
if (token.token === '(') {
count++
} else if (token.token == ')') {
count--;
}
}
return count;
}
var term = $('#multiline .term').terminal(function(command) {
console.log({command});
}, {
keymap: {
ENTER: function(e, original) {
if (balance(this.get_command()) === 0) {
original();
} else {
this.insert('\n');
}
}
}
});
If you want to create game like Rouge (game where characters that are elements of environmet
are just ASCII characters, more info on Wikipedia),
you can use rot.js framework,
here is base that you can be used to create
real game. It have random generated levels and you collect gold. If you create nice game don't
forget to share. The game don't renders on terminal but on canvas, but when interacting it look like
part of the terminal.
Browser confirm replacement
Here is jQuery plugin that can be used as replacement for native browser function confirm:
$.fn.confirm = async function(message) {
var term = $(this).terminal();
const response = await new Promise(function(resolve) {
term.push(function(command) {
if (command.match(/Y(es)?/i)) {
resolve(true);
} else if (command.match(/N(o)?/i)) {
resolve(false);
}
}, {
prompt: message
});
});
term.pop();
return response;
};
It use new async await syntax that will not work in IE, for this browser you can use this code that only use Promises or like in code below using jQuery Deferred:
$.fn.confirm = function(message) {
var term = $(this).terminal();
var deferred = new $.Deferred();
term.push(function(command) {
if (command.match(/^Y(es)?$/i)) {
deferred.resolve(true);
} else if (command.match(/^N(o)?$/i)) {
deferred.resolve(false);
}
if (command.match(/^(Y(es)?|N(o)?)$/i)) {
term.pop();
}
}, {
prompt: message
});
return deferred.promise();
};
To use the plugin you can use this code:
term.confirm('Are you sure? Y/N ').then(function(confirm) {
if (confirm) {
console.log('User confirm');
} else {
console.log("User didn't confirm");
}
});
Echo without newline
This was requested few times and I've finally created monkey patch for echo command.
From version 2.8.0 repo contain file (the file require version 2.8.0 that added new API method echo_command). If you include the file you will have new option in echo newline (default is true).
Below is code for original example that was adding echo, it was buggy. The one in repo is much better,
and it have unit test coverage.
It will not be added to the library though. It will only work with string prompts,
functions will require more work.
var last;
var term = $('body').terminal($.noop, {
echoCommand: false,
keymap: {
'ENTER': function(e, original) {
var str;
if (!last) {
str = this.get_prompt() + this.get_command();
} else {
var str = last + prompt + this.get_command();
last = '';
}
this.echo(str);
original(e);
}
},
prompt: '>>> '
});
var prompt = term.get_prompt();
(function(echo) {
term.echo = function(arg, options) {
var settings = $.extend({
newline: true
}, options);
if (!prompt) {
prompt = this.get_prompt();
}
if (settings.newline === false) {
// this probably can be simplify because terminal handle
// newlines in prompt
last += arg;
arg += this.get_prompt();
var arr = arg.split('\n');
var last_line;
if (arr.length === 1) {
last_line = arg;
} else {
echo(arr.slice(0, -1).join('\n'), options);
last_line = arr[arr.length - 1];
}
this.set_prompt(last_line);
} else {
if (prompt) {
this.set_prompt(prompt);
}
if (last) {
echo(last + arg, options);
} else {
echo(arg, options);
}
last = '';
prompt = '';
}
};
})(term.echo);
ANSI artwork
From version 2.0.0 that fixed unix_formatting you can view ANSI artwork on Terminal.
First you need to convert the artwork to UTF-8 on Linux you can use iconv command
iconv -f CP437 -t UTF-8 < artwork.ans
You can find ANSI artwork in Fuel Magazine (files with .ans extension) or in google.
Here is codepen demo that display few artworks from fuel. It looks best on Linux and Window 10. Windows 7 and lowwer have broken implementation of Unicode so they don't look good. On MacOS it also don't look good (because of how Unicode characters are rendered) but better than on Windows 7.
Figlet ASCII art fonts
Figlet is a unix utility, that allows to render text using ASCII art fonts.
There is also library in JavaScript on npm, that implement same algorithm (and reuse the fonts).
You can use that library from webpack or from unpgk.com (or different CDN), to render text or
greetings on the terminal.
With this you can have greetings similar to default one (but that was created by hand).
You also don't need to care about escaping special characters.
To see how it will look like in terminal, see this Codepen Demo.
FontAwesome Icons
From version 2.16.0 the library have improvements for handling FontAwesome icons, in output and command line.
If you use onBlur like this, your terminal will not work on mobile.
This is because on Mobile (require by the platform) keyboard can't be
in focus when you open any website. To activate the keyboard, you need
disabled terminal, so you can tap on it to enable and show virtual keyboard.
LTerm — Online bash terminal(emulator) tutorial, source can be found on github.
Ervy — library that allow to render charts in Terminal, uses jQuery Terminal as interactive demo. The same code can be found at CodePen that fixes text selection.
DConsole — Haxe game-like console that provides runtime acess to methods, variables and more.
Laravel Bukkit Console — Laravel package providing remote access to a Bukkit server console using JS/PHP/Laravel and the SwiftAPI Bukkit plugin.
Command-Lime — Show what your CLI application can do using this terminal onboarding mock.
Gaiman — simple programming language that compiles to jQuery Terminal based code. It has Live playgroud app where you can create terminal with simpler code.
SpecOps — SpecOps is a web-based, centralized PowerShell script repository where non-technical users can run scripts via a user-friendly GUI.
Numbat — high precision scientific calculator with full support for physical units.
Murmel — a lightweight JVM-based interpreter/ compiler for Murmel, a Lisp dialect inspired by a subset of Common Lisp use jQuery Terminal on the REPL page.
itridersclub.com — Full screen terminal with ASCII Art and background.
numeus.xyz — web3 related website with commands hidden on the server (it have easter egg command writtten in JavaScript, that can't be found in source code). The code was modified from original solution that don't leak information aboutt hidden command.
wedding.jai.im — use terminal to make OSX like terminal as invitation for a wedding.
premjith.in — Another wedding invitation using Ubuntu command line. (Archive)
weddinng npm package — this package use node to have few commands and it also have web interface using jQuery Terminal (didn't tested it only check the content using unpkg.com).
ed>os — Simple OS like application as home page, where you can open tab with terminal.
Fake Linux Terminal — Terminal inside SVG laptop. This is work in progress and attempt to create working Linux system that will work similar to real Linux written in JavaScript. More on GitHub Repo. WARNING The code use latest development version of the library.
Google Foobar — invitation only website created for recruitment.
Vintage Quiz Terminal App — application inspired by Batman Movie Quiz App that allowed to solve Riddler puzzles. The first version was created for NFT project and later refactored as a standalone configurable app that you can buy on Gumroad.
Inside of a biger projects
os2online — Web based simulation of OS/2 Warp 3.0 operating system use jquery terminal.