npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

keyscan

v1.7.0

Published

Simpler keyboard scanning for Node

Downloads

30

Readme

keyscan

Simple keyboard scanning for node

require('keyscan').make_scanner( (ch) => console.log('Caught ' + ch.parsed) );

Aren't there a thousand of these already?

Yep. And none of them are what I wanted.

  • One-liner node keypress handling
  • No need to configure beyond saying "this is the handler"
  • Suppress echo by default
  • Configurable (and removable) kill key(s)
  • Special keys (eg arrow keys) interpreted down to common sense strings
  • readline various-field defect repaired

Basic usage

Provide a function to keyscan. That function will be called whenever a non-control keyboard input is detected, with an object describing the input.

The object passed back to your handler function is a single depth flat object with six fields - the five mimicing what is exposed by the raw mode readline interface, plus a new field named parsed.

Generally you should use parsed, which is just (.name || .sequence). In most cases .parsed will contain .name, but there are characters (such as @) which have no .name. Checking for that and deferring to .sequence is a hassle, so, the .parsed property now reliably contains that behavior.

It's worth noting that .name frequently translates multiple keypresses to single keypresses (which is its purpose.) As a result, you may get keys represented in .parsed as strings like 'pagedown' and 'backspace'.

Example objects

This is the up arrow:

{
  "sequence": "\u001b[A",
  "name": "up",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "code": "[A",
  "parsed": "up"
}

This is page down:

{
  "sequence": "\u001b[6~",
  "name": "pagedown",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "code": "[6~",
  "parsed": "pagedown"
}

This is capital A produced with shift (note that only .sequence retains case)

{
  "sequence": "A",
  "name": "a",
  "ctrl": false,
  "meta": false,
  "shift": true,
  "parsed": "a"
}

This is capital A produced with caps lock (note that .shift is false here, unlike above)

{
  "sequence": "A",
  "name": "a",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "parsed": "a"
}

This is ctrl+z

{
  "sequence": "\u001a",
  "name": "z",
  "ctrl": true,
  "meta": false,
  "shift": false,
  "parsed": "z"
}

Because of the integrated approach to control keys, you will not get events when people hit, say, shift or caps lock on their own. This is what most applications want and need, but it's not right for many action games, which may want to use control keys as weapon buttons.

On Windows and Linux platforms, meta is called alt. On Macintoshes, meta is called option.

Code examples are often more readable.

ES5 Example

For the most part, you'll want the .parsed property, which has the lower case character for letters, the symbol for symbols, and a string like 'down' for arrow keys.

If you're in an es5 environment:

console.log('Type ctrl+c to exit.\n-------------------\n');

var echo_ch = function(ks) { console.log('Caught ' + JSON.stringify(ks.parsed)); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught a
Caught b
Caught c
Caught 1
Caught 2
Caught %
Caught ^
Caught up
Caught right
Caught delete
Caught pagedown
Caught end
Caught home

Premades

There are also some convenience pre-made filters, such as

  • scanner.yn(opts) - for yes/no - pre-configured to filter for ['y','n']
    • scanner.ync(opts) - for yes/no/cancel - pre-configured to filter for ['y','n','c']
  • scanner.abc(opts) - for abc multiple choice - pre-configured to filter for ['a','b','c']
    • scanner.abcd(opts) - for abcd multiple choice - pre-configured to filter for ['a','b','c','d']
  • scanner.d09(opts) - for star ratings - pre-configured to filter for digits ['0','1','2','3','4','5','6','7','8','9']
    • scanner.d14(opts) - for star ratings - like .d09() but only accepts 1-4
    • scanner.d15(opts) - for star ratings - like .d14() but 1-5
    • scanner.d19(opts) - for 1-9 ratings - like .d09() but excludes zeroes
  • scanner.d09c(opts) - for digits - also accepts 'c', for cancellation
    • scanner.d14c(opts)
    • scanner.d15c(opts)
    • scanner.d19c(opts)

ES5 Full-Show Example

If you'd like to see the full keypress data instead, letting you see control modes and the raw character sequence, try this instead:

console.log('Type ctrl+c to exit.\n-------------------\n');

var echo_ch = function(ch) { console.log('Caught ' + JSON.stringify(ch)); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught {"sequence":"a","name":"a","ctrl":false,"meta":false,"shift":false,"parsed":"a"}
Caught {"sequence":"s","name":"s","ctrl":false,"meta":false,"shift":false,"parsed":"s"}
Caught {"sequence":"d","name":"d","ctrl":false,"meta":false,"shift":false,"parsed":"d"}
Caught {"sequence":"!","ctrl":false,"meta":false,"shift":false,"parsed":"!"}
Caught {"sequence":"@","ctrl":false,"meta":false,"shift":false,"parsed":"@"}
Caught {"sequence":"\u001b[A","name":"up","ctrl":false,"meta":false,"shift":false,"code":"[A","parsed":"up"}
Caught {"sequence":"\u001b[D","name":"left","ctrl":false,"meta":false,"shift":false,"code":"[D","parsed":"left"}
Caught {"sequence":"\u001b[C","name":"right","ctrl":false,"meta":false,"shift":false,"code":"[C","parsed":"right"}
Caught {"sequence":"\u001b[B","name":"down","ctrl":false,"meta":false,"shift":false,"code":"[B","parsed":"down"}
Caught {"sequence":"\u001b[6~","name":"pagedown","ctrl":false,"meta":false,"shift":false,"code":"[6~","parsed":"pagedown"}
Caught {"sequence":"\u001b[5~","name":"pageup","ctrl":false,"meta":false,"shift":false,"code":"[5~","parsed":"pageup"}
Caught {"sequence":"\u0003","name":"c","ctrl":true,"meta":false,"shift":false,"parsed":"c"}

ES6 Module Example

In an es6 environment with module support (ie, not node yet,)

console.log('Type ctrl+c to exit.\n-------------------\n');

import { make_scanner } from 'keyscan';

const keyscan = make_scanner({ out: function(ch) { console.log('Caught ' + JSON.stringify(ch)); } });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught {"sequence":"a","name":"a","ctrl":false,"meta":false,"shift":false,"parsed":"a"}

Other features

The key scanner offers other features as well.

Manually releasing the key scanner

If you want to release the scanning process without requiring the user to abort with ctrl+c, just .release the scanner.

console.log('\nType three things, then this will exit.\n---------------------------------------\n');

var count   = 0,

    keyscan = require('../dist/keyscan.js'),
    scanner,

    echo_3x = function(ch) {
        console.log('Caught ' + JSON.stringify(ch.parsed));
        ++count;
        if (count >= 3) {
            console.log('Three items reached.  Releasing.');
            scanner.release();
        }
    },

    scanner = keyscan.make_scanner({ out: echo_3x });

Result

You'll see something like this:

$ node demo/manual_release.js

Type three things, then this will exit.
---------------------------------------

Caught "1"
Caught "2"
Caught "3"
Three items reached.  Releasing.

Replacing the kill keys with isAbort

If you want to keep the termination character behavior, but want it to be something other than ctrl+c (or multiple things,) write a function, and pass it in as .isAbort:

console.log('\nType ctrl+x to exit.\n-------------------\n');

var echo_ch = function(ch) { console.log('Caught ' + JSON.stringify(ch.parsed)); },
    use_c_x = function(ch) { return (ch && (ch.ctrl) && (ch.name == 'x')); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch, isAbort: use_c_x });

Todo:

  • read once only
  • unicode symbolic name representations
  • prompt (? may not make sense, deciding)
  • echo enable (wants unicode)
  • map keys to outputs (eg 's' echoes 'save')
  • make yn and ync show labels aftwerwards
  • .sync (inherently once)