Prohlížeč zdrojového kódu
docs/specs/markdown-code-highlighting.md
# Spec: Markdown Code Block Syntax Highlighting
- **Status:** active
- **Created:** 2026-03-12
- **Related code:** `app/helpers/highlight_helper.rb`, `spec/helpers/highlight_helper_spec.rb`
## Overview
Fenced code blocks in Markdown content are syntax-highlighted using Rouge, consistent with the manually-highlighted source code blocks elsewhere in the app. This applies wherever Markdown is rendered: example detail pages and the source browser Markdown view.
## Behaviour
- A fenced code block with a recognised language hint (e.g. ` ```ruby `) is highlighted using the matching Rouge lexer.
- A fenced code block with no language hint, or an unrecognised language, falls back to plain-text rendering without raising an error.
- The rendered HTML uses the same markup structure as `highlight_code` so existing Rouge CSS applies without additional stylesheet changes.
- Highlighting applies wherever `render_markdown` is called.
- A fenced code block may include an optional label in the info string, separated from the language hint by a space (e.g. ` ```ruby app/models/log.rb ` or ` ```ruby log_header.rb, log_item.rb `). The label is arbitrary text extending to the end of the fence line. When present, the label is displayed above the code block. A label without a language hint is not supported.
## Implementation Notes
- `SourceAwareMarkdownRenderer#block_code` delegates to `HighlightHelper.rouge_highlight`, a module-level method extracted from the old `highlight_code` instance method.
- `HighlightHelper.rouge_highlight` returns a plain HTML string (no `raw` wrapping); callers that need an `ActiveSupport::SafeBuffer` call `raw` themselves (`highlight_code` helper method, `render_markdown` via Redcarpet).
- `block_code` receives `nil` for language when no hint is given; `Rouge::Lexer.find(nil)` returns `nil`, so the fallback to `PlainText` handles both the no-hint and unknown-language cases.
- No CSS changes needed for highlighting — `rouge.scss` already covers `.highlight` for both light and dark themes.
- **Label pre-processing workaround:** Redcarpet stops reading the fence info string at the first space, causing fenced blocks with labels to not be recognised as code blocks at all. `render_markdown` pre-processes the Markdown with a `gsub` that replaces all spaces inside fence info lines (lines starting with `` ``` `` or `~~~`) with `\x1F` (ASCII Unit Separator) before handing the text to Redcarpet. `block_code` then decodes `\x1F` back to spaces and splits on the first space to separate the language from the label. Labels are rendered as raw HTML (not escaped) in a `<p class="code-block__label">` element before the `<pre>`.
## Tests
- `spec/helpers/highlight_helper_spec.rb` — fenced block with language renders Rouge HTML; fenced block without language renders plain; unknown language does not raise
- `spec/helpers/highlight_helper_spec.rb` — fenced block with `ruby path/to/file.rb` renders `.code-block__label` containing the path; fenced block with only a language hint renders no label; label text is HTML-escaped
## See Also
- [Rich Example Content](example-content.md) — example detail pages render Markdown via `render_markdown`, which is the primary consumer.
- [Source Browser](source-browser.md) — source browser Markdown view also uses `render_markdown`.
- [Styling and CSS](styling.md) — Rouge CSS (`rouge.scss`) provides the colour rules consumed by the highlighted output.
## Change Log
- 2026-03-12: Initial prospective spec.
- 2026-03-23: Added optional label in fence info string (space-separated, arbitrary text, raw HTML).