From 0a079ee4d8c0b528923c997206631b0b81d9658d Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sun, 6 Aug 2017 11:31:21 -0600 Subject: [PATCH] Display ANSI in file area desc view --- core/string_util.js | 175 +++++++++++++++++------------------------ mods/file_area_list.js | 21 ++--- 2 files changed, 81 insertions(+), 115 deletions(-) diff --git a/core/string_util.js b/core/string_util.js index f7224a66..903e47de 100644 --- a/core/string_util.js +++ b/core/string_util.js @@ -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); }); diff --git a/mods/file_area_list.js b/mods/file_area_list.js index fbe11d52..b356b790 100644 --- a/mods/file_area_list.js +++ b/mods/file_area_list.js @@ -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();