This tutorial shows how easy it is to create the below shell window with a custom prompt and a new command
hello
.
help
or hit TAB
for a list of commands.The
Josh.Shell
uses local storage to store a history of the commands that you have typed. By default this is keyed with
josh.history. That history is available to all instances of the shell on your site. For this tutorial, we want to make sure we have our own copy, so we don't get commands from other tutorials and examples, so we need to create a history object with its own key:
var history = new Josh.History({ key: 'helloworld.history'});
Now we can create a Shell instance with that history:
var shell = Josh.Shell({history: history});
Now the shell exists but has not yet been activated.
Note on how the shell attaches to its UI elements: By default
Josh.Shell
expects to find a div#shell-panel
that contains a
div#shell-view
. The former is the physical container providing the dimensions of the shell, while the latter is a div the shell will continue to append to and scroll to mimic a screen. If you want to use other div IDs (because you have multiple shells on one page), you can provide
shell-panel-id
and shell-view-id
in the constructor.
The default prompt for Josh is jsh$. Let's create a prompt instead that keeps track of how many times it has been shown:
var promptCounter = 0;
shell.onNewPrompt(function(callback) {
promptCounter++;
callback("[" + promptCounter + "] $ ");
});
onNewPrompt
is called every time Josh needs to re-render the prompt. This happens usually a command is executed, but can also happen on tab completion, when a list of possible completions is shown.
onNewPrompt
expects a function that accepts a callback as its only argument. Josh will not continue until the callback has been called with an html string to display. This allows the prompt to rendered as part of an asynchronous action. For our example, we just increment the promptCounter and send back a simple string with the counter.
Josh implements just three commands out of the box:
help
- show a list of known commandshistory
- show the commands previously enteredclear
- clear the console (i.e. remove all children from div#shell-view
Let's add a new command called hello with tab completion:
shell.setCommandHandler("hello", {
exec: function(cmd, args, callback) {
var arg = args[0] || '';
var response = "who is this " + arg + " you are talking to?";
if(arg === 'josh') {
response = 'pleased to meet you.';
} else if(arg === 'world') {
response = 'world says hi.'
} else if(!arg) {
response = 'who are you saying hello to?';
}
callback(response);
},
completion: function(cmd, arg, line, callback) {
callback(shell.bestMatch(arg, ['world', 'josh']))
}
});
To add a command, simply call shell.setCommandHandler
and provide it at least an
exec
handler, and optionally a completion
handler.
exec
expects a function that takes the name of the called command, an array of whitespace separated arguments to the command and a callback that MUST be called with an html string to output to the console. For our toy command we implement a command named
hello
which understands arguments josh and
world and has alternate outputs for no arguments and unknown arguments.
completion
expects a function that takes the current command, the current argument being completed, the complete line (since the cursor may not be at the tail) and a callback that MUST be called either with a completion data structure. The format of this data structure is:
{
completion: {string to append to current argument},
suggestions: [{array of possible completions},...]
}
Here are some expected completions:
hello <TAB>
=> {completion:null,suggestions:['world', 'josh']}
hello wo<TAB>
=> {completion:'rld',suggestions:['world']}
hello x<TAB>
=> {completion:'',suggestions:[]}
To simplify this process of finding the partial strings and possible completions, Shell offers a method
bestMatch
which expects as input the partial to match (our arg to the completion handler) and a list of all possible completions and it will narrow down what to append to the partial and what suggestions to show.
Now that we've added our custom behavior to
Josh.Shell
, all we have to do is activate the shell to render the prompt and start capturing all keystrokes via readline (i.e. if you want the shell to only capture keys while the shell has focus, it is up to you to write focus and blur code to activate and deactivate the shell.)
shell.activate();
And that's all there is to getting a custom Bash-like shell in your web page.