Problem
The editor renders block-level markdown beautifully — headings, bullets, checklists, code blocks with syntax highlighting, quotes with bars. But inline formatting is completely absent: **bold**, *italic*, ~~strikethrough~~, and __underline__ all display as literal delimiter characters. For a markdown editor targeting developers, this is the most visible gap.
Context
The rendering pipeline processes blocks individually through three paths in internal/editor/render.go:
renderInactiveBlock() (line 549) — blocks not being edited; renders static styled text via lipgloss
renderViewBlock() (line 718) — view mode rendering; same static pattern
renderActiveBlock() (line 88) — the block currently being edited; uses forked textarea on raw runes
All three follow the same pattern: get content → wrap text → apply block-level style → render. Inline formatting is a text transformation step inserted between wrap and render.
Existing pattern to extend: Checked checklist items already apply lipgloss.NewStyle().Faint(true) to entire wrapped text (render.go:616-619). This feature extends that from whole-block to span-level styling.
Wrap ordering: wrapText() calls textarea.Wrap() on raw runes. Wrap first → then parse and style inline formatting on wrapped text. Delimiters consume visual width during wrapping but are hidden after — acceptable, consistent with how glow and other terminal markdown renderers work.
Scope boundary: The active textarea (block being edited) shows raw markdown. Formatting appears when you click away. This matches Notion, Typora source mode, etc.
Approach: State-machine inline parser
Write a renderInlineMarkdown(text string) string function that walks text character-by-character, tracking delimiter open/close state, and emits lipgloss-styled spans.
- Handles nesting correctly (
***bold italic***, **bold with *italic* inside**)
- No external dependencies — pure string processing + lipgloss
- Ignores
_ in snake_case (only match __ at word boundaries or standalone)
- Extracted to
internal/format/ for reuse and easy unit testing
- Skips code blocks entirely (already syntax-highlighted)
Tasks
Test plan
Scope
Type: enhancement
Size: medium
Problem
The editor renders block-level markdown beautifully — headings, bullets, checklists, code blocks with syntax highlighting, quotes with bars. But inline formatting is completely absent:
**bold**,*italic*,~~strikethrough~~, and__underline__all display as literal delimiter characters. For a markdown editor targeting developers, this is the most visible gap.Context
The rendering pipeline processes blocks individually through three paths in
internal/editor/render.go:renderInactiveBlock()(line 549) — blocks not being edited; renders static styled text via lipglossrenderViewBlock()(line 718) — view mode rendering; same static patternrenderActiveBlock()(line 88) — the block currently being edited; uses forked textarea on raw runesAll three follow the same pattern: get content → wrap text → apply block-level style → render. Inline formatting is a text transformation step inserted between wrap and render.
Existing pattern to extend: Checked checklist items already apply
lipgloss.NewStyle().Faint(true)to entire wrapped text (render.go:616-619). This feature extends that from whole-block to span-level styling.Wrap ordering:
wrapText()callstextarea.Wrap()on raw runes. Wrap first → then parse and style inline formatting on wrapped text. Delimiters consume visual width during wrapping but are hidden after — acceptable, consistent with howglowand other terminal markdown renderers work.Scope boundary: The active textarea (block being edited) shows raw markdown. Formatting appears when you click away. This matches Notion, Typora source mode, etc.
Approach: State-machine inline parser
Write a
renderInlineMarkdown(text string) stringfunction that walks text character-by-character, tracking delimiter open/close state, and emits lipgloss-styled spans.***bold italic***,**bold with *italic* inside**)_insnake_case(only match__at word boundaries or standalone)internal/format/for reuse and easy unit testingTasks
RenderInlineMarkdown(text string) stringininternal/format/inline.go**bold**,*italic*,~~strikethrough~~,__underline__, and nesting****),snake_casenot treated as underline, unmatched delimiters rendered literallyrenderInactiveBlock()afterwrapText(), before block-type styling — skip for code blocksrenderViewBlock()at the same pipeline position — skip for code blocksTest plan
**bold**renders bold, no asterisks visible***bold italic***renders with both styles**bold with *nested italic* inside**renders correctly**text**without formattingsnake_case_variablesare NOT treated as underline**textrenders literally (no crash, no style leak)Scope
Type: enhancement
Size: medium