diff --git a/src/emitter.ts b/src/emitter.ts
index 0301f34..9a3002c 100644
--- a/src/emitter.ts
+++ b/src/emitter.ts
@@ -16,11 +16,18 @@ import type {
CommentNode,
} from './node-types';
+const getIndentMatcher = unaryMemoize(
+ (length: number) => new RegExp(String.raw`\n[\p{Space_Separator}\t]{1,${length}}`, 'gu'),
+ Array.from({ length: 20 }, (_, i) => i + 1)
+);
+
export class Emitter {
str: string;
+ indent: number;
constructor() {
this.str = '';
+ this.indent = 0;
}
emit(node: Node | Node[]) {
@@ -106,7 +113,10 @@ export class Emitter {
emitListItem(li: OrderedListItemNode | UnorderedListItemNode) {
const attrs = li.attrs.map(a => ` ${a.key}=${JSON.stringify(a.value)}`).join('');
this.str += `
`;
+ const oldIndent = this.indent;
+ this.indent = li.contentsIndent;
this.emitFragment(li.contents);
+ this.indent = oldIndent;
if (li.sublist !== null) {
if (li.sublist.name === 'ol') {
this.emitOrderedList(li.sublist);
@@ -130,7 +140,12 @@ export class Emitter {
}
emitText(text: TextNode) {
- this.str += text.contents;
+ let contents = text.contents;
+ if (this.indent) {
+ const indentMatcher = getIndentMatcher(this.indent);
+ contents = contents.replace(indentMatcher, '\n');
+ }
+ this.str += contents;
}
emitTick(node: TickNode) {
@@ -165,3 +180,15 @@ export class Emitter {
this.str += `${wrapping}>`;
}
}
+
+function unaryMemoize(fn: (arg: K) => V, prepopulate: K[] = []) {
+ const cache = new Map(prepopulate.map(arg => [arg, fn(arg)]));
+ return (arg: K) => {
+ let value = cache.get(arg);
+ if (value === undefined && !cache.has(arg)) {
+ value = fn(arg);
+ cache.set(arg, value);
+ }
+ return value as V;
+ };
+}
diff --git a/src/node-types.ts b/src/node-types.ts
index cf2000a..9393aaf 100644
--- a/src/node-types.ts
+++ b/src/node-types.ts
@@ -188,6 +188,7 @@ export type OrderedListNode = {
export type UnorderedListItemNode = {
name: 'unordered-list-item';
contents: FragmentNode[];
+ contentsIndent: number;
sublist: ListNode | null;
attrs: { key: string; value: string; location: LocationRange }[];
location: LocationRange;
@@ -196,6 +197,7 @@ export type UnorderedListItemNode = {
export type OrderedListItemNode = {
name: 'ordered-list-item';
contents: FragmentNode[];
+ contentsIndent: number;
sublist: ListNode | null;
attrs: { key: string; value: string; location: LocationRange }[];
location: LocationRange;
diff --git a/src/parser.ts b/src/parser.ts
index 5ba5065..861ada8 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -79,12 +79,15 @@ export class Parser {
const startTok = this._t.peek() as OrderedListToken | UnorderedListToken;
let node: Unlocated;
+ let contentsIndent: number;
if (startTok.name === 'ul') {
const match = startTok.contents.match(/(\s*)\* /);
node = { name: 'ul', indent: match![1].length, contents: [] };
+ contentsIndent = match![0].length;
} else {
const match = startTok.contents.match(/(\s*)([^.]+)\. /);
node = { name: 'ol', indent: match![1].length, start: Number(match![2]), contents: [] };
+ contentsIndent = match![0].length;
}
while (true) {
@@ -101,15 +104,19 @@ export class Parser {
}
// @ts-ignore typescript is not smart enough to figure out that the types line up
- node.contents.push(this.parseListItem(node.name, node.indent));
+ node.contents.push(this.parseListItem(node.name, node.indent, contentsIndent));
}
return this.finish(node);
}
- parseListItem(kind: 'ol', indent: number): OrderedListItemNode;
- parseListItem(kind: 'ul', indent: number): UnorderedListItemNode;
- parseListItem(kind: 'ol' | 'ul', indent: number): OrderedListItemNode | UnorderedListItemNode {
+ parseListItem(kind: 'ol', indent: number, contentsIndent: number): OrderedListItemNode;
+ parseListItem(kind: 'ul', indent: number, contentsIndent: number): UnorderedListItemNode;
+ parseListItem(
+ kind: 'ol' | 'ul',
+ indent: number,
+ contentsIndent: number
+ ): OrderedListItemNode | UnorderedListItemNode {
this.pushPos();
// consume list token
this._t.next();
@@ -132,7 +139,7 @@ export class Parser {
let name: 'ordered-list-item' | 'unordered-list-item' =
kind === 'ol' ? 'ordered-list-item' : 'unordered-list-item';
- return this.finish({ name, contents, sublist, attrs });
+ return this.finish({ name, contents, contentsIndent, sublist, attrs });
}
parseFragment(opts: ParseFragmentOpts): FragmentNode[];
diff --git a/test/cases/list-item-deindenting.raw.ecmarkdown b/test/cases/list-item-deindenting.raw.ecmarkdown
new file mode 100644
index 0000000..f88c0dd
--- /dev/null
+++ b/test/cases/list-item-deindenting.raw.ecmarkdown
@@ -0,0 +1,22 @@
+1. Item 1
+ 1. Item 1a
+ (multi-line)
+ 1. Item 1b
+ (also multi-line)
+ ...and extra-indented
+1. Item 2
+ 1. Item 2a
+ (multi-line)
+ 1. Item 2b
+ (also multi-line)
+ ...and extra-indented
+1. Item 3
+ (multi-line but under-indented)
+1. Item 4
+ * unordered item
+ (multi-line)
+ * unordered item
+ (multi-line and extra-indented)
+ ...partially
+ * unordered item
+ (multi-line and under-indented)
diff --git a/test/cases/list-item-deindenting.raw.html b/test/cases/list-item-deindenting.raw.html
new file mode 100644
index 0000000..9d224db
--- /dev/null
+++ b/test/cases/list-item-deindenting.raw.html
@@ -0,0 +1,12 @@
+- Item 1
- Item 1a
+(multi-line)
- Item 1b
+(also multi-line)
+ ...and extra-indented
- Item 2
- Item 2a
+(multi-line)
- Item 2b
+(also multi-line)
+ ...and extra-indented
- Item 3
+(multi-line but under-indented)
- Item 4
- unordered item
+(multi-line)
- unordered item
+ (multi-line and extra-indented)
+...partially
- unordered item
+(multi-line and under-indented)
diff --git a/test/parser.js b/test/parser.js
index 0d8d5f3..5eced82 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -59,6 +59,7 @@ describe('Parser', function () {
{
name: 'ordered-list-item',
attrs: [],
+ contentsIndent: 3,
contents: [
{
contents: 'Foo. ',
diff --git a/test/run-cases.js b/test/run-cases.js
index d9a5be6..737b5d5 100644
--- a/test/run-cases.js
+++ b/test/run-cases.js
@@ -22,7 +22,7 @@ describe('baselines', () => {
? ecmarkdown.fragment
: ecmarkdown.algorithm;
let rawOutput = processor(input);
- let output = beautify(rawOutput);
+ let output = file.endsWith('.raw.ecmarkdown') ? rawOutput + '\n' : beautify(rawOutput);
let existing = fs.existsSync(snapshotFile) ? fs.readFileSync(snapshotFile, 'utf8') : null;
if (shouldUpdate) {
if (existing !== output) {