@@ -97,6 +97,16 @@ local LANG_COMMENT_CHARS = {
9797
9898local DEFAULT_COMMENT = { " #" }
9999
100+ --- Counter for generating unique cell IDs across the document.
101+ local cell_id_counter = 0
102+
103+ --- Generate a unique cell ID for annotation linking.
104+ --- @return string Unique cell identifier , e.g. " cell-annote-1"
105+ local function next_cell_id ()
106+ cell_id_counter = cell_id_counter + 1
107+ return ' cell-annote-' .. tostring (cell_id_counter )
108+ end
109+
100110-- ============================================================================
101111-- ANNOTATION DETECTION
102112-- ============================================================================
@@ -219,37 +229,44 @@ local function annotations_to_typst_dict(annotations)
219229 return ' (' .. table.concat (parts , ' , ' ) .. ' )'
220230end
221231
222- --- Store annotation data on a CodeBlock as a custom attribute .
223- --- The code-window module reads this attribute to wrap the code with
232+ --- Store annotation data and cell ID on a CodeBlock as custom attributes .
233+ --- The code-window module reads these attributes to wrap the code with
224234--- annotated-code().
225235--- @param code_block pandoc.CodeBlock
226236--- @param annotations table
237+ --- @param cell_id string Unique cell identifier for bidirectional linking
227238--- @return pandoc.CodeBlock
228- local function tag_code_block (code_block , annotations )
239+ local function tag_code_block (code_block , annotations , cell_id )
229240 code_block .attributes [' data-code-annotations' ] = annotations_to_typst_dict (annotations )
241+ code_block .attributes [' data-cell-id' ] = cell_id
230242 return code_block
231243end
232244
233245--- Build annotation list items as raw Typst blocks.
234246--- Each item renders the circled number inline with the description text.
235247--- @param ol pandoc.OrderedList
236248--- @param annotations table
249+ --- @param cell_id string Unique cell identifier for bidirectional linking
237250--- @return pandoc.Blocks
238- local function build_annotation_list (ol , annotations )
251+ local function build_annotation_list (ol , annotations , cell_id )
239252 local items = pandoc .Blocks ({})
240253
241254 for i , item in ipairs (ol .content ) do
242255 local annotation_number = ol .start + i - 1
243256 if annotations [annotation_number ] then
244257 local content_inlines = item [1 ].content or pandoc .Inlines (item [1 ])
245- -- Wrap content in Typst content brackets: #annotation-item(N, [content], colours)
258+ -- Wrap content in Typst content brackets:
259+ -- #annotation-item(N, [content], colours, cell-id: "cell-annote-N")
246260 local block_content = pandoc .Inlines ({})
247261 block_content :insert (pandoc .RawInline (
248262 ' typst' ,
249263 ' #annotation-item(' .. tostring (annotation_number ) .. ' , ['
250264 ))
251265 block_content :extend (content_inlines )
252- block_content :insert (pandoc .RawInline (' typst' , ' ], ' .. COLOURS_EXPR .. ' )' ))
266+ block_content :insert (pandoc .RawInline (
267+ ' typst' ,
268+ ' ], ' .. COLOURS_EXPR .. ' , cell-id: "' .. cell_id .. ' ")'
269+ ))
253270 items :insert (pandoc .Plain (block_content ))
254271 end
255272 end
@@ -265,34 +282,40 @@ end
265282--- @param block pandoc.CodeBlock
266283--- @return pandoc.CodeBlock | nil Cleaned code block , or nil if no annotations
267284--- @return table | nil Annotations table
285+ --- @return string | nil Cell ID for bidirectional linking
268286local function process_code_block (block )
269287 if block .attr .classes :includes (' cell-code' ) then
270- return nil , nil
288+ return nil , nil , nil
271289 end
272290 local resolved , annotations = resolve_annotations (block )
273291 if annotations then
274- resolved = tag_code_block (resolved , annotations )
292+ local cell_id = next_cell_id ()
293+ resolved = tag_code_block (resolved , annotations , cell_id )
294+ return resolved , annotations , cell_id
275295 end
276- return resolved , annotations
296+ return resolved , annotations , nil
277297end
278298
279299--- Process a cell Div, looking for .cell-code CodeBlocks inside.
280300--- @param div pandoc.Div
281301--- @return pandoc.Div | nil Modified div , or nil if no annotations
282302--- @return table | nil Annotations table
303+ --- @return string | nil Cell ID for bidirectional linking
283304local function process_cell_div (div )
284305 if not div .attr .classes :includes (' cell' ) then
285- return nil , nil
306+ return nil , nil , nil
286307 end
287308
288309 local found_annotations = nil
310+ local found_cell_id = nil
289311 local resolved_div = pandoc .walk_block (div , {
290312 CodeBlock = function (el )
291313 if el .attr .classes :includes (' cell-code' ) then
292314 local resolved , annotations = resolve_annotations (el )
293315 if annotations and next (annotations ) ~= nil then
294316 found_annotations = annotations
295- resolved = tag_code_block (resolved , annotations )
317+ found_cell_id = next_cell_id ()
318+ resolved = tag_code_block (resolved , annotations , found_cell_id )
296319 return resolved
297320 end
298321 end
@@ -301,9 +324,9 @@ local function process_cell_div(div)
301324 })
302325
303326 if found_annotations then
304- return resolved_div , found_annotations
327+ return resolved_div , found_annotations , found_cell_id
305328 end
306- return nil , nil
329+ return nil , nil , nil
307330end
308331
309332-- ============================================================================
@@ -343,40 +366,47 @@ return {
343366 local outputs = pandoc .Blocks ({})
344367 local pending_code = nil
345368 local pending_annotations = nil
369+ local pending_cell_id = nil
346370
347371 local function flush_pending ()
348372 if pending_code then
349373 outputs :insert (pending_code )
350374 end
351375 pending_code = nil
352376 pending_annotations = nil
377+ pending_cell_id = nil
353378 end
354379
355380 for _ , block in ipairs (blocks ) do
356381 if block .t == ' CodeBlock' then
357382 flush_pending ()
358- local resolved , annotations = process_code_block (block )
383+ local resolved , annotations , cell_id = process_code_block (block )
359384 if annotations then
360385 pending_code = resolved
361386 pending_annotations = annotations
387+ pending_cell_id = cell_id
362388 else
363389 outputs :insert (block )
364390 end
365391 elseif block .t == ' Div' and block .attr .classes :includes (' cell' ) then
366392 flush_pending ()
367- local resolved , annotations = process_cell_div (block )
393+ local resolved , annotations , cell_id = process_cell_div (block )
368394 if annotations then
369395 pending_code = resolved
370396 pending_annotations = annotations
397+ pending_cell_id = cell_id
371398 else
372399 outputs :insert (block )
373400 end
374401 elseif block .t == ' OrderedList' and pending_annotations then
375- local annotation_blocks = build_annotation_list (block , pending_annotations )
402+ local annotation_blocks = build_annotation_list (
403+ block , pending_annotations , pending_cell_id or ' '
404+ )
376405 outputs :insert (pending_code )
377406 outputs :extend (annotation_blocks )
378407 pending_code = nil
379408 pending_annotations = nil
409+ pending_cell_id = nil
380410 else
381411 flush_pending ()
382412 outputs :insert (block )
0 commit comments