Skip to content

Commit

Permalink
Merge branch 'multiple_decorations' of https://github.com/ktunkiewicz…
Browse files Browse the repository at this point in the history
…/anser into new-version
  • Loading branch information
IonicaBizau committed Sep 6, 2020
2 parents 4b31a8a + b71f99c commit 48c0cbe
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 49 deletions.
7 changes: 6 additions & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Type definitions for Anser
// Project: https://github.com/IonicaBizau/anser

type DecorationName = 'bold' | 'dim' | 'italic' | 'underline' | 'blink' | 'reverse' | 'hidden' | 'strikethrough';

export interface AnserJsonEntry {
/** The text. */
content: string;
Expand All @@ -14,7 +16,10 @@ export interface AnserJsonEntry {
bg_truecolor: string;
/** `true` if a carriageReturn \r was fount at end of line. */
clearLine: boolean;
decoration: null | 'bold' | 'dim' | 'italic' | 'underline' | 'blink' | 'reverse' | 'hidden' | 'strikethrough';
/** The decoration last declared before the text. */
decoration: null | DecorationName;
/** All decorations that apply to the text. */
decorations: Array<DecorationName>;
/** `true` if the colors were processed, `false` otherwise. */
was_processed: boolean;
/** A function returning `true` if the content is empty, or `false` otherwise. */
Expand Down
132 changes: 84 additions & 48 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,16 @@ class Anser {

let result = {
content: text
, fg: null
, bg: null
, fg_truecolor: null
, bg_truecolor: null
, clearLine: options.clearLine
, decoration: null
, was_processed: false
, isEmpty: () => !result.content
, fg: null
, bg: null
, fg_truecolor: null
, bg_truecolor: null
, isInverted: false
, clearLine: options.clearLine
, decoration: null
, decorations: []
, was_processed: false
, isEmpty: () => !result.content
};

// Each "chunk" is the text after the CSI (ESC + "[") and before the next CSI/EOF.
Expand Down Expand Up @@ -358,32 +360,33 @@ class Anser {

let self = this;

self.decoration = null;
self.decorations = self.decorations || [];

while (nums.length > 0) {
let num_str = nums.shift();
let num = parseInt(num_str);

if (isNaN(num) || num === 0) {
self.fg = self.bg = self.decoration = null;
self.fg = self.bg = null;
self.decorations = [];
} else if (num === 1) {
self.decoration = "bold";
self.decorations.push("bold");
} else if (num === 2) {
self.decoration = "dim";
self.decorations.push("dim");
// Enable code 2 to get string
} else if (num == 3) {
self.decoration = "italic";
self.decorations.push("italic");
} else if (num == 4) {
self.decoration = "underline";
self.decorations.push("underline");
} else if (num == 5) {
self.decoration = "blink";
self.decorations.push("blink");
} else if (num === 7) {
self.decoration = "reverse";
self.decorations.push("reverse");
} else if (num === 8) {
self.decoration = "hidden";
self.decorations.push("hidden");
// Enable code 9 to get strikethrough
} else if (num === 9) {
self.decoration = "strikethrough";
self.decorations.push("strikethrough");
} else if (num == 39) {
self.fg = null;
} else if (num == 49) {
Expand Down Expand Up @@ -454,7 +457,7 @@ class Anser {
}
}

if ((self.fg === null) && (self.bg === null) && (self.decoration === null)) {
if ((self.fg === null) && (self.bg === null) && (self.decorations.length === 0)) {
return result;
} else {
let styles = [];
Expand All @@ -465,7 +468,8 @@ class Anser {
result.bg = self.bg;
result.fg_truecolor = self.fg_truecolor;
result.bg_truecolor = self.bg_truecolor;
result.decoration = self.decoration;
result.decorations = self.decorations;
result.decoration = self.decorations.slice(-1).pop() || null;
result.was_processed = true;

return result;
Expand All @@ -488,20 +492,42 @@ class Anser {
* @return {Object|String} The result (object if `json` is wanted back or string otherwise).
*/
processChunk (text, options, markup) {

let self = this;
options = options || {};
let jsonChunk = this.processChunkJson(text, options, markup);
let use_classes = options.use_classes;

// "reverse" decoration reverses foreground and background colors
jsonChunk.decorations = jsonChunk.decorations
.filter((decoration) => {
if (decoration === "reverse") {
// when reversing, missing colors are defaulted to black (bg) and white (fg)
if (!jsonChunk.fg) {
jsonChunk.fg = ANSI_COLORS[0][7][use_classes ? "class" : "color"];
}
if (!jsonChunk.bg) {
jsonChunk.bg = ANSI_COLORS[0][0][use_classes ? "class" : "color"];
}
let tmpFg = jsonChunk.fg;
jsonChunk.fg = jsonChunk.bg;
jsonChunk.bg = tmpFg;
let tmpFgTrue = jsonChunk.fg_truecolor;
jsonChunk.fg_truecolor = jsonChunk.bg_truecolor;
jsonChunk.bg_truecolor = tmpFgTrue;
jsonChunk.isInverted = true;
return false;
}
return true;
});

if (options.json) { return jsonChunk; }
if (jsonChunk.isEmpty()) { return ""; }
if (!jsonChunk.was_processed) { return jsonChunk.content; }

let use_classes = options.use_classes;

let styles = [];
let classes = [];
let colors = [];
let decorations = [];
let textDecorations = [];
let data = {};

let render_data = data => {
let fragments = [];
let key;
Expand All @@ -513,55 +539,65 @@ class Anser {
return fragments.length > 0 ? " " + fragments.join(" ") : "";
};

if (jsonChunk.isInverted) {
data["ansi-is-inverted"] = "true";
}

if (jsonChunk.fg) {
if (use_classes) {
classes.push(jsonChunk.fg + "-fg");
colors.push(jsonChunk.fg + "-fg");
if (jsonChunk.fg_truecolor !== null) {
data["ansi-truecolor-fg"] = jsonChunk.fg_truecolor;
jsonChunk.fg_truecolor = null;
}
} else {
styles.push("color:rgb(" + jsonChunk.fg + ")");
colors.push("color:rgb(" + jsonChunk.fg + ")");
}
}

if (jsonChunk.bg) {
if (use_classes) {
classes.push(jsonChunk.bg + "-bg");
colors.push(jsonChunk.bg + "-bg");
if (jsonChunk.bg_truecolor !== null) {
data["ansi-truecolor-bg"] = jsonChunk.bg_truecolor;
jsonChunk.bg_truecolor = null;
}
} else {
styles.push("background-color:rgb(" + jsonChunk.bg + ")");
colors.push("background-color:rgb(" + jsonChunk.bg + ")");
}
}

if (jsonChunk.decoration) {
jsonChunk.decorations.forEach((decoration) => {
// use classes
if (use_classes) {
classes.push("ansi-" + jsonChunk.decoration);
} else if (jsonChunk.decoration === "bold") {
styles.push("font-weight:bold");
} else if (jsonChunk.decoration === "dim") {
styles.push("opacity:0.5");
} else if (jsonChunk.decoration === "italic") {
styles.push("font-style:italic");
// underline and blink are treated bellow
} else if (jsonChunk.decoration === "reverse") {
styles.push("filter:invert(100%)");
} else if (jsonChunk.decoration === "hidden") {
styles.push("visibility:hidden");
} else if (jsonChunk.decoration === "strikethrough") {
styles.push("text-decoration:line-through");
decorations.push("ansi-" + decoration);
return;
}
// use styles
if (decoration === "bold") {
decorations.push("font-weight:bold");
} else if (decoration === "dim") {
decorations.push("opacity:0.5");
} else if (decoration === "italic") {
decorations.push("font-style:italic");
} else if (decoration === "hidden") {
decorations.push("visibility:hidden");
} else if (decoration === "strikethrough") {
textDecorations.push("line-through");
} else {
styles.push("text-decoration:" + jsonChunk.decoration);
// underline and blink are treated here
textDecorations.push(decoration);
}
});

if (textDecorations.length) {
decorations.push("text-decoration:" + textDecorations.join(" "));
}

if (use_classes) {
return "<span class=\"" + classes.join(" ") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
return "<span class=\"" + colors.concat(decorations).join(" ") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
} else {
return "<span style=\"" + styles.join(";") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
return "<span style=\"" + colors.concat(decorations).join(";") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
}
}
};
Expand Down
62 changes: 62 additions & 0 deletions test/ansi_up-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,68 @@ describe("Anser", () => {
});
});

describe("use multiple text styles", () => {
describe("default", () => {
it("underline, blinking, bold, blue text on red background", () => {
const start = "\x1B[4m" + "\x1B[5m" + "\x1B[1;34m" + "\x1B[41m" + "foo" + "\x1B[0m" + "bar";
const expected = '<span style="color:rgb(0, 0, 187);background-color:rgb(187, 0, 0);font-weight:bold;text-decoration:underline blink">foo</span>bar';
const l = Anser.ansiToHtml(start);
l.should.eql(expected);
});
});
describe("with classes", () => {
it("underline, blinking, bold, blue text on red background", () => {
const start = "\x1B[4m" + "\x1B[5m" + "\x1B[1;34m" + "\x1B[41m" + "foo" + "\x1B[0m" + "bar";
const expected = '<span class="ansi-blue-fg ansi-red-bg ansi-underline ansi-blink ansi-bold">foo</span>bar';
const l = Anser.ansiToHtml(start, {use_classes: true});
l.should.eql(expected);
});
});
});

describe("reverse/inverse colors", () => {
describe("default", () => {
it("blue text on red background, to red text on blue background", () => {
const start = "\x1B[7m" + "\x1B[34m" + "\x1B[41m" + "foo" + "\x1B[0m";
const expected = '<span style="color:rgb(187, 0, 0);background-color:rgb(0, 0, 187)" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start);
l.should.eql(expected);
});
it("blue text inversed to be blue background", () => {
const start = "\x1B[7m" + "\x1B[34m" + "foo" + "\x1B[0m";
const expected = '<span style="color:rgb(0, 0, 0);background-color:rgb(0, 0, 187)" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start);
l.should.eql(expected);
});
it("red background inversed to be red text", () => {
const start = "\x1B[7m" + "\x1B[41m" + "foo" + "\x1B[0m";
const expected = '<span style="color:rgb(187, 0, 0);background-color:rgb(255,255,255)" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start);
l.should.eql(expected);
});
});
describe("with classes", () => {
it("blue text on red background, to red text on blue background", () => {
const start = "\x1B[7m" + "\x1B[34m" + "\x1B[41m" + "foo" + "\x1B[0m";
const expected = '<span class="ansi-red-fg ansi-blue-bg" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start, {use_classes: true});
l.should.eql(expected);
});
it("blue text inversed to be blue background", () => {
const start = "\x1B[7m" + "\x1B[34m" + "foo" + "\x1B[0m";
const expected = '<span class="ansi-black-fg ansi-blue-bg" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start, {use_classes: true});
l.should.eql(expected);
});
it("red background inversed to be red text", () => {
const start = "\x1B[7m" + "\x1B[41m" + "foo" + "\x1B[0m";
const expected = '<span class="ansi-red-fg ansi-white-bg" data-ansi-is-inverted="true">foo</span>';
const l = Anser.ansiToHtml(start, {use_classes: true});
l.should.eql(expected);
});
});
});

describe("ignore unsupported CSI", () => {
it("should correctly convert a string similar to CSI", () => {
// https://github.com/drudru/Anser/pull/15
Expand Down

0 comments on commit 48c0cbe

Please sign in to comment.