@@ -235,6 +235,7 @@ class ProcessingState:
235235 new_lines : list [str ] = field (default_factory = list )
236236 backtick_options : dict [str , Any ] = field (default_factory = dict )
237237 backtick_standardize : bool = True
238+ indent : str = "" # Indentation prefix of current code block
238239
239240 def process_line (self , line : str , * , verbose : bool = False ) -> None :
240241 """Process a line of the Markdown file."""
@@ -264,20 +265,23 @@ def _process_start_markers(
264265 verbose : bool = False , # noqa: FBT001, FBT002, ARG002
265266 ) -> str | None :
266267 for marker_name in MARKERS :
267- if marker_name .endswith (":start" ) and is_marker (line , marker_name ):
268- # reset output in case previous output wasn't displayed
269- self .output = None
270- self .backtick_options = _extract_backtick_options (line )
271- self .section , _ = marker_name .rsplit (":" , 1 ) # type: ignore[assignment]
272-
273- # Standardize backticks if needed
274- if (
275- marker_name == "code:backticks:start"
276- and self .backtick_standardize
277- and "markdown-code-runner" in line
278- ):
279- return re .sub (r"\smarkdown-code-runner.*" , "" , line )
280- return line
268+ if marker_name .endswith (":start" ):
269+ match = is_marker (line , marker_name )
270+ if match :
271+ # reset output in case previous output wasn't displayed
272+ self .output = None
273+ self .backtick_options = _extract_backtick_options (line )
274+ self .section , _ = marker_name .rsplit (":" , 1 ) # type: ignore[assignment]
275+ self .indent = match .group ("spaces" )
276+
277+ # Standardize backticks if needed
278+ if (
279+ marker_name == "code:backticks:start"
280+ and self .backtick_standardize
281+ and "markdown-code-runner" in line
282+ ):
283+ return re .sub (r"\smarkdown-code-runner.*" , "" , line )
284+ return line
281285 return None
282286
283287 def _process_output_start (self , line : str ) -> None :
@@ -287,9 +291,16 @@ def _process_output_start(self, line: str) -> None:
287291 self .output ,
288292 list ,
289293 ), f"Output must be a list, not { type (self .output )} , line: { line } "
290- # Trim trailing whitespace from output lines
291- trimmed_output = [line .rstrip () for line in self .output ]
292- self .new_lines .extend ([line , MARKERS ["warning" ], * trimmed_output ])
294+ # Extract indent from OUTPUT:START line
295+ output_indent = line [: len (line ) - len (line .lstrip ())]
296+
297+ def _add_indent (s : str ) -> str :
298+ stripped = s .rstrip ()
299+ return output_indent + stripped if stripped else ""
300+
301+ trimmed_output = [_add_indent (ol ) for ol in self .output ]
302+ indented_warning = output_indent + MARKERS ["warning" ]
303+ self .new_lines .extend ([line , indented_warning , * trimmed_output ])
293304 else :
294305 self .original_output .append (line )
295306
@@ -301,6 +312,12 @@ def _process_output_end(self) -> None:
301312 self .original_output = []
302313 self .output = None # Reset output after processing end of the output section
303314
315+ def _strip_indent (self , line : str ) -> str :
316+ """Strip the code block's indentation prefix from a line."""
317+ if self .indent and line .startswith (self .indent ):
318+ return line [len (self .indent ) :]
319+ return line
320+
304321 def _process_code (
305322 self ,
306323 line : str ,
@@ -322,8 +339,13 @@ def _process_code(
322339 self .section = "normal"
323340 self .code = []
324341 self .backtick_options = {}
342+ self .indent = ""
325343 else :
326- self .code .append (remove_md_comment (line ) if remove_comment else line )
344+ # remove_md_comment already strips whitespace; for backticks, strip indent
345+ code_line = (
346+ remove_md_comment (line ) if remove_comment else self ._strip_indent (line )
347+ )
348+ self .code .append (code_line )
327349
328350 def _process_comment_code (self , line : str , * , verbose : bool ) -> None :
329351 _ , language = self .section .rsplit (":" , 1 )
0 commit comments