Skip to content

rohanchandra/javascript-terminal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

logo

JavaScript Terminal

An open-source JavaScript terminal emulator library, that works in your browser and Node.js.

Demo

Features

  • In-memory file system, backed by Immutable.js
  • Selected *NIX commands emulated (such as ls, cd, head, cat, echo, rm)
  • Command parsing
  • Support for environment variables
  • Autocompletion of terminal commands

Installation

Install with npm or with yarn.

npm install javascript-terminal --save
yarn add javascript-terminal

Usage

Creating an emulator

Create a new instance of the terminal emulator:

const emulator = new Terminal.Emulator();
const emulatorState = Terminal.EmulatorState.createEmpty();

In all examples below it is assumed that emulator and emulatorState have been created.

Running commands

Once you've created the emulator and emulatorState, you can run commands! emulator.execute is used to run a command string from the user, and it returns the new emulator state.

const commandStr = 'ls';
const plugins = [];

const newEmulatorState = emulator.execute(emulatorState, commandStr, plugins)

In the example above, newEmulatorState now contains the updated emulator state after the side-effects of running the ls command string.

You can then see the updated outputs using:

newEmulatorState.getOutputs()

Putting everything together you now have enough knowledge to build a simple terminal emulator in Node.js! Check out the demo code, or keep reading for more advanced features.

Autocomplete

The terminal can autocomplete user input using a partial command, filename or folder name.

Call emulator.autocomplete with the emulatorState and a partially completed string to get autocompleted text. If no autocompletion can be made, the original string is returned.

const partialString = 'ech';

emulator.autocomplete(emulatorState, partialString); // autocompletes to 'echo'

History iteration

The history of the terminal can be navigated using a keyboard.

Create History Keyboard Plugin To enable history iteration, create the history keyboard plugin.

const historyKeyboardPlugin = new Terminal.HistoryKeyboardPlugin(emulatorState);

Updating History Keyboard Plugin When running a command, provide the history keyboard plugin in the call to emulator.execute:

const commandStr = 'ls'; // commandStr contains the user input

emulator.execute(emulatorState, commandStr, [historyKeyboardPlugin]);

This ensures that the history keyboard plugin has the latest state required for history iteration.

Iteration When the user presses the up key call completeUp().

historyKeyboardPlugin.completeUp() // returns string from history stack

When the user presses the down key call completeDown().

historyKeyboardPlugin.completeDown() // returns string from history stack

Emulator state

The emulator state encapsulates the stateful elements of the emulator: the file system, command mapping, history, outputs and environment variables.

The default emulator state is created with createEmpty().

const emulatorState = Terminal.EmulatorState.createEmpty();

Elements of the emulator state can be accessed and updated during runtime using the getters and setters:

  • getFileSystem() and setFileSystem(newFileSystem)
  • getEnvVariables() and setEnvVariables(newEnvVariables)
  • getHistory() and setHistory(newHistory)
  • getOutputs() and setOutputs(newOutputs)
  • getCommandMapping() and setCommandMapping(newCommandMapping)

Using a setter returns a new instance of the emulator state.

For example:

const defaultState = Terminal.EmulatorState.createEmpty();
const defaultOutputs = defaultState.getOutputs();

const newOutputs = Terminal.Outputs.addRecord(
  defaultOutputs, Terminal.OutputFactory.makeTextOutput('added output')
);
const emulatorState = defaultState.setOutputs(newOutputs);

Notice how setting the new outputs with setOutputs returns new emulatorState by updating the old defaultState.

Customising the emulator state

To create emulator state with customised elements (e.g. a custom file system), use a JavaScript object with create() which conforms to the following object schema:

const emulatorState = Terminal.EmulatorState.create({
  'fs': customFileSystem,
  'environmentVariables': customEnvVariables,
  'history': customHistory,
  'outputs': customOutputs,
  'commandMapping': customCommandMapping
})

The JavaScript object only needs to have the keys of the element you wish to adjust. Default elements will be used as a fallback. For example, if you only need a custom file system your code would look like this:

const emulatorState = Terminal.EmulatorState.create({
  'fs': customFileSystem
})

Examples of creating and modifying custom elements follow.

File system

Terminal.FileSystem allows the creation of an in-memory file system with create using a JavaScript object.

Files and directories should be represented using a JavaScript object. File objects are differentiated from directories by use of a content key.

canModify: false in the object can be used to prevent modification of the file system object (such as deletion or renaming) of the file/directory and its children files/directories (if any).

const customFileSystem = Terminal.FileSystem.create({
  '/home': { },
  '/home/README': {content: 'This is a text file', canModify: false},
  '/home/nested/directory/file': {content: 'End of nested directory!'}
});

// Using a custom operation
const isHomeDefined = Terminal.DirOp.hasDirectory(customFileSystem, '/home')

The created file system (in customFileSystem) can be read or modified using a DirOp or FileOp (see the source code for JSDoc comments explaining each operation).

Command mapping

The command mapping can be used to add a new command to the terminal.

Here is an example of adding a command to print the contents of the arguments:

const customCommandMapping = Terminal.CommandMapping.create({
  ...Terminal.defaultCommandMapping,
  'print': {
    'function': (state, opts) => {
      const input = opts.join(' ');

      return {
        output: OutputFactory.makeTextOutput(input)
      };
    },
    'optDef': {}
  }
});

For more advanced source code examples of how commands are defined view the commands directory.

History

History contains a list of previously run commands. The list can be displayed to the user using the history command.

const customHistory = Terminal.History.create(['a', 'b']);
Outputs

Outputs contains a list which contains all emulator output including text output, error output and command headers. These outputs are intended to be visible to the user.

If a command has output, it will be appended to the outputs list.

const textOutput = Terminal.OutputFactory.makeTextOutput(
  `This is an example of adding an output to display to the user without running any command.`
);

const customOutputs = Terminal.Outputs.create([textOutput]);
Environment variables

Environment variables can be accessed inside a command's code or by the user (for example, by executing the echo command).

const defaultEnvVariables = Terminal.EnvironmentVariables.create();
const customEnvVariables = Terminal.EnvironmentVariables.setEnvironmentVariable(
  defaultEnvVariables, 'CUSTOM_ENV_VARIABLE', 'this is the value'
);

Examples

This library does not prescribe a method for displaying terminal output or the user interface, so I've provided examples in Node.js, pure JavaScript/HTML/CSS and with React/JavaScript/HTML/CSS:

  1. View the /demo-cli directory for an example of usage in Node.js
  2. View the /demo-web directory for an example of usage in plain HTML and JavaScript
  3. Visit the React Terminal Component website for usage with HTML, CSS, JavaScript and React
  4. See react-native-terminal-component by @Cawfee for a React Native wrapper (fork)

Building

Set-up

First, make sure you have Node.js, Yarn and Git installed.

Now, fork and clone repo and install the dependencies.

git clone https://github.com/rohanchandra/javascript-terminal.git
cd javascript-terminal/
yarn install

Scripts

Build scripts

  • yarn build - creates a development and production build of the library in lib

Test scripts

  • yarn test - run tests
  • yarn test:min - run tests with summary reports
  • yarn test:coverage - shows test coverage stats
  • yarn artifact:coverage-report - creates HTML test coverage report in .nyc_output

Demo scripts

  • yarn cli - demo of using the emulator library in a Node.js command-line interface (this requires you have built the library with yarn build)

License

Copyright 2018 Rohan Chandra

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.