Skip to content

Commit

Permalink
feat(ansi-escape): add ansi escape module (#1)
Browse files Browse the repository at this point in the history
* feat(ansi-escape): add ansi escape module

* update readme
  • Loading branch information
c4spar committed Apr 18, 2020
1 parent 83c767c commit 0ac92c2
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -8,13 +8,15 @@ A collection of modules for creating interactive command line tools.

**Included modules:**

* **[ansi-escape](packages/ansi-escape/):** Show, hide and move cli cursor, erase output and scroll window.

* **[command](packages/command/):** Create flexible command line interfaces with type checking, auto generated help and out of the box support for shell completions (inspired by [node.js's](http://nodejs.org) [commander.js](https://github.com/tj/commander.js/blob/master/Readme.md)).

* **[flags](packages/flags/):** Parse command line arguments.

* **[keycode](packages/keycode/):** Parse ANSI key codes.

* **[table](packages/table/):** Render data in table structure with correct indentation and support for multi-line rows.
* **[table](packages/table/):** Create cli table's with border, padding, nested table's, etc...

**Todo's:**

Expand Down
1 change: 1 addition & 0 deletions ansi-escape.ts
@@ -0,0 +1 @@
export * from './packages/ansi-escape/mod.ts';
34 changes: 34 additions & 0 deletions packages/ansi-escape/README.md
@@ -0,0 +1,34 @@
# Cliffy - ANSI Escape

ANSI escape module for handling cli cursor, erase output and scroll window.

![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/c4spar/deno-cliffy?logo=github) ![GitHub Release Date](https://img.shields.io/github/release-date/c4spar/deno-cliffy?logo=github)

![Build Status](https://github.com/c4spar/deno-cliffy/workflows/ci/badge.svg?branch=master) ![Deno version](https://img.shields.io/badge/deno-v0.41.0|v0.40.0|v0.39.0-green?logo=deno) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/c4spar/deno-cliffy?logo=github) ![GitHub issues](https://img.shields.io/github/issues/c4spar/deno-cliffy?logo=github) ![GitHub licence](https://img.shields.io/github/license/c4spar/deno-cliffy?logo=github)

## Usage

```typescript
#!/usr/bin/env -S deno

import { AnsiEscape } from 'https://deno.land/x/cliffy/ansi-escape.ts';

AnsiEscape.from( Deno.stdout )
// Hide cursor:
.cursorHide()
// Show cursor:
.cursorShow()
// Erase current line:
.eraseLine()
// Erase three line's up:
.eraseLines( 3 )
// Scroll two line's up:
.scrollUp( 2 )
// Scroll one line down:
.scrollDown()
// ...
```

## License

[MIT](LICENSE)
178 changes: 178 additions & 0 deletions packages/ansi-escape/lib/ansi-escape.ts
@@ -0,0 +1,178 @@
import { encode } from 'https://deno.land/std@v0.41.0/encoding/utf8.ts';
import { cursor, erase, image, ImageOptions, link, scroll } from './csi.ts';

export class AnsiEscape {

/** Create instance from file. */
public static from( file: Deno.File ): AnsiEscape {
return new this( file );
}

protected constructor( protected file: Deno.File ) {}

/** Write to file. */
public write( code: string ): this {
this.file.writeSync( encode( code ) );
return this;
}

/**
* Cursor
*/

/** Move cursor to x, y, counting from the top left corner. */
public cursorTo( x: number, y?: number ) {
this.write( cursor.to( x, y ) );
return this;
}

/** Move cursor by offset. */
public cursorMove( x: number, y: number ) {
this.write( cursor.move( x, y ) );
return this;
}

/** Move cursor up by n lines. */
public cursorUp( count: number = 1 ) {
this.write( cursor.up( count ) );
return this;
}

/** Move cursor down by n lines. */
public cursorDown( count: number = 1 ) {
this.write( cursor.down( count ) );
return this;
}

/** Move cursor right by n lines. */
public cursorForward( count: number = 1 ) {
this.write( cursor.forward( count ) );
return this;
}

/** Move cursor left by n lines. */
public cursorBackward( count: number = 1 ) {
this.write( cursor.backward( count ) );
return this;
}

/** Move cursor to the beginning of the line n lines down. */
public cursorNextLine( count: number = 1 ) {
this.write( cursor.nextLine( count ) );
return this;
}

/** Move cursor to the beginning of the line n lines up. */
public cursorPrevLine( count: number = 1 ) {
this.write( cursor.prevLine( count ) );
return this;
}

/** Move cursor to first column of current row. */
public cursorLeft() {
this.write( cursor.left );
return this;
}

/** Hide cursor. */
public cursorHide() {
this.write( cursor.hide );
return this;
}

/** Show cursor. */
public cursorShow() {
this.write( cursor.show );
return this;
}

/** Save cursor. */
public cursorSave() {
this.write( cursor.save );
return this;
}

/** Restore cursor. */
public cursorRestore() {
this.write( cursor.restore );
return this;
}

/**
* Scroll
*/

/** Scroll window up by n lines. */
public scrollUp( count: number = 1 ) {
this.write( scroll.up( count ) );
return this;
}

/** Scroll window down by n lines. */
public scrollDown( count: number = 1 ) {
this.write( scroll.down( count ) );
return this;
}

/**
* Erase
*/

/** Clear screen. */
public eraseScreen() {
this.write( erase.screen );
return this;
}

/** Clear screen up. */
public eraseUp( count: number = 1 ) {
this.write( erase.up( count ) );
return this;
}

/** Clear screen down. */
public eraseDown( count: number = 1 ) {
this.write( erase.down( count ) );
return this;
}

/** Clear current line. */
public eraseLine() {
this.write( erase.line );
return this;
}

/** Clear to line end. */
public eraseLineEnd() {
this.write( erase.lineEnd );
return this;
}

/** Clear to line start. */
public eraseLineStart() {
this.write( erase.lineStart );
return this;
}

/** Clear n line's up. */
public eraseLines( count: number ) {
this.write( erase.lines( count ) );
return this;
}

/**
* Style
*/

/** Render link. */
public link( text: string, url: string ) {
this.write( link( text, url ) );
return this;
}

/** Render image. */
public image( buffer: Uint8Array, options?: ImageOptions ) {
this.write( image( buffer, options ) );
return this;
}
}
130 changes: 130 additions & 0 deletions packages/ansi-escape/lib/csi.ts
@@ -0,0 +1,130 @@
import * as base64 from 'https://deno.land/x/base64@v0.2.0/mod.ts';

export const ESC = '\x1B';
export const CSI = `${ ESC }[`;
export const OSC = `${ ESC }]`;
export const BEL = '\u0007';
const SEP = ';';

export const cursor = {
/** Move cursor to x, y, counting from the top left corner. */
to( x: number, y?: number ) {
if ( typeof y !== 'number' ) {
return `${ CSI }${ x }G`;
}
return `${ CSI }${ y };${ x }H`;
},
/** Move cursor by offset. */
move( x: number, y: number ) {
let ret = '';

if ( x < 0 ) {
ret += `${ CSI }${ -x }D`;
} else if ( x > 0 ) {
ret += `${ CSI }${ x }C`;
}

if ( y < 0 ) {
ret += `${ CSI }${ -y }A`;
} else if ( y > 0 ) {
ret += `${ CSI }${ y }B`;
}

return ret;
},
/** Move cursor up by n lines. */
up: ( count: number = 1 ) => `${ CSI }${ count }A`,
/** Move cursor down by n lines. */
down: ( count: number = 1 ) => `${ CSI }${ count }B`,
/** Move cursor right by n lines. */
forward: ( count: number = 1 ) => `${ CSI }${ count }C`,
/** Move cursor left by n lines. */
backward: ( count: number = 1 ) => `${ CSI }${ count }D`,
/** Move cursor to the beginning of the line n lines down. */
nextLine: ( count: number = 1 ) => `${ CSI }E`.repeat( count ),
/** Move cursor to the beginning of the line n lines up. */
prevLine: ( count: number = 1 ) => `${ CSI }F`.repeat( count ),
/** Move cursor to first column of current row. */
left: `${ CSI }G`,
/** Hide cursor. */
hide: `${ CSI }?25l`,
/** Show cursor. */
show: `${ CSI }?25h`,
/** Save cursor. */
save: `${ ESC }7`,
/** Restore cursor. */
restore: `${ ESC }8`
};

export const scroll = {
/** Scroll window up by n lines. */
up: ( count: number = 1 ) => `${ CSI }S`.repeat( count ),
/** Scroll window down by n lines. */
down: ( count: number = 1 ) => `${ CSI }T`.repeat( count )
};

export const erase = {
/** Clear screen. */
screen: `${ CSI }2J`,
/** Clear screen up. */
up: ( count: number = 1 ) => `${ CSI }1J`.repeat( count ),
/** Clear screen down. */
down: ( count: number = 1 ) => `${ CSI }0J`.repeat( count ),
/** Clear current line. */
line: `${ CSI }2K`,
/** Clear to line end. */
lineEnd: `${ CSI }0K`,
/** Clear to line start. */
lineStart: `${ CSI }1K`,
/** Clear n line's up. */
lines( count: number ) {
let clear = '';
for ( let i = 0; i < count; i++ ) {
clear += this.line + ( i < count - 1 ? cursor.up() : '' );
}
clear += cursor.left;
return clear;
}
};

/** Render link. */
export const link = ( text: string, url: string ) => [
OSC,
'8',
SEP,
SEP,
url,
BEL,
text,
OSC,
'8',
SEP,
SEP,
BEL
].join( '' );

export interface ImageOptions {
width?: number,
height?: number,
preserveAspectRatio?: boolean
}

/** Render image. */
export const image = ( buffer: Uint8Array, options?: ImageOptions ) => {

let ret = `${ OSC }1337;File=inline=1`;

if ( options?.width ) {
ret += `;width=${ options.width }`;
}

if ( options?.height ) {
ret += `;height=${ options.height }`;
}

if ( options?.preserveAspectRatio === false ) {
ret += ';preserveAspectRatio=0';
}

return ret + ':' + base64.fromUint8Array( buffer ) + BEL;
};
2 changes: 2 additions & 0 deletions packages/ansi-escape/mod.ts
@@ -0,0 +1,2 @@
export * from './lib/csi.ts';
export * from './lib/ansi-escape.ts';

0 comments on commit 0ac92c2

Please sign in to comment.