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

382 lines
7.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') {
2015-04-21 11:56:07 +00:00
// var sgr = blessed.element.prototype.screenshot.call(art);
var sgr = art.screenshot();
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);
}
}
2015-04-21 11:56:07 +00:00
blessed.Element.prototype.screenshot = function(xi, xl, yi, yl) {
2015-04-21 11:58:41 +00:00
xi = this.lpos.xi + this.ileft + (xi || 0);
xl = this.lpos.xl - this.iright + (xl || 0);
yi = this.lpos.yi + this.itop + (yi || 0);
yl = this.lpos.yl - this.ibottom + (yl || 0);
2015-04-21 11:56:07 +00:00
return this.screen.screenshot(xi, xl, yi, yl);
};
blessed.Terminal.prototype.screenshot = function(xi, xl, yi, yl) {
2015-04-21 11:58:41 +00:00
xi = 0 + (xi || 0);
xl = this.term.lines[0].length + (xl || 0);
yi = 0 + (yi || 0);
yl = this.term.lines.length + (yl || 0);
2015-04-21 11:56:07 +00:00
return this.screen.screenshot(xi, xl, yi, yl, this.term);
};
blessed.Screen.prototype.screenshot = function(xi, xl, yi, yl, term) {
if (xi == null) xi = 0;
if (xl == null) xl = this.lpos.xl;
if (yi == null) yi = 0;
if (yl == null) yl = this.lpos.yl;
var x
, y
, line
, out
, ch
, data
, attr
, cwid
, point;
2015-04-21 11:43:02 +00:00
var sdattr = this.dattr;
if (term) {
this.dattr = term.defAttr;
// default foreground = 257
if (((this.dattr >> 9) & 0x1ff) === 257) {
this.dattr &= ~(0x1ff << 9);
this.dattr |= ((sdattr >> 9) & 0x1ff) << 9;
}
// default background = 256
if ((this.dattr & 0x1ff) === 256) {
this.dattr &= ~0x1ff;
this.dattr |= sdattr & 0x1ff;
}
}
var main = '';
var acs;
for (y = yi; y < yl; y++) {
2015-04-21 11:43:02 +00:00
line = term
? term.lines[y]
: this.lines[y];
if (!line) break;
out = '';
attr = this.dattr;
for (x = xi; x < xl; x++) {
2015-04-21 11:43:02 +00:00
if (!line[x]) break;
data = line[x][0];
ch = line[x][1];
2015-04-21 11:43:02 +00:00
if (term) {
// default foreground = 257
if (((data >> 9) & 0x1ff) === 257) {
data &= ~(0x1ff << 9);
data |= ((sdattr >> 9) & 0x1ff) << 9;
}
// default background = 256
if ((data & 0x1ff) === 256) {
data &= ~0x1ff;
data |= sdattr & 0x1ff;
}
}
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) {
2015-04-21 11:43:02 +00:00
if (x === xl - 1) {
ch = ' ';
} else {
2015-04-21 11:43:02 +00:00
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';
2015-04-21 11:43:02 +00:00
if (term) {
this.dattr = sdattr;
}
return main;
};