feat(markdown): open a markdown file as a text document - #734
Open
andiwand wants to merge 1 commit into
Open
Conversation
Implements stages 1 to 3 of `src/odr/internal/markdown/PLAN.md`: a `.md` decodes to a `TextRoot`, so the generic html renderer and every binding get it without format-specific code. CommonMark plus the GitHub extensions, parsed with md4c (new dependency): headings, paragraphs, lists, block quotes, code blocks, emphasis, links, tables and task lists. Raw html, images and horizontal rules are not modelled yet. `FileType::markdown` moves from `FileCategory::text` to `FileCategory::document` with it. `detect_by_content` stays false — markdown has no signature, and a content probe for it is a probe for prose — so the caller routes on the file name and a `.md` still opens as a text file by default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeD48ZqWKpgMsQCvkiSRPn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
Implements stages 1 to 3 of the markdown plan that was already in the repo
(
src/odr/internal/markdown/PLAN.md). A.mdfile decodes to aTextRoot, sothe generic html renderer and every binding get it without format-specific code
— rather than a markdown→html renderer next to
html/text_file.cpp, whichwould produce html only, with no element api and nothing for
JNI/embind/pybind/ObjC.
CommonMark plus the GitHub extensions, parsed with md4c (new dependency,
md4c/0.5.2, MIT): headings, paragraphs, soft and hard breaks, lists (orderedcounting from their
start), block quotes, code blocks, emphasis, strong,strikethrough, inline code, links, GFM tables and task lists.
FileType::markdownmoves fromFileCategory::texttoFileCategory::documentwithDocumentType::text.detect_by_contentstaysfalse — markdown has no signature, and a content probe for it is a probe
for "prose with occasional punctuation" — so the only way in is
DecodedFile(file, FileType::markdown), and a.mdstill opens as a text fileby default. Callers route on the file name, which is what they already have.
Design rationale and the mapping table are in the new
src/odr/internal/markdown/AGENTS.md;PLAN.mdis marked up with what it got wrong.Three things the plan had wrong, found by rendering a sample
The element-tree tests would not have caught any of these on their own.
html::translate_paragraphtakes only font family and size from aparagraph's text style (
translate_block_font_style) — weight, slant anddecoration are expected on the spans inside. Headings rendered unbolded.
Only the weight goes on the span: the paragraph's size is already in
em,and repeating it compounded
2eminto4em. ATHcell has the sameshape.
MD_BLOCK_Pinside a tight list item, andhtml::translate_list_itemwrites the marker into the item's first paragraph — so without one, every
bullet was silently dropped.
chain cannot carry both, and the renderer walks
table_first_column'ssiblings, so a 3-column table emitted six
<col>s — three columns and threerows. Now on their own chain via
append_column, the shape odf uses.The plan's fourth claim — that
html::translate_listhardcodes<ul>— isstale; the renderer takes the label from
list_item_markernow, so orderedlists number themselves.
Known gaps, documented rather than dropped quietly
) stayliteral. Numeric references resolve. The plan asked whether the conan
package exports the
md4c-htmlcomponent whoseentity_lookupresolves the~2000 named entities: it does, and the symbol is in the archive, but
entity.his not shipped — so using it means declaring md4c's privateMD_ENTITYlayout ourselves, where a mismatch corrupts silently instead offailing to link. Left out; worth revisiting before claiming CommonMark
conformance.
and inventing one means deciding what
Text::content()returns for it infour bindings.
Note for whoever picks it up:
html::translate_imagewrites the<img>atwidth:100%;height:100%absolutely positioned inside the frame'sdiv, so aframe with no dimensions renders nothing at all, and markdown carries none.
escape_attributed by therenderer, exactly as an odt's is — this is a second consumer of the existing
document-link policy, not a third policy.
Testing
20 gtests in
test/src/internal/markdown/markdown_file_test.cpp, all inlinestring literals in and element tree out, no fixture files. The rendered html
was eyeballed end to end for a document exercising every construct above.
FileTypeCapabilities.declaration_matches_the_enginesandcolor_scheme_matches_the_htmlnow exercise the.mdalready inodr-public,so nothing needs adding to the test-data repo.
Before merging
odr-public/README.mdwasskipped by
html_output_testwhile the row declaredopen = false, and nowrenders.
feat, i.e. a minor bump. The category flip isapi-visible, but the row was classification-only and declared no capabilities,
so nothing could hold a decoded markdown file to be broken — the plan's own
reasoning. If you read it as breaking, the subject needs a
!and thisbecomes v7.0.0.