* renderStringLength(): Account for ESC[<N>C "forward" ESC seq

* Use string util renderStringLength() in word wrap
* Hopefully resolve all issues with renderSubstr()
* Fix width issue in message list
This commit is contained in:
Bryan Ashby 2016-09-06 23:20:11 -06:00
parent f7c21baa52
commit d621fa9566
4 changed files with 74 additions and 26 deletions

View File

@ -291,6 +291,7 @@ function getValue(obj, path) {
module.exports = function format(fmt, obj) { module.exports = function format(fmt, obj) {
const re = REGEXP_BASIC_FORMAT; const re = REGEXP_BASIC_FORMAT;
re.lastIndex = 0; // reset from prev
let match; let match;
let pos; let pos;

View File

@ -195,30 +195,37 @@ function stringFromNullTermBuffer(buf, encoding) {
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8'); return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
} }
// :TODO: Add other codes from ansi_escape_parser const PIPE_REGEXP = /(\|[A-Z\d]{2})/g;
const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/g;
const PIPE_REGEXP = /\|[A-Z\d]{2}/g; const ANSI_OR_PIPE_REGEXP = new RegExp(PIPE_REGEXP.source + '|' + ANSI_REGEXP.source, 'g');
const ANSI_OR_PIPE_REGEXP = new RegExp(ANSI_REGEXP.source + '|' + PIPE_REGEXP.source, 'g');
// //
// Similar to substr() but works with ANSI/Pipe code strings // Similar to substr() but works with ANSI/Pipe code strings
// //
function renderSubstr(str, start, length) { function renderSubstr(str, start, length) {
start = start || 0; // shortcut for empty strings
length = Math.max(0, (length || str.length - start) - 1); if(0 === str.length) {
return str;
}
const re = ANSI_REGEXP; start = start || 0;
let pos; length = length || str.length - start;
const re = ANSI_OR_PIPE_REGEXP;
re.lastIndex = 0; // we recycle the obj; must reset!
let pos = 0;
let match; let match;
let out = ''; let out = '';
let renderLen = 0; let renderLen = 0;
let s;
do { do {
pos = re.lastIndex; pos = re.lastIndex;
match = re.exec(str); match = re.exec(str);
if(match) { if(match) {
if(match.index > pos) { if(match.index > pos) {
const s = str.slice(pos + start, match.index - (Math.min(0, length - renderLen))); s = str.slice(pos + start, Math.min(match.index, pos + (length - renderLen)));
start = 0; // start offset applies only once start = 0; // start offset applies only once
out += s; out += s;
renderLen += s.length; renderLen += s.length;
@ -230,19 +237,53 @@ function renderSubstr(str, start, length) {
// remainder // remainder
if(pos + start < str.length && renderLen < length) { if(pos + start < str.length && renderLen < length) {
out += str.slice(pos + start, pos + Math.max(0, length - renderLen)); out += str.slice(pos + start, (pos + start + (length - renderLen)));
//out += str.slice(pos + start, Math.max(1, pos + (length - renderLen - 1)));
} }
return out; return out;
} }
// //
// Method to return the "rendered" length taking into account // Method to return the "rendered" length taking into account Pipe and ANSI color codes.
// Pipe and ANSI color codes. Note that currently ANSI *movement*
// codes are not considred!
// //
function renderStringLength(str) { // We additionally account for ANSI *forward* movement ESC sequences
return str.replace(ANSI_OR_PIPE_REGEXP, '').length; // in the form of ESC[<N>C where <N> is the "go forward" character count.
//
// See also https://github.com/chalk/ansi-regex/blob/master/index.js
//
function renderStringLength(s) {
let m;
let pos;
let len = 0;
const re = ANSI_OR_PIPE_REGEXP;
re.lastIndex = 0; // we recycle the rege; reset
//
// Loop counting only literal (non-control) sequences
// paying special attention to ESC[<N>C which means forward <N>
//
do {
pos = re.lastIndex;
m = re.exec(s);
if(m) {
if(m.index > pos) {
len += s.slice(pos, m.index).length;
}
if('C' === m[3]) { // ESC[<N>C is foward/right
len += parseInt(m[2], 10) || 0;
}
}
} while(0 !== re.lastIndex);
if(pos < s.length) {
len += s.slice(pos).length;
}
return len;
} }
@ -267,7 +308,7 @@ function cleanControlCodes(input) {
pos = REGEXP_ANSI_CONTROL_CODES.lastIndex; pos = REGEXP_ANSI_CONTROL_CODES.lastIndex;
m = REGEXP_ANSI_CONTROL_CODES.exec(input); m = REGEXP_ANSI_CONTROL_CODES.exec(input);
if(null !== m) { if(m) {
if(m.index > pos) { if(m.index > pos) {
cleaned += input.slice(pos, m.index); cleaned += input.slice(pos, m.index);
} }

View File

@ -1,8 +1,9 @@
/* jslint node: true */ /* jslint node: true */
'use strict'; 'use strict';
var assert = require('assert'); var assert = require('assert');
var _ = require('lodash'); var _ = require('lodash');
const renderStringLength = require('./string_util.js').renderStringLength;
exports.wordWrapText = wordWrapText2; exports.wordWrapText = wordWrapText2;
@ -15,12 +16,14 @@ const SPACE_CHARS = [
const REGEXP_WORD_WRAP = new RegExp(`\t|[${SPACE_CHARS.join('')}]`, 'g'); const REGEXP_WORD_WRAP = new RegExp(`\t|[${SPACE_CHARS.join('')}]`, 'g');
/*
// //
// ANSI & pipe codes we indend to strip // ANSI & pipe codes we indend to strip
// //
// See also https://github.com/chalk/ansi-regex/blob/master/index.js // See also https://github.com/chalk/ansi-regex/blob/master/index.js
// //
// :TODO: Consolidate this, regexp's in ansi_escape_parser, and strutil. Need more complete set that includes common standads, bansi, and cterm.txt // :TODO: Consolidate this, regexp's in ansi_escape_parser, and strutil. Need more complete set that includes common standads, bansi, and cterm.txt
// renderStringLength() from strUtil does not account for ESC[<N>C (e.g. go forward)
const REGEXP_CONTROL_CODES = /(\|[\d]{2})|(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g; const REGEXP_CONTROL_CODES = /(\|[\d]{2})|(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g;
function getRenderLength(s) { function getRenderLength(s) {
@ -28,9 +31,11 @@ function getRenderLength(s) {
let pos; let pos;
let len = 0; let len = 0;
REGEXP_CONTROL_CODES.lastIndex = 0; // reset
// //
// Loop counting only literal (non-control) sequences // Loop counting only literal (non-control) sequences
// paying special attention to ESC[<N>C which means foward <N> // paying special attention to ESC[<N>C which means forward <N>
// //
do { do {
pos = REGEXP_CONTROL_CODES.lastIndex; pos = REGEXP_CONTROL_CODES.lastIndex;
@ -53,6 +58,7 @@ function getRenderLength(s) {
return len; return len;
} }
*/
function wordWrapText2(text, options) { function wordWrapText2(text, options) {
assert(_.isObject(options)); assert(_.isObject(options));
@ -79,7 +85,7 @@ function wordWrapText2(text, options) {
function appendWord() { function appendWord() {
word.match(REGEXP_GOBBLE).forEach( w => { word.match(REGEXP_GOBBLE).forEach( w => {
renderLen = getRenderLength(w); renderLen = renderStringLength(w);
if(result.renderLen[i] + renderLen > options.width) { if(result.renderLen[i] + renderLen > options.width) {
if(0 === i) { if(0 === i) {

View File

@ -206,8 +206,8 @@
messageAreaMessageList: { messageAreaMessageList: {
config: { config: {
listFormat: "|00|15{msgNum:>4} |03{subject:<29.29} |11{fromUserName:<20.20} |03{ts} |01|31{newIndicator}" listFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |01|31{newIndicator}"
focusListFormat: "|00|19|15{msgNum:>4} {subject:<29.29} {fromUserName:<20.20} {ts} {newIndicator}" focusListFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}"
dateTimeFormat: ddd MMM Do dateTimeFormat: ddd MMM Do
} }
mci: { mci: {
@ -260,8 +260,8 @@
mailMenuInbox: { mailMenuInbox: {
config: { config: {
listFormat: "|00|15{msgNum:>4} |03{subject:<29.29} |11{fromUserName:<20.20} |03{ts} |01|31{newIndicator}" listFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |01|31{newIndicator}"
focusListFormat: "|00|19|15{msgNum:>4} {subject:<29.29} {fromUserName:<20.20} {ts} {newIndicator}" focusListFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}"
dateTimeFormat: ddd MMM Do dateTimeFormat: ddd MMM Do
} }
mci: { mci: {
@ -467,8 +467,8 @@
newScanMessageList: { newScanMessageList: {
config: { config: {
listFormat: "|00|15 {msgNum:<5.5}|03{subject:<29.29} |15{fromUserName:<20.20} {ts}" listFormat: "|00|15 {msgNum:<5.5}|03{subject:<28.27} |15{fromUserName:<20.20} {ts}"
focusListFormat: "|00|19> |15{msgNum:<5.5}{subject:<29.29} {fromUserName:<20.20} {ts}" focusListFormat: "|00|19> |15{msgNum:<5.5}{subject:<28.27} {fromUserName:<20.20} {ts}"
dateTimeFormat: ddd MMM Do dateTimeFormat: ddd MMM Do
} }
mci: { mci: {