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/'
|
2015-04-19 05:14:51 +00:00
|
|
|
// $ 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
|
|
|
|
}
|
2015-04-19 05:14:51 +00:00
|
|
|
},
|
|
|
|
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 05:14:51 +00:00
|
|
|
});
|
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'
|
2015-04-19 05:14:51 +00:00
|
|
|
},
|
|
|
|
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));
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
list.on('select', function(el, selected) {
|
2015-04-19 05:14:51 +00:00
|
|
|
if (list._.rendering) return;
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
var name = el.getText();
|
|
|
|
var url = map[name];
|
|
|
|
|
2015-04-19 03:27:53 +00:00
|
|
|
status.setContent(url);
|
2015-04-19 05:14:51 +00:00
|
|
|
|
|
|
|
list._.rendering = true;
|
|
|
|
loader.load('Loading...');
|
|
|
|
|
2015-04-20 21:09:01 +00:00
|
|
|
request({
|
|
|
|
uri: url,
|
|
|
|
encoding: null
|
|
|
|
}, function(err, res, body) {
|
2015-04-19 05:14:51 +00:00
|
|
|
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') {
|
2015-04-21 11:19:50 +00:00
|
|
|
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-19 05:14:51 +00:00
|
|
|
|
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-19 05:14:51 +00:00
|
|
|
|
2015-04-20 21:09:01 +00:00
|
|
|
screen.render();
|
2015-04-21 11:19:50 +00:00
|
|
|
|
|
|
|
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();
|
2015-04-21 11:19:50 +00:00
|
|
|
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:19:50 +00:00
|
|
|
|
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;
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
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;
|
2015-04-21 11:19:50 +00:00
|
|
|
|
|
|
|
out = '';
|
|
|
|
attr = this.dattr;
|
|
|
|
|
|
|
|
for (x = xi; x < xl; x++) {
|
2015-04-21 11:43:02 +00:00
|
|
|
if (!line[x]) break;
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
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) {
|
2015-04-21 11:19:50 +00:00
|
|
|
ch = ' ';
|
|
|
|
} else {
|
2015-04-21 11:43:02 +00:00
|
|
|
x++;
|
2015-04-21 11:19:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-04-21 11:19:50 +00:00
|
|
|
return main;
|
|
|
|
};
|