Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions markdown_code_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ class ProcessingState:
new_lines: list[str] = field(default_factory=list)
backtick_options: dict[str, Any] = field(default_factory=dict)
backtick_standardize: bool = True
indent: str = "" # Indentation prefix of current code block

def process_line(self, line: str, *, verbose: bool = False) -> None:
"""Process a line of the Markdown file."""
Expand Down Expand Up @@ -269,6 +270,7 @@ def _process_start_markers(
self.output = None
self.backtick_options = _extract_backtick_options(line)
self.section, _ = marker_name.rsplit(":", 1) # type: ignore[assignment]
self.indent = self._get_indent(line)

# Standardize backticks if needed
if (
Expand All @@ -280,16 +282,23 @@ def _process_start_markers(
return line
return None

@staticmethod
def _get_indent(line: str) -> str:
"""Extract leading whitespace from a line."""
return line[: len(line) - len(line.lstrip())]

def _process_output_start(self, line: str) -> None:
self.section = "output"
if not self.skip_code_block:
assert isinstance(
self.output,
list,
), f"Output must be a list, not {type(self.output)}, line: {line}"
# Trim trailing whitespace from output lines
trimmed_output = [line.rstrip() for line in self.output]
self.new_lines.extend([line, MARKERS["warning"], *trimmed_output])
indent = self._get_indent(line)
trimmed_output = [
indent + ol.rstrip() if ol.strip() else "" for ol in self.output
]
self.new_lines.extend([line, indent + MARKERS["warning"], *trimmed_output])
else:
self.original_output.append(line)

Expand All @@ -301,6 +310,12 @@ def _process_output_end(self) -> None:
self.original_output = []
self.output = None # Reset output after processing end of the output section

def _strip_indent(self, line: str) -> str:
"""Strip the code block's indentation prefix from a line."""
if self.indent and line.startswith(self.indent):
return line[len(self.indent) :]
return line

def _process_code(
self,
line: str,
Expand All @@ -322,8 +337,13 @@ def _process_code(
self.section = "normal"
self.code = []
self.backtick_options = {}
self.indent = ""
else:
self.code.append(remove_md_comment(line) if remove_comment else line)
# remove_md_comment already strips whitespace; for backticks, strip indent
code_line = (
remove_md_comment(line) if remove_comment else self._strip_indent(line)
)
self.code.append(code_line)

def _process_comment_code(self, line: str, *, verbose: bool) -> None:
_, language = self.section.rsplit(":", 1)
Expand Down
76 changes: 76 additions & 0 deletions tests/test_main_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,3 +881,79 @@ def test_trailing_whitespace_trimming() -> None:
assert output_section_hidden[3] == "Final line"
assert output_section_hidden[4] == "" # Line with only spaces becomes empty
assert output_section_hidden[5] == "" # Trailing empty line preserved


def test_indented_code_blocks() -> None:
"""Test that indented code blocks (e.g., in list items) work correctly."""
# Test case 1: Indented backtick code block (4 spaces, like in a list)
input_lines = [
"1. List item:",
"",
" ```python markdown-code-runner",
" print('hello')",
" ```",
" <!-- OUTPUT:START -->",
" old output",
" <!-- OUTPUT:END -->",
]
expected_output = [
"1. List item:",
"",
" ```python markdown-code-runner",
" print('hello')",
" ```",
" <!-- OUTPUT:START -->",
" " + MARKERS["warning"],
" hello",
"",
" <!-- OUTPUT:END -->",
]
assert_process(input_lines, expected_output, backtick_standardize=False)

# Test case 2: Indented code with internal indentation (Python function)
input_lines = [
"1. Example:",
"",
" ```python markdown-code-runner",
" def foo():",
" return 42",
" print(foo())",
" ```",
" <!-- OUTPUT:START -->",
" <!-- OUTPUT:END -->",
]
expected_output = [
"1. Example:",
"",
" ```python markdown-code-runner",
" def foo():",
" return 42",
" print(foo())",
" ```",
" <!-- OUTPUT:START -->",
" " + MARKERS["warning"],
" 42",
"",
" <!-- OUTPUT:END -->",
]
assert_process(input_lines, expected_output, backtick_standardize=False)

# Test case 3: Indented bash code block
input_lines = [
" ```bash markdown-code-runner",
' echo "indented bash"',
" ```",
" <!-- OUTPUT:START -->",
" <!-- OUTPUT:END -->",
]
expected_output = [
" ```bash markdown-code-runner",
' echo "indented bash"',
" ```",
" <!-- OUTPUT:START -->",
" " + MARKERS["warning"],
" indented bash",
"",
" <!-- OUTPUT:END -->",
]
assert_process(input_lines, expected_output, backtick_standardize=False)