From 1bad0de5c1f9c1e800453eaadbedd8cbad2153bc Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sun, 27 Aug 2017 20:08:13 -0600 Subject: [PATCH] Major improvements to quote builder for pre-formatted text/etc. --- core/message.js | 62 ++++++++++++++++++++++++++++++++++++--------- core/string_util.js | 28 +++++++++++++++++++- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/core/message.js b/core/message.js index 600ccf55..d690c0ed 100644 --- a/core/message.js +++ b/core/message.js @@ -10,7 +10,7 @@ const Errors = require('./enig_error.js').Errors; const ANSI = require('./ansi_term.js'); const { - prepAnsi, isAnsi, + prepAnsi, isAnsi, isFormattedLine, splitTextAtTerms, renderSubstr } = require('./string_util.js'); @@ -481,6 +481,20 @@ Message.prototype.getQuoteLines = function(options, cb) { }); } + function getFormattedLine(line) { + // for pre-formatted text, we just append a line truncated to fit + let newLen; + const total = line.length + quotePrefix.length; + + if(total > options.cols) { + newLen = options.cols - total; + } else { + newLen = total; + } + + return `${quotePrefix}${line.slice(0, newLen)}`; + } + if(options.isAnsi) { prepAnsi( this.message.replace(/\r?\n/g, '\r\n'), // normalized LF -> CRLF @@ -507,6 +521,7 @@ Message.prototype.getQuoteLines = function(options, cb) { // strip colors, colorize the lines, etc. If we exclude the prefixes, this seems to do // the trick and allow them to leave them alone! // + // :TODO: The way this bumps to the right is super annoying -- it would be nice to just invert the entire line split.forEach(l => { quoteLines.push(`${lastSgr}${l}`); @@ -522,7 +537,7 @@ Message.prototype.getQuoteLines = function(options, cb) { } else { const QUOTE_RE = /^ ((?:[A-Za-z0-9]{2}\> )+(?:[A-Za-z0-9]{2}\>)*) */; const quoted = []; - const input = this.message.trim().replace(/\b/g, ''); + const input = _.trimEnd(this.message).replace(/\b/g, ''); // find *last* tearline let tearLinePos = this.getTearLinePosition(input); @@ -535,23 +550,42 @@ Message.prototype.getQuoteLines = function(options, cb) { // - New (pre)quoted line - quote_line // - Continuation of new/quoted line // - // :TODO: keep lines as-is that - // - have extended ASCII - // - have tabs or " " (3+ spaces), e.g. pre-formatting - // - contain pipe codes - // + // Also: + // - Detect pre-formatted lines & try to keep them as-is + // let state; let buf = ''; let quoteMatch; + + if(quoted.length > 0) { + // + // Preserve paragraph seperation. + // + // FSC-0032 states something about leaving blank lines fully blank + // (without a prefix) but it seems nicer (and more consistent with other systems) + // to put 'em in. + // + quoted.push(quotePrefix); + } + paragraph.split(/\r?\n/).forEach(line => { + if(0 === line.trim().length) { + // see blank line notes above + return quoted.push(quotePrefix); + } + quoteMatch = line.match(QUOTE_RE); switch(state) { case 'line' : if(quoteMatch) { - quoted.push(...getWrapped(buf, quoteMatch[1])); - state = 'quote_line'; - buf = line; + if(isFormattedLine(line)) { + quoted.push(getFormattedLine(line.replace(/\s/, ''))); + } else { + quoted.push(...getWrapped(buf, quoteMatch[1])); + state = 'quote_line'; + buf = line; + } } else { buf += ` ${line}`; } @@ -574,8 +608,12 @@ Message.prototype.getQuoteLines = function(options, cb) { break; default : - state = quoteMatch ? 'quote_line' : 'line'; - buf = 'line' === state ? line : line.replace(/\s/, '');//_.trimStart(line); + if(isFormattedLine(line)) { + quoted.push(getFormattedLine(line)); + } else { + state = quoteMatch ? 'quote_line' : 'line'; + buf = 'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any + } break; } }); diff --git a/core/string_util.js b/core/string_util.js index cfad6ae7..ea9e5ac6 100644 --- a/core/string_util.js +++ b/core/string_util.js @@ -27,6 +27,7 @@ exports.cleanControlCodes = cleanControlCodes; exports.prepAnsi = prepAnsi; exports.isAnsi = isAnsi; exports.isAnsiLine = isAnsiLine; +exports.isFormattedLine = isFormattedLine; exports.splitTextAtTerms = splitTextAtTerms; // :TODO: create Unicode verison of this @@ -582,6 +583,31 @@ function isAnsiLine(line) { return isAnsi(line);// || renderStringLength(line) < line.length; } +// +// Returns true if the line is considered "formatted". A line is +// considered formatted if it contains: +// * ANSI +// * Pipe codes +// * Extended (CP437) ASCII - https://www.ascii-codes.com/ +// * Tabs +// * Contigous 3+ spaces before the end of the line +// +function isFormattedLine(line) { + if(renderStringLength(line) < line.length) { + return true; // ANSI or Pipe Codes + } + + if(line.match(/[\t\x00-\x1f\x80-\xff]/)) { // eslint-disable-line no-control-regex + return true; + } + + if(_.trimEnd(line).match(/[ ]{3,}/)) { + return true; + } + + return false; +} + function isAnsi(input) { // // * ANSI found - limited, just colors @@ -603,7 +629,7 @@ function isAnsi(input) { */ // :TODO: if a similar method is kept, use exec() until threshold - const ANSI_DET_REGEXP = /(?:\x1b\x5b)[\?=;0-9]*?[ABCDEFGHJKLMSTfhlmnprsu]/g; + const ANSI_DET_REGEXP = /(?:\x1b\x5b)[\?=;0-9]*?[ABCDEFGHJKLMSTfhlmnprsu]/g; // eslint-disable-line no-control-regex const m = input.match(ANSI_DET_REGEXP) || []; return m.length >= 4; // :TODO: do this reasonably, e.g. a percent or soemthing }