neo-blessed/example/ansi-viewer/index.js

343 lines
6.8 KiB
JavaScript
Raw Normal View History

2015-04-19 03:27:53 +00:00
/**
2015-04-19 06:58:41 +00:00
* ansi-viewer
2015-04-19 03:27:53 +00:00
* ANSI art viewer for node.
* Copyright (c) 2015, Christopher Jeffrey and contributors (MIT License).
* https://github.com/chjj/blessed
*/
2015-04-19 19:09:05 +00:00
var blessed = require('blessed')
2015-04-19 03:27:53 +00:00
, request = require('request')
, fs = require('fs')
2015-04-20 21:09:01 +00:00
, cp = require('child_process')
, singlebyte = require('./singlebyte');
2015-04-19 03:27:53 +00:00
// $ wget -r -o log --tries=10 'http://artscene.textfiles.com/ansi/'
// $ grep 'http.*\.ans$' log | awk '{ print $3 }' > ansi-art.list
2015-04-19 03:27:53 +00:00
var urls = fs.readFileSync(__dirname + '/ansi-art.list', 'utf8').trim().split('\n');
var map = urls.reduce(function(map, url) {
map[/([^.\/]+\/[^.\/]+)\.ans$/.exec(url)[1]] = url;
return map;
}, {});
2015-04-19 06:58:41 +00:00
var max = Object.keys(map).reduce(function(out, text) {
return Math.max(out, text.length);
}, 0) + 6;
2015-04-20 21:09:01 +00:00
var screen = blessed.screen({
2015-04-19 03:27:53 +00:00
smartCSR: true,
dockBorders: true
});
var art = blessed.terminal({
parent: screen,
left: 0,
top: 0,
2015-04-21 00:40:17 +00:00
height: 60,
// some are 78/80, some are 80/82
width: 82,
border: 'line',
tags: true,
label: ' {bold}{cyan-fg}ANSI Art{/cyan-fg}{/bold} (Drag Me) ',
handler: function() {},
draggable: true
2015-04-19 03:27:53 +00:00
});
var list = blessed.list({
parent: screen,
2015-04-21 00:40:17 +00:00
label: ' {bold}{cyan-fg}Art List{/cyan-fg}{/bold} (Drag Me) ',
2015-04-19 03:27:53 +00:00
tags: true,
draggable: true,
top: 0,
right: 0,
2015-04-19 06:58:41 +00:00
width: max,
height: '50%',
2015-04-19 03:27:53 +00:00
keys: true,
vi: true,
mouse: true,
border: 'line',
scrollbar: {
ch: ' ',
track: {
bg: 'cyan'
},
style: {
inverse: true
}
},
style: {
item: {
hover: {
bg: 'blue'
}
},
selected: {
bg: 'blue',
bold: true
}
},
search: function(callback) {
prompt.input('Search:', '', function(err, value) {
if (err) return;
2015-04-19 05:25:13 +00:00
return callback(null, value);
});
2015-04-19 03:27:53 +00:00
}
});
var status = blessed.box({
parent: screen,
bottom: 0,
right: 0,
height: 1,
width: 'shrink',
style: {
bg: 'blue'
},
content: 'Select your piece of ANSI art (`/` to search).'
});
var loader = blessed.loading({
parent: screen,
top: 'center',
left: 'center',
height: 5,
align: 'center',
width: '50%',
tags: true,
hidden: true,
border: 'line'
});
var msg = blessed.message({
parent: screen,
top: 'center',
left: 'center',
height: 'shrink',
width: '50%',
align: 'center',
tags: true,
hidden: true,
border: 'line'
});
var prompt = blessed.prompt({
parent: screen,
top: 'center',
left: 'center',
height: 'shrink',
width: 'shrink',
keys: true,
vi: true,
mouse: true,
tags: true,
border: 'line',
hidden: true
2015-04-19 03:27:53 +00:00
});
list.setItems(Object.keys(map));
list.on('select', function(el, selected) {
if (list._.rendering) return;
var name = el.getText();
var url = map[name];
2015-04-19 03:27:53 +00:00
status.setContent(url);
list._.rendering = true;
loader.load('Loading...');
2015-04-20 21:09:01 +00:00
request({
uri: url,
encoding: null
}, function(err, res, body) {
list._.rendering = false;
loader.stop();
if (err) {
return msg.error(err.message);
}
if (!body) {
return msg.error('No body.');
}
2015-04-20 21:09:01 +00:00
return cp437ToUtf8(body, function(err, body) {
if (err) {
return msg.error(err.message);
}
2015-04-20 22:04:39 +00:00
if (process.argv[2] === '--debug') {
var filename = name.replace(/\//g, '.') + '.ans';
fs.writeFileSync(__dirname + '/' + filename, body);
2015-04-20 22:04:39 +00:00
}
// Remove text:
body = body.replace('Downloaded From P-80 International Information Systems 304-744-2253', '');
2015-04-20 21:09:01 +00:00
// Remove MCI codes:
2015-04-20 22:04:39 +00:00
body = body.replace(/%[A-Z0-9]{2}/g, '');
2015-04-21 00:40:17 +00:00
// ^A (SOH) seems to need to produce CRLF in some cases??
// body = body.replace(/\x01/g, '\r\n');
2015-04-20 21:09:01 +00:00
// Reset and write the art:
art.term.reset();
art.term.write(body);
art.term.cursorHidden = true;
2015-04-20 21:09:01 +00:00
screen.render();
if (process.argv[2] === '--debug' || process.argv[2] === '--save') {
// XXX Could just save from terminal, that way the image doesn't get
// cut off. Add a `lines` param and convert special fg and bg values
// used in term.js.
var sgr = screen.sgrRegion(
art.lpos.xi + art.ileft,
art.lpos.xl - art.iright,
art.lpos.yi + art.itop,
art.lpos.yl - art.ibottom);
fs.writeFileSync(__dirname + '/' + filename + '.sgr', sgr);
}
2015-04-20 21:09:01 +00:00
});
2015-04-19 03:27:53 +00:00
});
});
list.focus();
screen.key('q', function() {
return process.exit(0);
});
screen.key('h', function() {
list.toggle();
if (list.visible) list.focus();
});
screen.render();
2015-04-20 21:09:01 +00:00
/**
* Helpers
*/
// https://github.com/chjj/blessed/issues/127
// https://github.com/Mithgol/node-singlebyte
function cp437ToUtf8(buf, callback) {
try {
return callback(null, singlebyte.bufToStr(buf, 'cp437'));
} catch (e) {
return callback(e);
}
}
screen.__proto__.sgrRegion = function(xi, xl, yi, yl) {
var x
, y
, line
, out
, ch
, data
, attr
, cwid
, point;
var main = '';
var acs;
var angles = {
'\u2518': true, // '┘'
'\u2510': true, // '┐'
'\u250c': true, // '┌'
'\u2514': true, // '└'
'\u253c': true, // '┼'
'\u251c': true, // '├'
'\u2524': true, // '┤'
'\u2534': true, // '┴'
'\u252c': true, // '┬'
'\u2502': true, // '│'
'\u2500': true // '─'
};
for (y = yi; y < yl; y++) {
line = this.lines[y];
out = '';
attr = this.dattr;
for (x = xi; x < xl; x++) {
data = line[x][0];
ch = line[x][1];
if (data !== attr) {
if (attr !== this.dattr) {
out += '\x1b[m';
}
if (data !== this.dattr) {
out += screen.codeAttr(data);
}
}
if (this.fullUnicode) {
point = blessed.unicode.codePointAt(line[x][1], 0);
if (point <= 0x00ffff) {
cwid = blessed.unicode.charWidth(point);
if (cwid === 2) {
if (x === line.length - 1 || angles[line[x + 1][1]]) {
ch = ' ';
} else {
++x;
}
}
}
}
/*
if (this.tput.strings.enter_alt_charset_mode
&& !this.tput.brokenACS && (this.tput.acscr[ch] || acs)) {
if (this.tput.acscr[ch]) {
if (acs) {
ch = this.tput.acscr[ch];
} else {
ch = this.tput.smacs()
+ this.tput.acscr[ch];
acs = true;
}
} else if (acs) {
ch = this.tput.rmacs() + ch;
acs = false;
}
} else {
if (!this.tput.unicode && this.tput.numbers.U8 !== 1 && ch > '~') {
ch = this.tput.utoa[ch] || '?';
}
}
*/
out += ch;
attr = data;
}
if (attr !== this.dattr) {
out += '\x1b[m';
}
if (out) {
main += (y > 0 ? '\n' : '') + out;
}
}
/*
if (acs) {
main += this.tput.rmacs();
acs = false;
}
*/
main = main.replace(/(?:\s*\x1b\[40m\s*\x1b\[m\s*)*$/, '') + '\n';
return main;
};