* 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) {
const re = REGEXP_BASIC_FORMAT;
re.lastIndex = 0; // reset from prev
let match;
let pos;

View File

@ -195,30 +195,37 @@ function stringFromNullTermBuffer(buf, encoding) {
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
}
// :TODO: Add other codes from ansi_escape_parser
const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
const PIPE_REGEXP = /\|[A-Z\d]{2}/g;
const ANSI_OR_PIPE_REGEXP = new RegExp(ANSI_REGEXP.source + '|' + PIPE_REGEXP.source, 'g');
const PIPE_REGEXP = /(\|[A-Z\d]{2})/g;
const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/g;
const ANSI_OR_PIPE_REGEXP = new RegExp(PIPE_REGEXP.source + '|' + ANSI_REGEXP.source, 'g');
//
// Similar to substr() but works with ANSI/Pipe code strings
//
function renderSubstr(str, start, length) {
start = start || 0;
length = Math.max(0, (length || str.length - start) - 1);
// shortcut for empty strings
if(0 === str.length) {
return str;
}
const re = ANSI_REGEXP;
let pos;
start = start || 0;
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 out = '';
let renderLen = 0;
let s;
do {
pos = re.lastIndex;
match = re.exec(str);
if(match) {
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
out += s;
renderLen += s.length;
@ -230,19 +237,53 @@ function renderSubstr(str, start, length) {
// remainder
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;
}
//
// Method to return the "rendered" length taking into account
// Pipe and ANSI color codes. Note that currently ANSI *movement*
// codes are not considred!
// Method to return the "rendered" length taking into account Pipe and ANSI color codes.
//
function renderStringLength(str) {
return str.replace(ANSI_OR_PIPE_REGEXP, '').length;
// We additionally account for ANSI *forward* movement ESC sequences
// 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;
m = REGEXP_ANSI_CONTROL_CODES.exec(input);
if(null !== m) {
if(m) {
if(m.index > pos) {
cleaned += input.slice(pos, m.index);
}

View File

@ -3,6 +3,7 @@
var assert = require('assert');
var _ = require('lodash');
const renderStringLength = require('./string_util.js').renderStringLength;
exports.wordWrapText = wordWrapText2;
@ -15,12 +16,14 @@ const SPACE_CHARS = [
const REGEXP_WORD_WRAP = new RegExp(`\t|[${SPACE_CHARS.join('')}]`, 'g');
/*
//
// ANSI & pipe codes we indend to strip
//
// 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
// 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;
function getRenderLength(s) {
@ -28,9 +31,11 @@ function getRenderLength(s) {
let pos;
let len = 0;
REGEXP_CONTROL_CODES.lastIndex = 0; // reset
//
// 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 {
pos = REGEXP_CONTROL_CODES.lastIndex;
@ -53,6 +58,7 @@ function getRenderLength(s) {
return len;
}
*/
function wordWrapText2(text, options) {
assert(_.isObject(options));
@ -79,7 +85,7 @@ function wordWrapText2(text, options) {
function appendWord() {
word.match(REGEXP_GOBBLE).forEach( w => {
renderLen = getRenderLength(w);
renderLen = renderStringLength(w);
if(result.renderLen[i] + renderLen > options.width) {
if(0 === i) {

View File

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