Hello World Shell

This tutorial shows how easy it is to create the below shell window with a custom prompt and a new command hello.

Type help or hit TAB for a list of commands.

Creating the Shell

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.

Rendering the prompt

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.

Adding a new command

Josh implements just three commands out of the box:

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:

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.

Turning it on

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.