chjj / blessed

A high-level terminal interface library for node.js.
Other
11.2k stars 528 forks source link

blessed

A curses-like library with a high level terminal interface API for node.js.

blessed

Blessed is over 16,000 lines of code and terminal goodness. It's completely implemented in javascript, and its goal consists of two things:

  1. Reimplement ncurses entirely by parsing and compiling terminfo and termcap, and exposing a Program object which can output escape sequences compatible with any terminal.

  2. Implement a widget API which is heavily optimized for terminals.

The blessed renderer makes use of CSR (change-scroll-region), and BCE (back-color-erase). It draws the screen using the painter's algorithm and is sped up with smart cursor movements and a screen damage buffer. This means rendering of your application will be extremely efficient: blessed only draws the changes (damage) to the screen.

Blessed is arguably as accurate as ncurses, but even more optimized in some ways. The widget library gives you an API which is reminiscent of the DOM. Anyone is able to make an awesome terminal application with blessed. There are terminal widget libraries for other platforms (primarily python and perl), but blessed is possibly the most DOM-like (dare I say the most user-friendly?).

Blessed has been used to implement other popular libraries and programs. Examples include: the slap text editor and blessed-contrib. The blessed API itself has gone on to inspire termui for Go.

Install

$ npm install blessed

Example

This will render a box with line borders containing the text 'Hello world!', perfectly centered horizontally and vertically.

NOTE: It is recommend you use either smartCSR or fastCSR as a blessed.screen option. This will enable CSR when scrolling text in elements or when manipulating lines.

var blessed = require('blessed');

// Create a screen object.
var screen = blessed.screen({
  smartCSR: true
});

screen.title = 'my window title';

// Create a box perfectly centered horizontally and vertically.
var box = blessed.box({
  top: 'center',
  left: 'center',
  width: '50%',
  height: '50%',
  content: 'Hello {bold}world{/bold}!',
  tags: true,
  border: {
    type: 'line'
  },
  style: {
    fg: 'white',
    bg: 'magenta',
    border: {
      fg: '#f0f0f0'
    },
    hover: {
      bg: 'green'
    }
  }
});

// Append our box to the screen.
screen.append(box);

// Add a png icon to the box
var icon = blessed.image({
  parent: box,
  top: 0,
  left: 0,
  type: 'overlay',
  width: 'shrink',
  height: 'shrink',
  file: __dirname + '/my-program-icon.png',
  search: false
});

// If our box is clicked, change the content.
box.on('click', function(data) {
  box.setContent('{center}Some different {red-fg}content{/red-fg}.{/center}');
  screen.render();
});

// If box is focused, handle `enter`/`return` and give us some more content.
box.key('enter', function(ch, key) {
  box.setContent('{right}Even different {black-fg}content{/black-fg}.{/right}\n');
  box.setLine(1, 'bar');
  box.insertLine(1, 'foo');
  screen.render();
});

// Quit on Escape, q, or Control-C.
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
  return process.exit(0);
});

// Focus our element.
box.focus();

// Render the screen.
screen.render();

Documentation

Widgets

Other

Mechanics

Notes

Widgets

Blessed comes with a number of high-level widgets so you can avoid all the nasty low-level terminal stuff.

Base Nodes

Node (from EventEmitter)

The base node which everything inherits from.

Options:
Properties:
Events:
Methods:

Screen (from Node)

The screen on which every other node renders.

Options:
Properties:
Events:
Methods:

Element (from Node)

The base element.

Options:
Properties:
Events:
Methods:
Content Methods

Methods for dealing with text content, line by line. Useful for writing a text editor, irc client, etc.

Note: All of these methods deal with pre-aligned, pre-wrapped text. If you use deleteTop() on a box with a wrapped line at the top, it may remove 3-4 "real" lines (rows) depending on how long the original line was.

The lines parameter can be a string or an array of strings. The line parameter must be a string.

Boxes

Box (from Element)

A box element which draws a simple box containing content or other elements.

Options:
Properties:
Events:
Methods:

Text (from Element)

An element similar to Box, but geared towards rendering simple text elements.

Options:

Inherits all options, properties, events, and methods from Element.

Line (from Box)

A simple line which can be line or bg styled.

Options:

Inherits all options, properties, events, and methods from Box.

ScrollableBox (from Box)

DEPRECATED - Use Box with the scrollable option instead.

A box with scrollable content.

Options:
Properties:
Events:
Methods:

ScrollableText (from ScrollableBox)

DEPRECATED - Use Box with the scrollable and alwaysScroll options instead.

A scrollable text box which can display and scroll text, as well as handle pre-existing newlines and escape codes.

Options:
Properties:
Events:
Methods:

BigText (from Box)

A box which can render content drawn as 8x14 cell characters using the terminus font.

Options:
Properties:
Events:
Methods:

Lists

List (from Box)

A scrollable list which can display selectable items.

Options:
Properties:
Events:
Methods:

FileManager (from List)

A very simple file manager for selecting files.

Options:
Properties:
Events:
Methods:

ListTable (from List)

A stylized table of text elements with a list.

Options:
Properties:
Events:
Methods:

Listbar (from Box)

A horizontal list. Useful for a main menu bar.

Options:
Properties:
Events:
Methods:

Forms

Form (from Box)

A form which can contain form elements.

Options:
Properties:
Events:
Methods:

Input (from Box)

A form input.

Textarea (from Input)

A box which allows multiline text input.

Options:
Properties:
Events:
Methods:

Textbox (from Textarea)

A box which allows text input.

Options:
Properties:
Events:
Methods:

Button (from Input)

A button which can be focused and allows key and mouse input.

Options:
Properties:
Events:
Methods:

Checkbox (from Input)

A checkbox which can be used in a form element.

Options:
Properties:
Events:
Methods:

RadioSet (from Box)

An element wrapping RadioButtons. RadioButtons within this element will be mutually exclusive with each other.

Options:
Properties:
Events:
Methods:

RadioButton (from Checkbox)

A radio button which can be used in a form element.

Options:
Properties:
Events:
Methods:

Prompts

Prompt (from Box)

A prompt box containing a text input, okay, and cancel buttons (automatically hidden).

Options:
Properties:
Events:
Methods:

Question (from Box)

A question box containing okay and cancel buttons (automatically hidden).

Options:
Properties:
Events:
Methods:

Message (from Box)

A box containing a message to be displayed (automatically hidden).

Options:
Properties:
Events:
Methods:

Loading (from Box)

A box with a spinning line to denote loading (automatically hidden).

Options:
Properties:
Events:
Methods:

Data Display

ProgressBar (from Input)

A progress bar allowing various styles. This can also be used as a form input.

Options:
Properties:
Events:
Methods:

Log (from ScrollableText)

A log permanently scrolled to the bottom.

Options:
Properties:
Events:
Methods:

Table (from Box)

A stylized table of text elements.

Options:
Properties:
Events:
Methods:

Special Elements

Terminal (from Box)

A box which spins up a pseudo terminal and renders the output. Useful for writing a terminal multiplexer, or something similar to an mc-like file manager. Requires term.js and pty.js to be installed. See example/multiplex.js for an example terminal multiplexer.

Options:
Properties:
Events:
Methods:

Image (from Box)

Display an image in the terminal (jpeg, png, gif) using either blessed's internal png/gif-to-terminal renderer (using a ANSIImage element) or using w3mimgdisplay (using a OverlayImage element).

Options:
Properties:
Events:
Methods:

ANSIImage (from Box)

Convert any .png file (or .gif, see below) to an ANSI image and display it as an element. This differs from the OverlayImage element in that it uses blessed's internal PNG/GIF parser and does not require external dependencies.

Blessed uses an internal from-scratch PNG/GIF reader because no other javascript PNG reader supports Adam7 interlaced images (much less pass the png test suite).

The blessed PNG reader supports adam7 deinterlacing, animation (APNG), all color types, bit depths 1-32, alpha, alpha palettes, and outputs scaled bitmaps (cellmaps) in blessed for efficient rendering to the screen buffer. It also uses some code from libcaca/libcucul to add density ASCII characters in order to give the image more detail in the terminal.

If a corrupt PNG or a non-PNG is passed in, blessed will display error text in the element.

.gif files are also supported via a javascript implementation (they are internally converted to bitmaps and fed to the PNG renderer). Any other image format is support only if the user has imagemagick (convert and identify) installed.

Options:
Properties:
Events:
Methods:

OverlayImage (from Box)

Display an image in the terminal (jpeg, png, gif) using w3mimgdisplay. Requires w3m to be installed. X11 required: works in xterm, urxvt, and possibly other terminals.

Options:
Properties:
Events:
Methods:

Video (from Box)

A box which spins up a pseudo terminal in order to render a video via mplayer -vo caca or mpv --vo caca. Requires mplayer or mpv to be installed with libcaca support.

Options:
Properties:
Events:
Methods:

Layout (from Element)

A layout which can position children automatically based on a renderer method (experimental - the mechanics of this element may be changed in the future!).

By default, the Layout element automatically positions children as if they were display: inline-block; in CSS.

Options:
Properties:
Events:
Methods:
Rendering a Layout for child elements
Notes

You must always give Layout a width and height. This is a chicken-and-egg problem: blessed cannot calculate the width and height dynamically before the children are positioned.

border and padding are already calculated into the coords object the renderer receives, so there is no need to account for it in your renderer.

Try to set position for children using el.position. el.position is the most primitive "to-be-rendered" way to set coordinates. Setting el.left directly has more dynamic behavior which may interfere with rendering.

Some definitions for coords (otherwise known as el.lpos):

Note again: the coords the renderer receives for the Layout already has border and padding subtracted, so you do not have to account for these. The children do not.

Example

Here is an example of how to provide a renderer. Note that this is also the default renderer if none is provided. This renderer will render each child as though they were display: inline-block; in CSS, as if there were a dynamically sized horizontal grid from left to right.

var layout = blessed.layout({
  parent: screen,
  top: 'center',
  left: 'center',
  width: '50%',
  height: '50%',
  border: 'line',
  style: {
    bg: 'red',
    border: {
      fg: 'blue'
    }
  },
  // NOTE: This is already the default renderer if none is provided!
  renderer: function(coords) {
    var self = this;

    // The coordinates of the layout element
    var width = coords.xl - coords.xi
      , height = coords.yl - coords.yi
      , xi = coords.xi
      , xl = coords.xl
      , yi = coords.yi
      , yl = coords.yl;

    // The current row offset in cells (which row are we on?)
    var rowOffset = 0;

    // The index of the first child in the row
    var rowIndex = 0;

    return function iterator(el, i) {
      // Make our children shrinkable. If they don't have a height, for
      // example, calculate it for them.
      el.shrink = true;

      // Find the previous rendered child's coordinates
      var last = self.getLastCoords(i);

      // If there is no previously rendered element, we are on the first child.
      if (!last) {
        el.position.left = 0;
        el.position.top = 0;
      } else {
        // Otherwise, figure out where to place this child. We'll start by
        // setting it's `left`/`x` coordinate to right after the previous
        // rendered element. This child will end up directly to the right of it.
        el.position.left = last.xl - xi;

        // If our child does not overlap the right side of the Layout, set it's
        // `top`/`y` to the current `rowOffset` (the coordinate for the current
        // row).
        if (el.position.left + el.width <= width) {
          el.position.top = rowOffset;
        } else {
          // Otherwise we need to start a new row and calculate a new
          // `rowOffset` and `rowIndex` (the index of the child on the current
          // row).
          rowOffset += self.children.slice(rowIndex, i).reduce(function(out, el) {
            if (!self.isRendered(el)) return out;
            out = Math.max(out, el.lpos.yl - el.lpos.yi);
            return out;
          }, 0);
          rowIndex = i;
          el.position.left = 0;
          el.position.top = rowOffset;
        }
      }

      // If our child overflows the Layout, do not render it!
      // Disable this feature for now.
      if (el.position.top + el.height > height) {
        // Returning false tells blessed to ignore this child.
        // return false;
      }
    };
  }
});

for (var i = 0; i < 10; i++) {
  blessed.box({
    parent: layout,
    width: i % 2 === 0 ? 10 : 20,
    height: i % 2 === 0 ? 5 : 10,
    border: 'line'
  });
}

Other

Helpers

All helpers reside on blessed.helpers or blessed.

Mechanics

Content & Tags

Every element can have text content via setContent. If tags: true was passed to the element's constructor, the content can contain tags. For example:

box.setContent('hello {red-fg}{green-bg}{bold}world{/bold}{/green-bg}{/red-fg}');

To make this more concise {/} cancels all character attributes.

box.setContent('hello {red-fg}{green-bg}{bold}world{/}');
Colors

Blessed tags support the basic 16 colors for colors, as well as up to 256 colors.

box.setContent('hello {red-fg}{green-bg}world{/}');

Tags can also use hex colors (which will be reduced to the most accurate terminal color):

box.setContent('hello {#ff0000-fg}{#00ff00-bg}world{/}');
Attributes

Blessed supports all terminal attributes, including bold, underline, blink, inverse, and invisible.

box.setContent('hello {bold}world{/bold}');
Alignment

Newlines and alignment are also possible in content.

box.setContent('hello\n'
  + '{right}world{/right}\n'
  + '{center}foo{/center}\n');
  + 'left{|}right');

This will produce a box that looks like:

| hello                 |
|                 world |
|          foo          |
| left            right |
Escaping

Escaping can either be done using blessed.escape()

box.setContent('here is an escaped tag: ' + blessed.escape('{bold}{/bold}'));

Or with the special {open} and {close} tags:

box.setContent('here is an escaped tag: {open}bold{close}{open}/bold{close}');

Either will produce:

here is an escaped tag: {bold}{/bold}
SGR Sequences

Content can also handle SGR escape codes. This means if you got output from a program, say git log for example, you can feed it directly to an element's content and the colors will be parsed appropriately.

This means that while {red-fg}foo{/red-fg} produces ^[[31mfoo^[[39m, you could just feed ^[[31mfoo^[[39m directly to the content.

Style

The style option controls most of the visual aspects of an element.

  style: {
    fg: 'blue',
    bg: 'black',
    bold: true,
    underline: false,
    blink: false,
    inverse: false,
    invisible: false,
    transparent: false,
    border: {
      fg: 'blue',
      bg: 'red'
    },
    scrollbar: {
      bg: 'blue'
    },
    focus: {
      bg: 'red'
    },
    hover: {
      bg: 'red'
    }
  }
Colors

Colors can be the names of any of the 16 basic terminal colors, along with hex values (e.g. #ff0000) for 256 color terminals. If 256 or 88 colors is not supported. Blessed with reduce the color to whatever is available.

Attributes

Blessed supports all terminal attributes, including bold, underline, blink, inverse, and invisible. Attributes are represented as bools in the style object.

Transparency

Blessed can set the opacity of an element to 50% using style.transparent = true;. While this seems like it normally shouldn't be possible in a terminal, blessed will use a color blending algorithm to blend the element of the foremost element with the background behind it. Obviously characters cannot be blended, but background colors can.

Shadow

Translucent shadows are also an option when it comes to styling an element. This option will create a 50% opacity 2-cell wide, 1-cell high shadow offset to the bottom-right.

shadow: true
Effects

Blessed supports hover and focus styles. (Hover is only useful is mouse input is enabled).

  style: {
    hover: {
      bg: 'red'
    },
    focus: {
      border: {
        fg: 'blue'
      }
    }
  }
Scrollbar

On scrollable elements, blessed will support style options for the scrollbar, such as:

style: {
  scrollbar: {
    bg: 'red',
    fg: 'blue'
  }
}

As a main option, scrollbar will either take a bool or an object:

scrollbar: {
  ch: ' '
}

Or:

scrollbar: true

Events

Events in Blessed work similar to the traditional node.js model, with one important difference: they have a concept of a tree and event bubbling.

Event Bubbling

Events can bubble in blessed. For example:

Receiving all click events for box (a normal event listener):

box.on('click', function(mouse) {
  box.setContent('You clicked ' + mouse.x + ', ' + mouse.y + '.');
  screen.render();
});

Receiving all click events for box, as well as all of its children:

box.on('element click', function(el, mouse) {
  box.setContent('You clicked '
    + el.type + ' at ' + mouse.x + ', ' + mouse.y + '.');
  screen.render();
  if (el === box) {
    return false; // Cancel propagation.
  }
});

el gets passed in as the first argument. It refers to the target element the event occurred on. Returning false will cancel propagation up the tree.

Positioning

Offsets may be a number, a percentage (e.g. 50%), or a keyword (e.g. center).

Dimensions may be a number, or a percentage (e.g. 50%).

Positions are treated almost exactly the same as they are in CSS/CSSOM when an element has the position: absolute CSS property.

When an element is created, it can be given coordinates in its constructor:

var box = blessed.box({
  left: 'center',
  top: 'center',
  bg: 'yellow',
  width: '50%',
  height: '50%'
});

This tells blessed to create a box, perfectly centered relative to its parent, 50% as wide and 50% as tall as its parent.

Percentages can also have offsets applied to them:

  ...
  height: '50%-1',
  left: '45%+1',
  ...

To access the calculated offsets, relative to the parent:

console.log(box.left);
console.log(box.top);

To access the calculated offsets, absolute (relative to the screen):

console.log(box.aleft);
console.log(box.atop);
Overlapping offsets and dimensions greater than parents'

This still needs to be tested a bit, but it should work.

Rendering

To actually render the screen buffer, you must call render.

box.setContent('Hello {#0fe1ab-fg}world{/}.');
screen.render();

Elements are rendered with the lower elements in the children array being painted first. In terms of the painter's algorithm, the lowest indicies in the array are the furthest away, just like in the DOM.

Artificial Cursors

Terminal cursors can be tricky. They all have different custom escape codes to alter. As an experimental alternative, blessed can draw a cursor for you, allowing you to have a custom cursor that you control.

var screen = blessed.screen({
  cursor: {
    artificial: true,
    shape: 'line',
    blink: true,
    color: null // null for default
  }
});

That's it. It's controlled the same way as the regular cursor.

To create a custom cursor:

var screen = blessed.screen({
  cursor: {
    artificial: true,
    shape: {
      bg: 'red',
      fg: 'white',
      bold: true,
      ch: '#'
    },
    blink: true
  }
});

Multiple Screens

Blessed supports the ability to create multiple screens. This may not seem useful at first, but if you're writing a program that serves terminal interfaces over http, telnet, or any other protocol, this can be very useful.

Server Side Usage

A simple telnet server might look like this (see examples/blessed-telnet.js for a full example):

var blessed = require('blessed');
var telnet = require('telnet2');

telnet({ tty: true }, function(client) {
  client.on('term', function(terminal) {
    screen.terminal = terminal;
    screen.render();
  });

  client.on('size', function(width, height) {
    client.columns = width;
    client.rows = height;
    client.emit('resize');
  });

  var screen = blessed.screen({
    smartCSR: true,
    input: client,
    output: client,
    terminal: 'xterm-256color',
    fullUnicode: true
  });

  client.on('close', function() {
    if (!screen.destroyed) {
      screen.destroy();
    }
  });

  screen.key(['C-c', 'q'], function(ch, key) {
    screen.destroy();
  });

  screen.on('destroy', function() {
    if (client.writable) {
      client.destroy();
    }
  });

  screen.data.main = blessed.box({
    parent: screen,
    left: 'center',
    top: 'center',
    width: '80%',
    height: '90%',
    border: 'line',
    content: 'Welcome to my server. Here is your own private session.'
  });

  screen.render();
}).listen(2300);

Once you've written something similar and started it, you can simply telnet into your blessed app:

$ telnet localhost 2300

Creating a netcat server would also work as long as you disable line buffering and terminal echo on the commandline via stty: $ stty -icanon -echo; ncat localhost 2300; stty icanon echo

Or by using netcat's -t option: $ ncat -t localhost 2300

Creating a streaming http 1.1 server than runs in the terminal is possible by curling it with special arguments: $ curl -sSNT. localhost:8080.

There are currently no examples of netcat/nc/ncat or http->curl servers yet.


The blessed.screen constructor can accept input, output, and term arguments to aid with this. The multiple screens will be managed internally by blessed. The programmer just has to keep track of the references, however, to avoid ambiguity, it's possible to explicitly dictate which screen a node is part of by using the screen option when creating an element.

The screen.destroy() method is also crucial: this will clean up all event listeners the screen has bound and make sure it stops listening on the event loop. Make absolutely certain to remember to clean up your screens once you're done with them.

A tricky part is making sure to include the ability for the client to send the TERM which is reset on the serverside, and the terminal size, which is should also be reset on the serverside. Both of these capabilities are demonstrated above.

For a working example of a blessed telnet server, see examples/blessed-telnet.js.

Notes

Windows Compatibility

Currently there is no mouse or resize event support on Windows.

Windows users will need to explicitly set term when creating a screen like so (NOTE: This is no longer necessary as of the latest versions of blessed. This is now handled automatically):

var screen = blessed.screen({ terminal: 'windows-ansi' });

Low-level Usage

This will actually parse the xterm terminfo and compile every string capability to a javascript function:

var blessed = require('blessed');

var tput = blessed.tput({
  terminal: 'xterm-256color',
  extended: true
});

process.stdout.write(tput.setaf(4) + 'Hello' + tput.sgr0() + '\n');

To play around with it on the command line, it works just like tput:

$ tput.js setaf 2
$ tput.js sgr0
$ echo "$(tput.js setaf 2)Hello World$(tput.js sgr0)"

The main functionality is exposed in the main blessed module:

var blessed = require('blessed')
  , program = blessed.program();

program.key('q', function(ch, key) {
  program.clear();
  program.disableMouse();
  program.showCursor();
  program.normalBuffer();
  process.exit(0);
});

program.on('mouse', function(data) {
  if (data.action === 'mousemove') {
    program.move(data.x, data.y);
    program.bg('red');
    program.write('x');
    program.bg('!red');
  }
});

program.alternateBuffer();
program.enableMouse();
program.hideCursor();
program.clear();

program.move(1, 1);
program.bg('black');
program.write('Hello world', 'blue fg');
program.setx((program.cols / 2 | 0) - 4);
program.down(5);
program.write('Hi again!');
program.bg('!black');
program.feed();

Testing

Most tests contained in the test/ directory are interactive. It's up to the programmer to determine whether the test is properly displayed. In the future it might be better to do something similar to vttest.

Examples

Examples can be found in examples/.

FAQ

  1. Why doesn't the Linux console render lines correctly on Ubuntu?
    • You need to install the ncurses-base package and the ncurses-term package. (#98)
  2. Why do vertical lines look chopped up in iTerm2?
    • All ACS vertical lines look this way in iTerm2 with the default font.
  3. Why can't I use my mouse in Terminal.app?
    • Terminal.app does not support mouse events.
  4. Why doesn't the OverlayImage element appear in my terminal?
    • The OverlayImage element uses w3m to display images. This generally only works on X11+xterm/urxvt, but it may work on other unix terminals.
  5. Why can't my mouse clicks register beyond 255 cells?
    • Older versions of VTE do not support any modern mouse protocol. On top of that, the old X10 protocol it does implement is bugged. Through several workarounds we've managed to get the cell limit from 127 to 255. If you're not happy with this, you may want to look into using xterm or urxvt, or a terminal which uses a modern VTE, like gnome-terminal.
  6. Is blessed efficient?
    • Yes. Blessed implements CSR and uses the painter's algorithm to render the screen. It maintains two screen buffers so it only needs to render what has changed on the terminal screen.
  7. Will blessed work with all terminals?
    • Yes. Blessed has a terminfo/termcap parser and compiler that was written from scratch. It should work with every terminal as long as a terminfo file is provided. If you notice any compatibility issues in your termial, do not hesitate to post an issue.
  8. What is "curses" and "ncurses"?
    • "curses" was an old library written in the early days of unix which allowed a programmer to easily manipulate the cursor in order to render the screen. "ncurses" is a free reimplementation of curses. It improved upon it quite a bit by focusing more on terminal compatibility and is now the standard library for implementing terminal programs. Blessed uses neither of these, and instead handles terminal compatibility itself.
  9. What is the difference between blessed and blessed-contrib?
    • blessed is a major piece of code which reimplements curses from the ground up. A UI API is then layered on top of this. blessed-contrib is a popular library built on top of blessed which makes clever use of modules to implement useful widgets like graphs, ascii art, and so on.
  10. Are there blessed-like solutions for non-javascript platforms?

Contribution and License Agreement

If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. </legalese>

License

Copyright (c) 2013-2015, Christopher Jeffrey. (MIT License)

See LICENSE for more info.