Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions __tests__/formatter/__snapshots__/formatter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ exports[`MarkupFormatter <frame> tag scenario 20 1`] = `
└───────┘"
`;

exports[`MarkupFormatter <frame> tag scenario 21 1`] = `
"┌─────────────┐
│< not-a-tag >│
└─────────────┘"
`;

exports[`MarkupFormatter <frame> tag scenario 22 1`] = `
"┌─────────────┐
│First Line │
│ │
│Second Line │
└─────────────┘"
`;

exports[`MarkupFormatter <ol> tag should correctly render list with numbered indexes 1`] = `
"1. Red
2. Green
Expand Down Expand Up @@ -387,11 +401,11 @@ exports[`MarkupFormatter should correctly format the xml scenario 03 1`] = `
Normal"
`;

exports[`MarkupFormatter should correctly format the xml scenario 04 1`] = `" Red   Green "`;
exports[`MarkupFormatter should correctly format the xml scenario 04 1`] = `" Red  Green "`;

exports[`MarkupFormatter should correctly format the xml scenario 05 1`] = `"Lorem ipsum dolor sit amet consectetur adipiscing"`;

exports[`MarkupFormatter should correctly format the xml scenario 06 1`] = `"Green Blue Orange"`;
exports[`MarkupFormatter should correctly format the xml scenario 06 1`] = `"Green Blue Orange"`;

exports[`MarkupFormatter should correctly format the xml scenario 07 1`] = `"Lorem ipsum dolor sit amet"`;

Expand Down
44 changes: 41 additions & 3 deletions __tests__/formatter/formatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe("MarkupFormatter", () => {

const formatted = MarkupFormatter.format(xml);

expect(formatted).toMatchAnsiString(" Red Green ");
expect(formatted).toMatchAnsiString(" Red Green ");
expect(formatted).toContainAnsiStringWithStyles(" Red ", {
color: "red",
bold: true,
Expand Down Expand Up @@ -214,14 +214,14 @@ describe("MarkupFormatter", () => {

const formatted = MarkupFormatter.format(xml);

expect(formatted).toMatchAnsiString("Green Blue Orange");
expect(formatted).toMatchAnsiString("Green Blue Orange");
expect(formatted).toContainAnsiStringWithStyles("Green", {
color: "rgb(137, 245, 209)",
});
expect(formatted).toContainAnsiStringWithStyles("Blue", {
color: "blue",
});
expect(formatted).toContainAnsiStringWithStyles(" Orange", {
expect(formatted).toContainAnsiStringWithStyles(" Orange", {
color: "#f5aa42",
});
expect(formatted).toMatchSnapshot();
Expand Down Expand Up @@ -2202,6 +2202,44 @@ Other text
);
expect(formatted).toMatchSnapshot();
});

it("scenario 21", () => {
const xml = html`<frame width="15"> &lt; not-a-tag &gt; </frame> `; // effective width of 12

const formatted = MarkupFormatter.format(xml);

expect(formatted).toMatchAnsiString(
`
┌─────────────┐
│< not-a-tag >│
└─────────────┘
`.trim()
);
expect(formatted).toMatchSnapshot();
});

it("scenario 22", () => {
const xml = html`<frame width="15">
<line>First Line</line> <br />
<span></span>
<span>
<line>Second Line</line>
</span>
</frame> `;

const formatted = MarkupFormatter.format(xml);

expect(formatted).toMatchAnsiString(
`
┌─────────────┐
│First Line │
│ │
│Second Line │
└─────────────┘
`.trim()
);
expect(formatted).toMatchSnapshot();
});
});

describe("complex structures scenarios", () => {
Expand Down
98 changes: 76 additions & 22 deletions src/formatter/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { TermxBgColors } from "../colors/termx-bg-color";
import { TermxFontColors } from "../colors/termx-font-colors";
import { desanitizeHtml } from "../html-tag";
import type { MarkupNode } from "../markup-parser";
import { parseMarkup } from "../markup-parser";
import type { Settings } from "../settings";
Expand Down Expand Up @@ -127,11 +126,12 @@ export class MarkupFormatter {

format(markup: string | MarkupNode): string {
const node = typeof markup === "string" ? parseMarkup(markup) : markup;
const text = this.parse(node, new TextRenderer());
this.normalizeNode(node);
const text = this.putInto(node, new TextRenderer());

text.removeTrailingNewLine();

return desanitizeHtml(text.render());
return text.render();
}

private handleError(message: string) {
Expand Down Expand Up @@ -162,8 +162,40 @@ export class MarkupFormatter {
return result;
}

private normalizeNode(node: MarkupNode) {
private getPreNodeLastLine(node: MarkupNode, initLine: string): string {
let currentLine = initLine;

if (
node.tag === "br" ||
node.tag == "li" ||
BLOCK_NODE_TYPES.includes(node.tag)
) {
return "";
}

for (let i = 0; i < node.content.length; i++) {
const content = node.content[i]!;

if (typeof content === "string") {
const lastEolIdx = content.lastIndexOf("\n");
if (lastEolIdx === -1) {
currentLine += content;
} else {
currentLine = content.substring(lastEolIdx + 1);
}
} else {
currentLine = this.getPreNodeLastLine(content, currentLine);
}
}

return currentLine;
}

private normalizeNode(node: MarkupNode, initLine = ""): string {
let currentLine = initLine;

if (node.tag !== "pre") {
// remove unnecessary whitespace
for (let i = 0; i < node.content.length; i++) {
const content = node.content[i]!;

Expand All @@ -175,18 +207,19 @@ export class MarkupFormatter {
const prevTag = getNodeType(node.content[i - 1]);
const nextTag = getNodeType(node.content[i + 1]);

const isStartOfLine =
prevNodeDisplayType === "block" || prevTag === "br" || i === 0;
const isStartOfLine = currentLine.trim() === "";
const isEndOfLine =
nextNodeDisplayType === "block" ||
nextTag === "br" ||
i === node.content.length - 1;

if (trimmed.length === 0) {
const shouldKeepWhitespace = !isStartOfLine && !isEndOfLine;
const shouldKeepWhitespace =
!isStartOfLine && !isEndOfLine && currentLine.at(-1) != " ";

if (shouldKeepWhitespace && prevTag !== "s") {
node.content[i] = " ";
currentLine += " ";
} else {
// Remove the empty node
node.content = node.content
Expand All @@ -198,19 +231,26 @@ export class MarkupFormatter {
if (
!isStartOfLine &&
singleLined[0] === " " &&
currentLine.at(-1) != " " &&
prevNodeDisplayType === "inline"
) {
trimmed = " " + trimmed;
}

if (!isEndOfLine && singleLined[singleLined.length - 1] === " ") {
node.content[i] = trimmed + " ";
currentLine += trimmed + " ";
} else {
node.content[i] = trimmed;
currentLine += trimmed;
}
}
} else {
currentLine = this.normalizeNode(content, currentLine);
}
}
} else {
currentLine = this.getPreNodeLastLine(node, currentLine);
}

for (let i = 0; i < node.content.length; i++) {
Expand All @@ -234,13 +274,27 @@ export class MarkupFormatter {
}
}
}

if (
node.tag === "br" ||
node.tag === "li" ||
BLOCK_NODE_TYPES.includes(node.tag)
) {
currentLine = "";
}

return currentLine;
}

private parse(node: MarkupNode, result: TextRenderer): TextRenderer {
/**
* Takes a MarkupNode and puts all of its contents into the
* TextRenderer
*/
private putInto(node: MarkupNode, result: TextRenderer): TextRenderer {
switch (node.tag) {
case "pre": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand All @@ -265,7 +319,7 @@ export class MarkupFormatter {
case "line":
case "span": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand All @@ -277,7 +331,7 @@ export class MarkupFormatter {
if (typeof content === "string") {
result.appendText(charGroup.createChars(content));
} else {
this.parse(content, result);
this.putInto(content, result);
}
}

Expand All @@ -291,7 +345,7 @@ export class MarkupFormatter {
const padding = this.getListPadding();

ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const unstyledCharGroup = new CharacterGroup({
bg: ScopeTracker.currentScope.attributes.bg,
Expand Down Expand Up @@ -325,7 +379,7 @@ export class MarkupFormatter {
i + 1
);

const subText = this.parse(content, new TextRenderer());
const subText = this.putInto(content, new TextRenderer());

subText.prependAllLines(
unstyledCharGroup.createChars(" ".repeat(contentPad))
Expand All @@ -350,7 +404,7 @@ export class MarkupFormatter {
}
case "li": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand All @@ -366,7 +420,7 @@ export class MarkupFormatter {
if (typeof content === "string") {
result.appendText(charGroup.createChars(content));
} else {
this.parse(content, result);
this.putInto(content, result);
}
}

Expand All @@ -376,7 +430,7 @@ export class MarkupFormatter {
}
case "pad": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand All @@ -392,7 +446,7 @@ export class MarkupFormatter {
if (typeof content === "string") {
contentText.appendText(charGroup.createChars(content));
} else {
this.parse(content, contentText);
this.putInto(content, contentText);
}
}

Expand All @@ -407,7 +461,7 @@ export class MarkupFormatter {
}
case "frame": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand Down Expand Up @@ -459,7 +513,7 @@ export class MarkupFormatter {
if (content.length > 0)
contentText.appendText(charGroup.createChars(content));
} else {
this.parse(content, contentText);
this.putInto(content, contentText);
}
}

Expand Down Expand Up @@ -603,7 +657,7 @@ export class MarkupFormatter {
}
case "s": {
ScopeTracker.enterScope(this.createScope(node));
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup = new CharacterGroup(
ScopeTracker.currentScope.attributes
Expand All @@ -616,7 +670,7 @@ export class MarkupFormatter {
return result;
}
case "": {
this.normalizeNode(node);
// this.normalizeNode(node);

const charGroup =
result.lastGroup ??
Expand All @@ -628,7 +682,7 @@ export class MarkupFormatter {
if (typeof content === "string") {
result.appendText(charGroup.createChars(content));
} else {
this.parse(content, result);
this.putInto(content, result);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/formatter/text-renderer/text.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TermxBgColors } from "../../colors/termx-bg-color";
import { TermxFontColors } from "../../colors/termx-font-colors";
import { desanitizeHtml } from "../../html-tag";
import { terminalWidth } from "../../terminal-width";
import type { Styles } from "./styles";

Expand Down Expand Up @@ -61,6 +62,8 @@ export class CharacterGroup {
constructor(public readonly styles: Styles) {}

createChars(value: string): Character[] {
value = desanitizeHtml(value);

const chars: Character[] = [];

for (let i = 0; i < value.length; i++) {
Expand Down