Display ANSI in file area desc view

This commit is contained in:
Bryan Ashby 2017-08-06 11:31:21 -06:00
parent 9cb31367fd
commit 0a079ee4d8
2 changed files with 81 additions and 115 deletions

View File

@ -21,7 +21,7 @@ exports.renderStringLength = renderStringLength;
exports.formatByteSizeAbbr = formatByteSizeAbbr;
exports.formatByteSize = formatByteSize;
exports.cleanControlCodes = cleanControlCodes;
exports.createCleanAnsi = createCleanAnsi;
exports.prepAnsi = prepAnsi;
// :TODO: create Unicode verison of this
const VOWELS = [ 'a', 'e', 'i', 'o', 'u' ];
@ -373,48 +373,35 @@ function cleanControlCodes(input, options) {
return cleaned;
}
function createCleanAnsi(input, options, cb) {
function prepAnsi(input, options, cb) {
if(!input) {
return cb('');
return cb(null, '');
}
options.width = options.width || 80;
options.height = options.height || 25;
const canvas = new Array(options.height);
for(let i = 0; i < options.height; ++i) {
canvas[i] = new Array(options.width);
for(let j = 0; j < options.width; ++j) {
canvas[i][j] = {};
}
}
options.termWidth = options.termWidth || 80;
options.termHeight = options.termHeight || 25;
const parserOpts = {
termHeight : options.height,
termWidth : options.width,
options.cols = options.cols || options.termWidth || 80;
options.rows = options.rows || options.termHeight || 25;
options.startCol = options.startCol || 1;
const canvas = Array.from( { length : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) );
const parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
const state = {
row : 0,
col : 0,
};
const parser = new ANSIEscapeParser(parserOpts);
let lastRow = 0;
const canvasPos = {
col : 0,
row : 0,
};
parser.on('position update', (row, col) => {
state.row = row - 1;
state.col = col - 1;
let sgr;
function ensureCell() {
// we've pre-allocated a matrix, but allow for > supplied dimens up front. They will be trimmed @ finalize
if(!canvas[canvasPos.row]) {
canvas[canvasPos.row] = new Array(options.width);
for(let j = 0; j < options.width; ++j) {
canvas[canvasPos.row][j] = {};
}
}
canvas[canvasPos.row][canvasPos.col] = canvas[canvasPos.row][canvasPos.col] || {};
//canvas[canvasPos.row][0].width = Math.max(canvas[canvasPos.row][0].width || 0, canvasPos.col);
}
lastRow = Math.max(state.row, lastRow);
});
parser.on('literal', literal => {
//
@ -422,99 +409,83 @@ function createCleanAnsi(input, options, cb) {
//
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
for(let i = 0; i < literal.length; ++i) {
const c = literal.charAt(i);
for(let c of literal) {
if(state.col < options.cols && state.row < options.rows) {
canvas[state.row][state.col].char = c;
ensureCell();
canvas[canvasPos.row][canvasPos.col].char = c;
if(sgr) {
canvas[canvasPos.row][canvasPos.col].sgr = sgr;
sgr = null;
if(state.sgr) {
canvas[state.row][state.col].sgr = state.sgr;
state.sgr = null;
}
}
canvasPos.col += 1;
state.col += 1;
}
});
parser.on('control', (match, opCode) => {
if('m' !== opCode) {
return; // don't care'
//
// Movement is handled via 'position update', so we really only care about
// display opCodes
//
switch(opCode) {
case 'm' :
state.sgr = (state.sgr || '') + match;
break;
default :
if(-1 === [ 'C' ].indexOf(opCode)) {
console.log(`ignore opCode: ${opCode}`); // :TODO: REMOVE ME
}
}
sgr = match;
});
parser.on('position update', (row, col) => {
canvasPos.row = row - 1;
canvasPos.col = Math.min(col - 1, options.width);
});
function getLastPopulatedColumn(row) {
let col = row.length;
while(--col > 0) {
if(row[col].char || row[col].sgr) {
break;
}
}
return col;
}
parser.on('complete', () => {
for(let row = 0; row < options.height; ++row) {
let col = 0;
let output = '';
let lastSgr = '';
canvas.slice(0, lastRow + 1).forEach(row => {
const lastCol = getLastPopulatedColumn(row) + 1;
//while(col <= canvas[row][0].width) {
while(col < options.width) {
if(!canvas[row][col].char) {
canvas[row][col].char = ' ';
if(!canvas[row][col].sgr) {
// :TODO: fix duplicate SGR's in a row here - we just need one per sequence
canvas[row][col].sgr = ANSI.reset();
}
let i;
for(i = 0; i < lastCol; ++i) {
const col = row[i];
if(col.sgr) {
lastSgr = col.sgr;
}
col += 1;
output += `${col.sgr || ''}${col.char || ' '}`;
}
// :TODO: end *all* with CRLF - general usage will be width : 79 - prob update defaults
if(col <= options.width) {
canvas[row][col] = canvas[row][col] || {};
canvas[row][col].char = '\r\n';
canvas[row][col].sgr = ANSI.reset();
// :TODO: don't splice, just reset + fill with ' ' till end
for(let fillCol = col; fillCol <= options.width; ++fillCol) {
canvas[row][fillCol].char = ' ';
}
//canvas[row] = canvas[row].splice(0, col + 1);
//canvas[row][options.width - 1].char = '\r\n';
} else {
canvas[row] = canvas[row].splice(0, options.width + 1);
if(i < row.length) {
output += `${ANSI.blackBG()}${row.slice(i).map( () => ' ').join('')}${lastSgr}`;
}
}
let out = '';
for(let row = 0; row < options.height; ++row) {
out += canvas[row].map( col => {
let c = col.sgr || '';
c += col.char;
return c;
}).join('');
}
if(options.startCol + options.cols < options.termWidth || options.forceLineTerm) {
output += '\r\n';
}
});
// :TODO: finalize: @ any non-char cell, reset sgr & set to ' '
// :TODO: finalize: after sgr established, omit anything > supplied dimens
return cb(out);
return cb(null, output);
});
parser.parse(input);
}
// create2dArray = (rows, columns) => [...Array(rows).keys()].map(i => Array(columns).fill({}))
/*
const fs = require('graceful-fs');
let data = fs.readFileSync('/home/nuskooler/Downloads/art3.ans');
//let data = fs.readFileSync('/home/nuskooler/Downloads/art3.ans');
//let data = fs.readFileSync('/home/nuskooler/dev/enigma-bbs/mods/themes/nu-xibalba/MATRIX1.ANS');
let data = fs.readFileSync('/home/nuskooler/Downloads/ansi_diz_test/file_id.diz.2.ans');
data = iconv.decode(data, 'cp437');
createCleanAnsi(data, { width : 79, height : 25 }, (out) => {
prepAnsi(data, { cols : 45, rows : 25 }, (err, out) => {
out = iconv.encode(out, 'cp437');
fs.writeFileSync('/home/nuskooler/Downloads/art4.ans', out);
});

View File

@ -8,7 +8,6 @@ const ansi = require('../core/ansi_term.js');
const theme = require('../core/theme.js');
const FileEntry = require('../core/file_entry.js');
const stringFormat = require('../core/string_format.js');
const createCleanAnsi = require('../core/string_util.js').createCleanAnsi;
const FileArea = require('../core/file_base_area.js');
const Errors = require('../core/enig_error.js').Errors;
const ErrNotEnabled = require('../core/enig_error.js').ErrorReasons.NotEnabled;
@ -19,8 +18,6 @@ const FileAreaWeb = require('../core/file_area_web.js');
const FileBaseFilters = require('../core/file_base_filter.js');
const resolveMimeType = require('../core/mime_util.js').resolveMimeType;
const cleanControlCodes = require('../core/string_util.js').cleanControlCodes;
// deps
const async = require('async');
const _ = require('lodash');
@ -377,21 +374,19 @@ exports.getModule = class FileAreaList extends MenuModule {
function populateViews(callback) {
if(_.isString(self.currentFileEntry.desc)) {
const descView = self.viewControllers.browse.getView(MciViewIds.browse.desc);
if(descView) {
createCleanAnsi(
if(descView) {
descView.setAnsi(
self.currentFileEntry.desc,
{ height : self.client.termHeight, width : descView.dimens.width },
cleanDesc => {
// :TODO: use cleanDesc -- need to finish createCleanAnsi() !!
//descView.setText(cleanDesc);
descView.setText( self.currentFileEntry.desc );
{
prepped : false,
forceLineTerm : true
},
() => {
self.updateQueueIndicator();
self.populateCustomLabels('browse', MciViewIds.browse.customRangeStart);
return callback(null);
}
);
);
}
} else {
self.updateQueueIndicator();