Skip to content

Commit 9679c58

Browse files
committed
feat(mdviewer): add fine-grained data-source-line to nested elements
Recursively annotate list items, table cells, and nested blockquote children with data-source-line attributes for more granular cursor sync and scroll sync. Sub-list items, individual table cells, and paragraphs inside blockquotes now have their own source line mapping.
1 parent 5234f68 commit 9679c58

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

src-mdviewer/src/bridge.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,93 @@ function _annotateTokenLines(tokens) {
5050
if (token.type !== "space") {
5151
token._sourceLine = line;
5252
}
53+
// Recursively annotate children with their source lines
54+
_annotateTokenChildren(token, line);
55+
if (token.raw) {
56+
line += (token.raw.match(/\n/g) || []).length;
57+
}
58+
}
59+
}
60+
61+
function _annotateTokenChildren(token, startLine) {
62+
// List items
63+
if (token.type === "list" && token.items) {
64+
let itemLine = startLine;
65+
for (const item of token.items) {
66+
item._sourceLine = itemLine;
67+
if (item.tokens) {
68+
_annotateNestedTokens(item.tokens, itemLine);
69+
}
70+
if (item.raw) {
71+
itemLine += (item.raw.match(/\n/g) || []).length;
72+
}
73+
}
74+
}
75+
// Blockquote children
76+
if (token.type === "blockquote" && token.tokens) {
77+
_annotateNestedTokens(token.tokens, startLine);
78+
}
79+
// Table rows
80+
if (token.type === "table") {
81+
if (token.header) {
82+
for (const cell of token.header) {
83+
cell._sourceLine = startLine;
84+
}
85+
}
86+
if (token.rows) {
87+
let rowLine = startLine + 2;
88+
for (const row of token.rows) {
89+
for (const cell of row) {
90+
cell._sourceLine = rowLine;
91+
}
92+
rowLine++;
93+
}
94+
}
95+
}
96+
}
97+
98+
function _annotateNestedTokens(tokens, startLine) {
99+
let line = startLine;
100+
for (const token of tokens) {
101+
if (token.type !== "space") {
102+
token._sourceLine = line;
103+
}
104+
// Recurse into nested lists
105+
if (token.type === "list" && token.items) {
106+
let itemLine = line;
107+
for (const item of token.items) {
108+
item._sourceLine = itemLine;
109+
if (item.tokens) {
110+
_annotateNestedTokens(item.tokens, itemLine);
111+
}
112+
if (item.raw) {
113+
itemLine += (item.raw.match(/\n/g) || []).length;
114+
}
115+
}
116+
}
117+
// Recurse into blockquote children
118+
if (token.type === "blockquote" && token.tokens) {
119+
_annotateNestedTokens(token.tokens, line);
120+
}
121+
// Annotate table rows
122+
if (token.type === "table") {
123+
// Header row
124+
if (token.header) {
125+
for (const cell of token.header) {
126+
cell._sourceLine = line;
127+
}
128+
}
129+
// Body rows: each row is one line after header + separator (2 lines)
130+
if (token.rows) {
131+
let rowLine = line + 2; // skip header + separator lines
132+
for (const row of token.rows) {
133+
for (const cell of row) {
134+
cell._sourceLine = rowLine;
135+
}
136+
rowLine++;
137+
}
138+
}
139+
}
53140
if (token.raw) {
54141
line += (token.raw.match(/\n/g) || []).length;
55142
}
@@ -74,7 +161,9 @@ marked.use({
74161
heading: _withSourceLine(_proto.heading, /^<h[1-6]/),
75162
paragraph: _withSourceLine(_proto.paragraph, /^<p/),
76163
list: _withSourceLine(_proto.list, /^<[ou]l/),
164+
listitem: _withSourceLine(_proto.listitem, /^<li/),
77165
table: _withSourceLine(_proto.table, /^<table/),
166+
tablecell: _withSourceLine(_proto.tablecell, /^<t[dh]/),
78167
blockquote: _withSourceLine(_proto.blockquote, /^<blockquote/),
79168
code: _withSourceLine(_proto.code, /^<pre/),
80169
hr: _withSourceLine(_proto.hr, /^<hr/)

0 commit comments

Comments
 (0)