Skip to content

Commit c1c31de

Browse files
committed
Deindent rendered list item contents
1 parent f49b108 commit c1c31de

File tree

7 files changed

+78
-7
lines changed

7 files changed

+78
-7
lines changed

src/emitter.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@ import type {
1616
CommentNode,
1717
} from './node-types';
1818

19+
const getIndentMatcher = unaryMemoize(
20+
(length: number) => new RegExp(String.raw`\n[\p{Space_Separator}\t]{1,${length}}`, 'gu'),
21+
Array.from({ length: 20 }, (_, i) => i + 1)
22+
);
23+
1924
export class Emitter {
2025
str: string;
26+
indent: number;
2127

2228
constructor() {
2329
this.str = '';
30+
this.indent = 0;
2431
}
2532

2633
emit(node: Node | Node[]) {
@@ -106,7 +113,10 @@ export class Emitter {
106113
emitListItem(li: OrderedListItemNode | UnorderedListItemNode) {
107114
const attrs = li.attrs.map(a => ` ${a.key}=${JSON.stringify(a.value)}`).join('');
108115
this.str += `<li${attrs}>`;
116+
const oldIndent = this.indent;
117+
this.indent = li.contentsIndent;
109118
this.emitFragment(li.contents);
119+
this.indent = oldIndent;
110120
if (li.sublist !== null) {
111121
if (li.sublist.name === 'ol') {
112122
this.emitOrderedList(li.sublist);
@@ -130,7 +140,12 @@ export class Emitter {
130140
}
131141

132142
emitText(text: TextNode) {
133-
this.str += text.contents;
143+
let contents = text.contents;
144+
if (this.indent) {
145+
const indentMatcher = getIndentMatcher(this.indent);
146+
contents = contents.replace(indentMatcher, '\n');
147+
}
148+
this.str += contents;
134149
}
135150

136151
emitTick(node: TickNode) {
@@ -165,3 +180,15 @@ export class Emitter {
165180
this.str += `</${wrapping}>`;
166181
}
167182
}
183+
184+
function unaryMemoize<K, V>(fn: (arg: K) => V, prepopulate: K[] = []) {
185+
const cache = new Map(prepopulate.map(arg => [arg, fn(arg)]));
186+
return (arg: K) => {
187+
let value = cache.get(arg);
188+
if (!value) {
189+
value = fn(arg);
190+
cache.set(arg, value);
191+
}
192+
return value;
193+
};
194+
}

src/node-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export type OrderedListNode = {
188188
export type UnorderedListItemNode = {
189189
name: 'unordered-list-item';
190190
contents: FragmentNode[];
191+
contentsIndent: number;
191192
sublist: ListNode | null;
192193
attrs: { key: string; value: string; location: LocationRange }[];
193194
location: LocationRange;
@@ -196,6 +197,7 @@ export type UnorderedListItemNode = {
196197
export type OrderedListItemNode = {
197198
name: 'ordered-list-item';
198199
contents: FragmentNode[];
200+
contentsIndent: number;
199201
sublist: ListNode | null;
200202
attrs: { key: string; value: string; location: LocationRange }[];
201203
location: LocationRange;

src/parser.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,15 @@ export class Parser {
7979
const startTok = this._t.peek() as OrderedListToken | UnorderedListToken;
8080

8181
let node: Unlocated<ListNode>;
82+
let contentsIndent: number;
8283
if (startTok.name === 'ul') {
8384
const match = startTok.contents.match(/(\s*)\* /);
8485
node = { name: 'ul', indent: match![1].length, contents: [] };
86+
contentsIndent = match![0].length;
8587
} else {
8688
const match = startTok.contents.match(/(\s*)([^.]+)\. /);
8789
node = { name: 'ol', indent: match![1].length, start: Number(match![2]), contents: [] };
90+
contentsIndent = match![0].length;
8891
}
8992

9093
while (true) {
@@ -101,15 +104,19 @@ export class Parser {
101104
}
102105

103106
// @ts-ignore typescript is not smart enough to figure out that the types line up
104-
node.contents.push(this.parseListItem(node.name, node.indent));
107+
node.contents.push(this.parseListItem(node.name, node.indent, contentsIndent));
105108
}
106109

107110
return this.finish(node);
108111
}
109112

110-
parseListItem(kind: 'ol', indent: number): OrderedListItemNode;
111-
parseListItem(kind: 'ul', indent: number): UnorderedListItemNode;
112-
parseListItem(kind: 'ol' | 'ul', indent: number): OrderedListItemNode | UnorderedListItemNode {
113+
parseListItem(kind: 'ol', indent: number, contentsIndent: number): OrderedListItemNode;
114+
parseListItem(kind: 'ul', indent: number, contentsIndent: number): UnorderedListItemNode;
115+
parseListItem(
116+
kind: 'ol' | 'ul',
117+
indent: number,
118+
contentsIndent: number
119+
): OrderedListItemNode | UnorderedListItemNode {
113120
this.pushPos();
114121
// consume list token
115122
this._t.next();
@@ -132,7 +139,7 @@ export class Parser {
132139

133140
let name: 'ordered-list-item' | 'unordered-list-item' =
134141
kind === 'ol' ? 'ordered-list-item' : 'unordered-list-item';
135-
return this.finish({ name, contents, sublist, attrs });
142+
return this.finish({ name, contents, contentsIndent, sublist, attrs });
136143
}
137144

138145
parseFragment(opts: ParseFragmentOpts): FragmentNode[];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
1. Item 1
2+
1. Item 1a
3+
(multi-line)
4+
1. Item 1b
5+
(also multi-line)
6+
...and extra-indented
7+
1. Item 2
8+
1. Item 2a
9+
(multi-line)
10+
1. Item 2b
11+
(also multi-line)
12+
...and extra-indented
13+
1. Item 3
14+
(multi-line but under-indented)
15+
1. Item 4
16+
* unordered item
17+
(multi-line)
18+
* unordered item
19+
(multi-line and extra-indented)
20+
...partially
21+
* unordered item
22+
(multi-line and under-indented)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<ol><li>Item 1<ol><li>Item 1a
2+
(multi-line)</li><li>Item 1b
3+
(also multi-line)
4+
...and extra-indented</li></ol></li><li>Item 2<ol><li>Item 2a
5+
(multi-line)</li><li>Item 2b
6+
(also multi-line)
7+
...and extra-indented</li></ol></li><li>Item 3
8+
(multi-line but under-indented)</li><li>Item 4<ul><li>unordered item
9+
(multi-line)</li><li>unordered item
10+
(multi-line and extra-indented)
11+
...partially</li><li>unordered item
12+
(multi-line and under-indented)</li></ul></li></ol>

test/parser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ describe('Parser', function () {
5959
{
6060
name: 'ordered-list-item',
6161
attrs: [],
62+
contentsIndent: 3,
6263
contents: [
6364
{
6465
contents: 'Foo. ',

test/run-cases.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('baselines', () => {
2222
? ecmarkdown.fragment
2323
: ecmarkdown.algorithm;
2424
let rawOutput = processor(input);
25-
let output = beautify(rawOutput);
25+
let output = file.endsWith('.raw.ecmarkdown') ? rawOutput + '\n' : beautify(rawOutput);
2626
let existing = fs.existsSync(snapshotFile) ? fs.readFileSync(snapshotFile, 'utf8') : null;
2727
if (shouldUpdate) {
2828
if (existing !== output) {

0 commit comments

Comments
 (0)